activerecord-postgresql-cursors 1.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/main.yml +71 -0
- data/.rubocop-minitest.yml +240 -0
- data/.rubocop.yml +5683 -0
- data/.rubocop_todo.yml +26 -0
- data/FUNDING.yml +2 -0
- data/Gemfile +13 -16
- data/Guardfile +8 -10
- data/MIT-LICENSE +1 -2
- data/{README.rdoc → README.md} +17 -10
- data/Rakefile +7 -6
- data/activerecord-postgresql-cursors.gemspec +15 -15
- data/lib/active_record/postgresql_cursors/cursors.rb +14 -55
- data/lib/active_record/postgresql_cursors/version.rb +2 -2
- data/lib/activerecord-postgresql-cursors.rb +50 -66
- data/sonar-project.properties +17 -0
- data/test/ci/github/database.yml +10 -0
- data/test/cursor_test.rb +91 -0
- data/test/test_helper.rb +34 -45
- metadata +22 -19
- data/lib/active_record/postgresql_cursors/cursors_2.rb +0 -75
- data/test/cursor_tests.rb +0 -97
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2024-08-06 02:41:08 UTC using RuboCop version 1.65.1.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 1
|
10
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Exclude:
|
13
|
+
- 'lib/activerecord-postgresql-cursors.rb'
|
14
|
+
|
15
|
+
# Offense count: 1
|
16
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
17
|
+
Metrics/PerceivedComplexity:
|
18
|
+
Max: 9
|
19
|
+
|
20
|
+
# Offense count: 1
|
21
|
+
# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
|
22
|
+
# CheckDefinitionPathHierarchyRoots: lib, spec, test, src
|
23
|
+
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
|
24
|
+
Naming/FileName:
|
25
|
+
Exclude:
|
26
|
+
- 'lib/activerecord-postgresql-cursors.rb'
|
data/FUNDING.yml
ADDED
data/Gemfile
CHANGED
@@ -1,21 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
gemspec
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
15
|
-
gem "guard-minitest"
|
16
|
-
gem "simplecov"
|
17
|
-
|
18
|
-
if File.exists?('Gemfile.local')
|
19
|
-
instance_eval File.read('Gemfile.local')
|
20
|
-
end
|
7
|
+
gem 'guard'
|
8
|
+
gem 'guard-minitest'
|
9
|
+
gem 'minitest'
|
10
|
+
gem 'minitest-reporters'
|
11
|
+
gem 'pg'
|
12
|
+
gem 'rake'
|
13
|
+
gem 'rubocop', require: false
|
14
|
+
gem 'rubocop-minitest', require: false
|
15
|
+
gem 'simplecov', require: false
|
16
|
+
gem 'simplecov_json_formatter', require: false
|
21
17
|
|
18
|
+
instance_eval File.read('Gemfile.local') if File.exist?('Gemfile.local')
|
data/Guardfile
CHANGED
@@ -1,17 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
guard 'minitest', :
|
3
|
-
watch(%r
|
3
|
+
guard 'minitest', test_folders: 'test', test_file_patterns: '*_tests.rb' do
|
4
|
+
watch(%r{^test/(.+)_tests\.rb})
|
4
5
|
|
5
|
-
watch(%r
|
6
|
-
|
6
|
+
watch(%r{^lib/(.*)([^/]+)\.rb}) do |_m|
|
7
|
+
'test/cursor_tests.rb'
|
7
8
|
end
|
8
9
|
|
9
|
-
watch(%r
|
10
|
-
|
10
|
+
watch(%r{^test/test_helper\.rb}) do
|
11
|
+
'test'
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
if File.
|
15
|
-
instance_eval File.read('Guardfile.local')
|
16
|
-
end
|
17
|
-
|
15
|
+
instance_eval File.read('Guardfile.local') if File.exist?('Guardfile.local')
|
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2008-
|
1
|
+
Copyright (c) 2008-2024 J Smith <dark.panda@gmail.com>
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person
|
4
4
|
obtaining a copy of this software and associated documentation
|
@@ -20,4 +20,3 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
20
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
21
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
22
|
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
-
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# ActiveRecord PostgreSQL Cursors
|
2
2
|
|
3
3
|
This extension allows you to loop through record sets using cursors in an
|
4
4
|
Enumerable fashion. This allows you to cut down memory usage by only pulling
|
@@ -9,21 +9,24 @@ To use a cursor, just change the first parameter to an
|
|
9
9
|
ActiveRecord::Base.find to :cursor instead of :first or :all or
|
10
10
|
whatever or use the ActiveRecord::Base.cursor method directly.
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
```ruby
|
13
|
+
MyModel.find(:cursor, :conditions => 'some_column = true').each do |r|
|
14
|
+
puts r.inspect
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
+
MyModel.find(:cursor).collect { |r| r.foo / PI }.avg
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
MyModel.cursor.each do |r|
|
20
|
+
puts r.inspect
|
21
|
+
end
|
22
|
+
```
|
21
23
|
|
22
24
|
All ActiveRecord::Base.find options are available and should work as-is.
|
23
25
|
As a bonus, the PostgreSQLCursor object returned includes Enumerable,
|
24
26
|
so you can iterate to your heart's content.
|
25
27
|
|
26
|
-
This extension should work
|
28
|
+
This extension should work with Rails 6.1+. For older versions of Rails, try
|
29
|
+
out older versions of the gem.
|
27
30
|
|
28
31
|
At the moment, this is a non-scrollable cursor -- it will only fetch
|
29
32
|
forward. Also note that these cursors are non-updateable/insensitive to
|
@@ -48,5 +51,9 @@ while the first query is just used to build the larger joined query. This
|
|
48
51
|
allows for a brief window between the point that the cursor query is
|
49
52
|
created and the time it is executed. In these cases, it may be wise to wrap
|
50
53
|
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
|
54
|
+
the underlying data don't interfere with your cursor's visibility.
|
52
55
|
|
56
|
+
## License
|
57
|
+
|
58
|
+
This gem is licensed under an MIT-style license. See the +MIT-LICENSE+ file for
|
59
|
+
details.
|
data/Rakefile
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
# -*- ruby -*-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'rubygems'
|
5
4
|
require 'rubygems/package_task'
|
@@ -7,22 +6,24 @@ require 'rake/testtask'
|
|
7
6
|
require 'rdoc/task'
|
8
7
|
require 'bundler/gem_tasks'
|
9
8
|
|
10
|
-
|
9
|
+
$LOAD_PATH.push File.expand_path(File.dirname(__FILE__), 'lib')
|
11
10
|
|
12
11
|
version = ActiveRecord::PostgreSQLCursors::VERSION
|
13
12
|
|
14
13
|
desc 'Test PostgreSQL extensions'
|
15
14
|
Rake::TestTask.new(:test) do |t|
|
16
|
-
t.
|
15
|
+
t.libs << "#{File.dirname(__FILE__)}/test"
|
16
|
+
t.test_files = FileList['test/**/*_test.rb']
|
17
17
|
t.verbose = !!ENV['VERBOSE_TESTS']
|
18
18
|
t.warning = !!ENV['WARNINGS']
|
19
19
|
end
|
20
20
|
|
21
|
+
task default: :test
|
22
|
+
|
21
23
|
desc 'Build docs'
|
22
24
|
Rake::RDocTask.new do |t|
|
23
25
|
t.title = "ActiveRecord PostgreSQL Cursors #{version}"
|
24
|
-
t.main = 'README.
|
26
|
+
t.main = 'README.md'
|
25
27
|
t.rdoc_dir = 'doc'
|
26
28
|
t.rdoc_files.include('README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb')
|
27
29
|
end
|
28
|
-
|
@@ -1,26 +1,26 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path('
|
3
|
+
require File.expand_path('lib/active_record/postgresql_cursors/version', __dir__)
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
6
|
+
s.name = 'activerecord-postgresql-cursors'
|
7
7
|
s.version = ActiveRecord::PostgreSQLCursors::VERSION
|
8
8
|
|
9
|
-
s.required_rubygems_version = Gem::Requirement.new(
|
10
|
-
s.
|
11
|
-
s.
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
10
|
+
s.required_ruby_version = '>= 3.0'
|
11
|
+
s.authors = ['J Smith']
|
12
|
+
s.description = 'Provides some support for PostgreSQL cursors in ActiveRecord.'
|
12
13
|
s.summary = s.description
|
13
|
-
s.email =
|
14
|
-
s.license =
|
14
|
+
s.email = 'dark.panda@gmail.com'
|
15
|
+
s.license = 'MIT'
|
15
16
|
s.extra_rdoc_files = [
|
16
|
-
|
17
|
+
'README.md'
|
17
18
|
]
|
18
|
-
s.files = `git ls-files`.split(
|
19
|
+
s.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
19
20
|
s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
20
|
-
s.
|
21
|
-
s.
|
22
|
-
s.require_paths = ["lib"]
|
21
|
+
s.homepage = 'http://github.com/dark-panda/activerecord-postgresql-cursors'
|
22
|
+
s.require_paths = ['lib']
|
23
23
|
|
24
|
-
s.add_dependency(
|
24
|
+
s.add_dependency('activerecord', ['>= 6.1'])
|
25
|
+
s.metadata['rubygems_mfa_required'] = 'true'
|
25
26
|
end
|
26
|
-
|
@@ -1,15 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module ActiveRecord
|
3
4
|
module CursorExtensions
|
4
5
|
extend ActiveSupport::Concern
|
5
6
|
|
6
|
-
|
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,
|
7
|
+
# Find using cursors. A PostgreSQLCursor object will be returned,
|
13
8
|
# which can then be used as an Enumerable to loop through the
|
14
9
|
# results.
|
15
10
|
#
|
@@ -18,45 +13,30 @@ module ActiveRecord
|
|
18
13
|
# is pretty unlikely to clash if you're using nested cursors.
|
19
14
|
# Alternatively, you can supply a specific cursor name by
|
20
15
|
# supplying a :cursor_name option.
|
21
|
-
def
|
22
|
-
|
16
|
+
def cursor(*args)
|
17
|
+
find_with_cursor('cursor', *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_with_cursor(*args)
|
23
23
|
options = args.extract_options!
|
24
24
|
cursor_name = options.delete(:cursor_name)
|
25
25
|
find_cursor(cursor_name, options)
|
26
|
-
else
|
27
|
-
find_without_cursors(*args)
|
28
26
|
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def cursor(*args)
|
32
|
-
find_with_cursors('cursor', *args)
|
33
|
-
end
|
34
27
|
|
35
|
-
private
|
36
28
|
# Find method for using cursors. This works just like the regular
|
37
29
|
# ActiveRecord::Base#find_every method, except it returns a
|
38
30
|
# PostgreSQLCursor object that can be used to loop through records.
|
39
31
|
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 = if ActiveRecord::VERSION::MAJOR >= 4
|
45
|
-
apply_finder_options(options, silence_deprecation = true)
|
46
|
-
else
|
47
|
-
apply_finder_options(options)
|
48
|
-
end
|
32
|
+
raise CursorsNotSupported, "#{connection.class} doesn't support cursors" unless connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
49
33
|
|
34
|
+
relation = merge(options.slice(:readonly, :references, :order, :limit, :joins, :group, :having, :offset, :select, :uniq))
|
50
35
|
including = (relation.eager_load_values + relation.includes_values).uniq
|
51
36
|
|
52
37
|
if including.present?
|
53
|
-
join_dependency =
|
54
|
-
|
55
|
-
else
|
56
|
-
ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
|
57
|
-
end
|
58
|
-
|
59
|
-
join_relation = relation.construct_relation_for_association_find(join_dependency)
|
38
|
+
join_dependency = construct_join_dependency(joins_values, nil)
|
39
|
+
join_relation = apply_join_dependency
|
60
40
|
|
61
41
|
ActiveRecord::PostgreSQLCursor.new(self, cursor_name, join_relation, join_dependency)
|
62
42
|
else
|
@@ -64,23 +44,6 @@ module ActiveRecord
|
|
64
44
|
end
|
65
45
|
end
|
66
46
|
end
|
67
|
-
|
68
|
-
class PostgreSQLCursor
|
69
|
-
def initialize_with_rails(model, cursor_name, relation, join_dependency = nil)
|
70
|
-
@relation = relation
|
71
|
-
|
72
|
-
query = if ActiveRecord::VERSION::MAJOR >= 4
|
73
|
-
model.connection.unprepared_statement do
|
74
|
-
relation.to_sql
|
75
|
-
end
|
76
|
-
else
|
77
|
-
relation.to_sql
|
78
|
-
end
|
79
|
-
|
80
|
-
initialize_without_rails(model, cursor_name, query, join_dependency)
|
81
|
-
end
|
82
|
-
alias_method_chain :initialize, :rails
|
83
|
-
end
|
84
47
|
end
|
85
48
|
|
86
49
|
class ActiveRecord::Relation
|
@@ -89,10 +52,6 @@ end
|
|
89
52
|
|
90
53
|
class ActiveRecord::Base
|
91
54
|
class << self
|
92
|
-
|
93
|
-
delegate :cursor, :to => :all
|
94
|
-
else
|
95
|
-
delegate :cursor, :to => :scoped
|
96
|
-
end
|
55
|
+
delegate :cursor, to: :all
|
97
56
|
end
|
98
57
|
end
|
@@ -1,40 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module ActiveRecord
|
3
4
|
# Exception raised when database cursors aren't supported, which they
|
4
5
|
# absolutely should be in our app.
|
5
6
|
class CursorsNotSupported < ActiveRecordError; end
|
6
7
|
|
7
|
-
module PostgreSQLCursors
|
8
|
-
module JoinDependency
|
9
|
-
# Extra method we can use to clear out a couple of things in
|
10
|
-
# JoinDependency so we can use some of the methods for our
|
11
|
-
# cursors code.
|
12
|
-
def clear_with_cursor
|
13
|
-
@reflections = []
|
14
|
-
@base_records_hash = {}
|
15
|
-
@base_records_in_order = []
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
8
|
# PostgreSQLCursor is an Enumerable class so you can use each, map,
|
21
9
|
# any? and all of those nice Enumerable methods.
|
22
10
|
#
|
23
11
|
# At the moment, cursors aren't scrollable and are fetch forward-only
|
24
12
|
# and read-only.
|
25
|
-
#
|
26
|
-
# This class isn't really meant to be used outside of the
|
27
|
-
# ActiveRecord::Base#find method.
|
28
13
|
class PostgreSQLCursor
|
29
14
|
include Enumerable
|
30
15
|
|
31
|
-
def initialize(model, cursor_name,
|
16
|
+
def initialize(model, cursor_name, relation, join_dependency = nil)
|
32
17
|
@model = model
|
33
|
-
@
|
34
|
-
@model.connection.quote_table_name(cursor_name.gsub(/"/, '\"'))
|
35
|
-
end
|
36
|
-
@query = query
|
18
|
+
@relation = relation
|
37
19
|
@join_dependency = join_dependency
|
20
|
+
|
21
|
+
@cursor_name = (@model.connection.quote_table_name(cursor_name.gsub('"', '\"')) if cursor_name)
|
22
|
+
|
23
|
+
@query = model.connection.unprepared_statement do
|
24
|
+
relation.to_sql
|
25
|
+
end
|
38
26
|
end
|
39
27
|
|
40
28
|
def inspect
|
@@ -45,71 +33,67 @@ module ActiveRecord
|
|
45
33
|
# record as a parameter.
|
46
34
|
def each
|
47
35
|
@model.transaction do
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@join_dependency.clear_with_cursor
|
62
|
-
rows = [ row ]
|
63
|
-
end
|
36
|
+
declare_cursor
|
37
|
+
|
38
|
+
if @join_dependency
|
39
|
+
rows = []
|
40
|
+
last_id = nil
|
41
|
+
|
42
|
+
until (row = fetch_forward).empty?
|
43
|
+
instantiated_row = @join_dependency.instantiate(row, true).first
|
44
|
+
current_id = instantiated_row[@join_dependency.send(:join_root).primary_key]
|
45
|
+
last_id ||= current_id
|
46
|
+
|
47
|
+
if last_id == current_id
|
48
|
+
rows << row.first.values
|
64
49
|
last_id = current_id
|
65
|
-
|
50
|
+
else
|
51
|
+
result_set = ActiveRecord::Result.new(row.columns, rows, row.column_types)
|
66
52
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
else
|
71
|
-
while row = fetch_forward
|
72
|
-
yield row
|
53
|
+
yield @join_dependency.instantiate(result_set, true).first
|
54
|
+
|
55
|
+
rows = [row.first.values]
|
73
56
|
end
|
57
|
+
|
58
|
+
last_id = current_id
|
59
|
+
end
|
60
|
+
|
61
|
+
unless rows.empty?
|
62
|
+
result_set = ActiveRecord::Result.new(row.columns, rows, row.column_types)
|
63
|
+
|
64
|
+
yield @join_dependency.instantiate(result_set, true).first
|
65
|
+
end
|
66
|
+
else
|
67
|
+
until (row = fetch_forward).empty?
|
68
|
+
yield @model.instantiate(row.first)
|
74
69
|
end
|
75
|
-
ensure
|
76
|
-
close_cursor
|
77
70
|
end
|
71
|
+
ensure
|
72
|
+
close_cursor
|
78
73
|
end
|
79
74
|
nil
|
80
75
|
end
|
81
76
|
|
82
77
|
private
|
78
|
+
|
83
79
|
def cursor_name
|
84
|
-
@cursor_name ||= "cursor_#{(rand *
|
80
|
+
@cursor_name ||= "cursor_#{(rand * 1_000_000).ceil}"
|
85
81
|
end
|
86
82
|
|
87
|
-
def fetch_forward
|
88
|
-
@
|
83
|
+
def fetch_forward # :nodoc:
|
84
|
+
@relation.uncached do
|
85
|
+
@relation.connection.select_all(%{FETCH FORWARD FROM #{cursor_name}})
|
86
|
+
end
|
89
87
|
end
|
90
88
|
|
91
|
-
def declare_cursor
|
89
|
+
def declare_cursor # :nodoc:
|
92
90
|
@model.connection.execute(%{DECLARE #{cursor_name} CURSOR FOR #{@query}})
|
93
91
|
end
|
94
92
|
|
95
|
-
def close_cursor
|
93
|
+
def close_cursor # :nodoc:
|
96
94
|
@model.connection.execute(%{CLOSE #{cursor_name}})
|
97
95
|
end
|
98
96
|
end
|
99
97
|
end
|
100
98
|
|
101
|
-
|
102
|
-
class ActiveRecord::Associations::JoinDependency
|
103
|
-
include ActiveRecord::PostgreSQLCursors::JoinDependency
|
104
|
-
end
|
105
|
-
else
|
106
|
-
class ActiveRecord::Associations::ClassMethods::JoinDependency
|
107
|
-
include ActiveRecord::PostgreSQLCursors::JoinDependency
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
if ActiveRecord::VERSION::MAJOR >= 3
|
112
|
-
require File.join(File.dirname(__FILE__), *%w{ active_record postgresql_cursors cursors })
|
113
|
-
else
|
114
|
-
require File.join(File.dirname(__FILE__), *%w{ active_record postgresql_cursors cursors_2 })
|
115
|
-
end
|
99
|
+
require File.join(File.dirname(__FILE__), *%w{ active_record postgresql_cursors cursors })
|
@@ -0,0 +1,17 @@
|
|
1
|
+
sonar.projectKey=dark-panda_activerecord-postgresql-cursors
|
2
|
+
sonar.organization=dark-panda
|
3
|
+
|
4
|
+
# This is the name and version displayed in the SonarCloud UI.
|
5
|
+
sonar.projectName=activerecord-postgresql-cursors
|
6
|
+
sonar.projectVersion=1.0
|
7
|
+
|
8
|
+
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
|
9
|
+
sonar.sources=lib, test
|
10
|
+
sonar.coverage.exclusions=test
|
11
|
+
|
12
|
+
# Encoding of the source code. Default is default system encoding
|
13
|
+
sonar.sourceEncoding=UTF-8
|
14
|
+
|
15
|
+
# Additional reports
|
16
|
+
sonar.ruby.rubocop.reportPaths=rubocop-report.json
|
17
|
+
sonar.ruby.coverage.reportPaths=coverage/coverage.json
|
data/test/cursor_test.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class PostgreSQLCursorTests < Minitest::Test
|
6
|
+
include PostgreSQLCursorTestHelper
|
7
|
+
|
8
|
+
def test_cursor_scoped
|
9
|
+
cursor = Foo.cursor(order: 'id')
|
10
|
+
|
11
|
+
assert_kind_of(ActiveRecord::PostgreSQLCursor, cursor)
|
12
|
+
|
13
|
+
assert_equal(%w{ one two three four five }, cursor.collect(&:name))
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_cursor_while_updating
|
17
|
+
cursor = Foo.cursor(order: 'id')
|
18
|
+
|
19
|
+
cursor.each do |row|
|
20
|
+
row.name = "#{row.name}_updated"
|
21
|
+
|
22
|
+
assert(row.save)
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_equal(%w{ one_updated two_updated three_updated four_updated five_updated }, cursor.collect(&:name))
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_with_associations
|
29
|
+
cursor = Foo.cursor(order: 'id')
|
30
|
+
|
31
|
+
cursor.each do |row|
|
32
|
+
assert_kind_of(Foo, row)
|
33
|
+
row.bars.each do |bar|
|
34
|
+
assert_kind_of(Bar, bar)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_with_associations_eager_loading
|
40
|
+
cursor = Foo.cursor(order: 'foos.id', include: :bars)
|
41
|
+
|
42
|
+
cursor.each do |row|
|
43
|
+
assert_kind_of(Foo, row)
|
44
|
+
row.bars.each do |bar|
|
45
|
+
assert_kind_of(Bar, bar)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_nested_cursors
|
51
|
+
cursor = Foo.cursor(order: 'foos.id')
|
52
|
+
|
53
|
+
cursor.each do |row|
|
54
|
+
bars_cursor = row.bars.cursor
|
55
|
+
|
56
|
+
assert_kind_of(ActiveRecord::PostgreSQLCursor, bars_cursor)
|
57
|
+
|
58
|
+
bars_cursor.each do |bar|
|
59
|
+
assert_kind_of(Bar, bar)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_as_relation
|
65
|
+
cursor = Foo.order('foos.id').where('foos.id >= 3').cursor
|
66
|
+
|
67
|
+
assert_equal(3, cursor.to_a.length)
|
68
|
+
|
69
|
+
cursor.each do |row|
|
70
|
+
assert_kind_of(Foo, row)
|
71
|
+
assert_equal(2, row.bars.length)
|
72
|
+
row.bars.each do |bar|
|
73
|
+
assert_kind_of(Bar, bar)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_as_relation_with_associations
|
79
|
+
cursor = Foo.includes(:bars).order('foos.id').where('foos.id >= 3').cursor
|
80
|
+
|
81
|
+
assert_equal(3, cursor.to_a.length)
|
82
|
+
|
83
|
+
cursor.each do |row|
|
84
|
+
assert_kind_of(Foo, row)
|
85
|
+
assert_equal(2, row.bars.length)
|
86
|
+
row.bars.each do |bar|
|
87
|
+
assert_kind_of(Bar, bar)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|