sequel 3.43.0 → 3.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +32 -0
- data/doc/association_basics.rdoc +10 -3
- data/doc/release_notes/3.37.0.txt +1 -1
- data/doc/release_notes/3.44.0.txt +152 -0
- data/lib/sequel/adapters/ado.rb +0 -6
- data/lib/sequel/adapters/db2.rb +0 -3
- data/lib/sequel/adapters/dbi.rb +0 -6
- data/lib/sequel/adapters/ibmdb.rb +0 -3
- data/lib/sequel/adapters/jdbc.rb +8 -12
- data/lib/sequel/adapters/jdbc/as400.rb +2 -18
- data/lib/sequel/adapters/jdbc/derby.rb +10 -0
- data/lib/sequel/adapters/jdbc/h2.rb +10 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +10 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +5 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -4
- data/lib/sequel/adapters/mock.rb +5 -0
- data/lib/sequel/adapters/mysql2.rb +4 -13
- data/lib/sequel/adapters/odbc.rb +0 -5
- data/lib/sequel/adapters/oracle.rb +3 -5
- data/lib/sequel/adapters/postgres.rb +2 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/cubrid.rb +9 -0
- data/lib/sequel/adapters/shared/db2.rb +10 -0
- data/lib/sequel/adapters/shared/mssql.rb +10 -0
- data/lib/sequel/adapters/shared/mysql.rb +14 -0
- data/lib/sequel/adapters/shared/oracle.rb +15 -0
- data/lib/sequel/adapters/shared/postgres.rb +26 -2
- data/lib/sequel/adapters/shared/sqlite.rb +15 -0
- data/lib/sequel/adapters/tinytds.rb +5 -32
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +2 -37
- data/lib/sequel/core.rb +3 -3
- data/lib/sequel/database/misc.rb +40 -4
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +33 -12
- data/lib/sequel/dataset/actions.rb +51 -2
- data/lib/sequel/dataset/features.rb +0 -6
- data/lib/sequel/dataset/sql.rb +1 -1
- data/lib/sequel/exceptions.rb +22 -7
- data/lib/sequel/extensions/columns_introspection.rb +30 -5
- data/lib/sequel/extensions/pg_auto_parameterize.rb +9 -0
- data/lib/sequel/model/associations.rb +50 -37
- data/lib/sequel/model/base.rb +30 -1
- data/lib/sequel/plugins/eager_each.rb +17 -21
- data/lib/sequel/plugins/identity_map.rb +2 -1
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
- data/lib/sequel/sql.rb +4 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +32 -2
- data/spec/adapters/sqlite_spec.rb +20 -0
- data/spec/core/database_spec.rb +40 -0
- data/spec/core/dataset_spec.rb +91 -4
- data/spec/core/mock_adapter_spec.rb +2 -1
- data/spec/core/schema_generator_spec.rb +4 -0
- data/spec/core/schema_spec.rb +9 -3
- data/spec/extensions/association_dependencies_spec.rb +3 -3
- data/spec/extensions/columns_introspection_spec.rb +28 -2
- data/spec/extensions/eager_each_spec.rb +0 -1
- data/spec/extensions/identity_map_spec.rb +3 -2
- data/spec/extensions/migration_spec.rb +6 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +2 -2
- data/spec/extensions/rcte_tree_spec.rb +2 -2
- data/spec/extensions/single_table_inheritance_spec.rb +3 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
- data/spec/extensions/validation_class_methods_spec.rb +8 -0
- data/spec/integration/associations_test.rb +4 -4
- data/spec/integration/database_test.rb +68 -20
- data/spec/integration/dataset_test.rb +48 -0
- data/spec/integration/schema_test.rb +25 -1
- data/spec/model/associations_spec.rb +21 -8
- data/spec/model/dataset_methods_spec.rb +58 -18
- metadata +4 -2
data/lib/sequel/model/base.rb
CHANGED
@@ -1934,6 +1934,35 @@ module Sequel
|
|
1934
1934
|
end
|
1935
1935
|
end
|
1936
1936
|
|
1937
|
+
# If there is no order already defined on this dataset, order it by
|
1938
|
+
# the primary key and call last.
|
1939
|
+
#
|
1940
|
+
# Album.last
|
1941
|
+
# # SELECT * FROM albums ORDER BY id DESC LIMIT 1
|
1942
|
+
def last(*a, &block)
|
1943
|
+
if opts[:order].nil? && model && (pk = model.primary_key)
|
1944
|
+
order(*pk).last(*a, &block)
|
1945
|
+
else
|
1946
|
+
super
|
1947
|
+
end
|
1948
|
+
end
|
1949
|
+
|
1950
|
+
# If there is no order already defined on this dataset, order it by
|
1951
|
+
# the primary key and call paged_each.
|
1952
|
+
#
|
1953
|
+
# Album.paged_each{|row| ...}
|
1954
|
+
# # SELECT * FROM albums ORDER BY id LIMIT 1000 OFFSET 0
|
1955
|
+
# # SELECT * FROM albums ORDER BY id LIMIT 1000 OFFSET 1000
|
1956
|
+
# # SELECT * FROM albums ORDER BY id LIMIT 1000 OFFSET 2000
|
1957
|
+
# # ...
|
1958
|
+
def paged_each(*a, &block)
|
1959
|
+
if opts[:order].nil? && model && (pk = model.primary_key)
|
1960
|
+
order(*pk).paged_each(*a, &block)
|
1961
|
+
else
|
1962
|
+
super
|
1963
|
+
end
|
1964
|
+
end
|
1965
|
+
|
1937
1966
|
# This allows you to call +to_hash+ without any arguments, which will
|
1938
1967
|
# result in a hash with the primary key value being the key and the
|
1939
1968
|
# model object being the value.
|
@@ -1946,7 +1975,7 @@ module Sequel
|
|
1946
1975
|
if key_column
|
1947
1976
|
super
|
1948
1977
|
else
|
1949
|
-
raise(Sequel::Error, "No primary key for model") unless model
|
1978
|
+
raise(Sequel::Error, "No primary key for model") unless model && (pk = model.primary_key)
|
1950
1979
|
super(pk, value_column)
|
1951
1980
|
end
|
1952
1981
|
end
|
@@ -12,7 +12,7 @@ module Sequel
|
|
12
12
|
# #each, this is a bit of issue, but this plugin resolves the issue by cloning the dataset
|
13
13
|
# and setting a new flag in the cloned dataset, so that each can check with the flag to
|
14
14
|
# determine whether it should call all.
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# Usage:
|
17
17
|
#
|
18
18
|
# # Make all model subclass instances eagerly load for each (called before loading subclasses)
|
@@ -21,37 +21,33 @@ module Sequel
|
|
21
21
|
# # Make the Album class eagerly load for each
|
22
22
|
# Album.plugin :eager_each
|
23
23
|
module EagerEach
|
24
|
-
|
25
|
-
|
26
|
-
#
|
24
|
+
module DatasetMethods
|
25
|
+
# Call #all instead of #each if eager loading,
|
26
|
+
# uless #each is being called by #all.
|
27
27
|
def each(&block)
|
28
|
-
if
|
29
|
-
super
|
30
|
-
else
|
28
|
+
if use_eager_all?
|
31
29
|
all(&block)
|
30
|
+
else
|
31
|
+
super
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
#
|
35
|
+
# If eager loading, clone the dataset and set a flag to let #each know not to call #all,
|
36
36
|
# to avoid the infinite loop.
|
37
37
|
def all(&block)
|
38
|
-
if
|
39
|
-
super
|
40
|
-
else
|
38
|
+
if use_eager_all?
|
41
39
|
clone(:all_called=>true).all(&block)
|
40
|
+
else
|
41
|
+
super
|
42
42
|
end
|
43
43
|
end
|
44
|
-
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# Make sure calling each on this dataset will eagerly load the dataset.
|
53
|
-
def eager_graph(*)
|
54
|
-
super.extend(EagerDatasetMethods)
|
45
|
+
private
|
46
|
+
|
47
|
+
# Wether to use all when each is called, true when eager loading
|
48
|
+
# unless the flag has already been set.
|
49
|
+
def use_eager_all?
|
50
|
+
(opts[:eager] || opts[:eager_graph]) && !opts[:all_called]
|
55
51
|
end
|
56
52
|
end
|
57
53
|
end
|
@@ -36,7 +36,7 @@ module Sequel
|
|
36
36
|
module ClassMethods
|
37
37
|
# Override the default :eager_loader option for many_*_many associations to work
|
38
38
|
# with an identity_map. If the :eager_graph association option is used, you'll probably have to use
|
39
|
-
# :uniq=>true on the current association
|
39
|
+
# :uniq=>true on the current association and :cartesian_product_number=>2 on the association
|
40
40
|
# mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
|
41
41
|
# getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
|
42
42
|
# they'll probably be lost. Making that work correctly would require changing a lot of the core
|
@@ -52,6 +52,7 @@ module Sequel
|
|
52
52
|
uses_lcks = opts[:uses_left_composite_keys]
|
53
53
|
uses_rcks = opts[:uses_right_composite_keys]
|
54
54
|
right = opts[:right_key]
|
55
|
+
rcks = opts[:right_keys]
|
55
56
|
join_table = opts[:join_table]
|
56
57
|
left = opts[:left_key]
|
57
58
|
lcks = opts[:left_keys]
|
@@ -192,7 +192,7 @@ module Sequel
|
|
192
192
|
lpkc = opts[:left_primary_key_column] ||= left_pk
|
193
193
|
lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
|
194
194
|
opts[:dataset] ||= lambda do
|
195
|
-
ds = opts.
|
195
|
+
ds = opts.associated_dataset
|
196
196
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
197
197
|
ft = opts.final_reverse_edge
|
198
198
|
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + opts.predicate_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias], :qualify=>:deep)
|
@@ -59,8 +59,8 @@ module Sequel
|
|
59
59
|
# # which are different but do not require different code.
|
60
60
|
# Employee.plugin :single_table_inheritance, :type,
|
61
61
|
# :model_map=>{'staff' => "Staff",
|
62
|
-
# 'manager => "Manager",
|
63
|
-
# 'overpayed staff => "Staff",
|
62
|
+
# 'manager' => "Manager",
|
63
|
+
# 'overpayed staff' => "Staff",
|
64
64
|
# 'underpayed staff' => "Staff"}
|
65
65
|
#
|
66
66
|
# One minor issue to note is that if you specify the <tt>:key_map</tt>
|
@@ -60,7 +60,7 @@ module Sequel
|
|
60
60
|
module DatasetMethods
|
61
61
|
private
|
62
62
|
|
63
|
-
# Set the
|
63
|
+
# Set the retrieved_with and retrieved_by attributes for the object
|
64
64
|
# with the current dataset and array of all objects.
|
65
65
|
def post_load(objects)
|
66
66
|
super
|
data/lib/sequel/sql.rb
CHANGED
@@ -1497,8 +1497,8 @@ module Sequel
|
|
1497
1497
|
#
|
1498
1498
|
# An instance of this class is yielded to the block supplied to <tt>Dataset#filter</tt>, <tt>Dataset#order</tt>, and <tt>Dataset#select</tt>
|
1499
1499
|
# (and the other methods that accept a block and pass it to one of those methods).
|
1500
|
-
# If the block doesn't take an argument, the block is
|
1501
|
-
#
|
1500
|
+
# If the block doesn't take an argument, the block is instance_execed in the context of
|
1501
|
+
# an instance of this class.
|
1502
1502
|
#
|
1503
1503
|
# +VirtualRow+ uses +method_missing+ to return either an +Identifier+, +QualifiedIdentifier+, +Function+, or +WindowFunction+,
|
1504
1504
|
# depending on how it is called.
|
@@ -1621,6 +1621,8 @@ module Sequel
|
|
1621
1621
|
Function.new(m, *args)
|
1622
1622
|
end
|
1623
1623
|
end
|
1624
|
+
|
1625
|
+
Sequel::VIRTUAL_ROW = new
|
1624
1626
|
end
|
1625
1627
|
|
1626
1628
|
# A +Window+ is part of a window function specifying the window over which the function operates.
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 3
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 44
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
8
8
|
# releases that fix regressions from previous versions.
|
9
9
|
TINY = 0
|
@@ -48,6 +48,28 @@ describe "PostgreSQL", '#create_table' do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
describe "PostgreSQL temporary views" do
|
52
|
+
before do
|
53
|
+
@db = POSTGRES_DB
|
54
|
+
@db.drop_view(:items_view) rescue nil
|
55
|
+
@db.create_table!(:items){Integer :number}
|
56
|
+
@db[:items].insert(10)
|
57
|
+
@db[:items].insert(20)
|
58
|
+
end
|
59
|
+
after do
|
60
|
+
@db.drop_table?(:items)
|
61
|
+
end
|
62
|
+
|
63
|
+
specify "should be supported" do
|
64
|
+
@db.create_view(:items_view, @db[:items].where(:number=>10), :temp=>true)
|
65
|
+
@db[:items_view].map(:number).should == [10]
|
66
|
+
@db.create_or_replace_view(:items_view, @db[:items].where(:number=>20), :temp=>true)
|
67
|
+
@db[:items_view].map(:number).should == [20]
|
68
|
+
@db.disconnect
|
69
|
+
lambda{@db[:items_view].map(:number)}.should raise_error(Sequel::DatabaseError)
|
70
|
+
end
|
71
|
+
end unless POSTGRES_DB.adapter_scheme == :do # Causes freezing later
|
72
|
+
|
51
73
|
describe "A PostgreSQL database" do
|
52
74
|
before(:all) do
|
53
75
|
@db = POSTGRES_DB
|
@@ -61,6 +83,14 @@ describe "A PostgreSQL database" do
|
|
61
83
|
@db.server_version.should > 70000
|
62
84
|
end
|
63
85
|
|
86
|
+
specify "should not typecast the int2vector type incorrectly" do
|
87
|
+
@db.get(Sequel.cast('10 20', :int2vector)).should_not == 10
|
88
|
+
end
|
89
|
+
|
90
|
+
cspecify "should not typecast the money type incorrectly", :do do
|
91
|
+
@db.get(Sequel.cast('10.01', :money)).should_not == 0
|
92
|
+
end
|
93
|
+
|
64
94
|
specify "should correctly parse the schema" do
|
65
95
|
@db.schema(:public__testfk, :reload=>true).should == [
|
66
96
|
[:id, {:type=>:integer, :ruby_default=>nil, :db_type=>"integer", :default=>"nextval('testfk_id_seq'::regclass)", :oid=>23, :primary_key=>true, :allow_null=>false}],
|
@@ -149,13 +179,13 @@ describe "A PostgreSQL dataset" do
|
|
149
179
|
@db.create_table!(:atest){Integer :t; exclude [[Sequel.desc(:t, :nulls=>:last), '=']], :using=>:btree, :where=>proc{t > 0}}
|
150
180
|
@db[:atest].insert(1)
|
151
181
|
@db[:atest].insert(2)
|
152
|
-
proc{@db[:atest].insert(2)}.should raise_error(Sequel::
|
182
|
+
proc{@db[:atest].insert(2)}.should raise_error(Sequel::Postgres::ExclusionConstraintViolation)
|
153
183
|
|
154
184
|
@db.create_table!(:atest){Integer :t}
|
155
185
|
@db.alter_table(:atest){add_exclusion_constraint [[:t, '=']], :using=>:btree, :name=>'atest_ex'}
|
156
186
|
@db[:atest].insert(1)
|
157
187
|
@db[:atest].insert(2)
|
158
|
-
proc{@db[:atest].insert(2)}.should raise_error(Sequel::
|
188
|
+
proc{@db[:atest].insert(2)}.should raise_error(Sequel::Postgres::ExclusionConstraintViolation)
|
159
189
|
@db.alter_table(:atest){drop_constraint 'atest_ex'}
|
160
190
|
end if POSTGRES_DB.server_version >= 90000
|
161
191
|
|
@@ -175,6 +175,26 @@ describe "An SQLite database" do
|
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
178
|
+
describe "SQLite temporary views" do
|
179
|
+
before do
|
180
|
+
@db = SQLITE_DB
|
181
|
+
@db.drop_view(:items) rescue nil
|
182
|
+
@db.create_table!(:items){Integer :number}
|
183
|
+
@db[:items].insert(10)
|
184
|
+
@db[:items].insert(20)
|
185
|
+
end
|
186
|
+
after do
|
187
|
+
@db.drop_table?(:items)
|
188
|
+
end
|
189
|
+
|
190
|
+
specify "should be supported" do
|
191
|
+
@db.create_view(:items_view, @db[:items].where(:number=>10), :temp=>true)
|
192
|
+
@db[:items_view].map(:number).should == [10]
|
193
|
+
@db.disconnect
|
194
|
+
lambda{@db[:items_view].map(:number)}.should raise_error(Sequel::DatabaseError)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
178
198
|
describe "SQLite type conversion" do
|
179
199
|
before do
|
180
200
|
@db = SQLITE_DB
|
data/spec/core/database_spec.rb
CHANGED
@@ -479,6 +479,26 @@ describe "Database#extend_datasets" do
|
|
479
479
|
end
|
480
480
|
end
|
481
481
|
|
482
|
+
describe "Database#disconnect_connection" do
|
483
|
+
specify "should call close on the connection" do
|
484
|
+
o = Object.new
|
485
|
+
def o.close() @closed=true end
|
486
|
+
Sequel::Database.new.disconnect_connection(o)
|
487
|
+
o.instance_variable_get(:@closed).should be_true
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
describe "Database#valid_connection?" do
|
492
|
+
specify "should issue a query to validate the connection" do
|
493
|
+
db = Sequel.mock
|
494
|
+
db.synchronize{|c| db.valid_connection?(c)}.should be_true
|
495
|
+
db.synchronize do |c|
|
496
|
+
def c.execute(*) raise Sequel::DatabaseError, "error" end
|
497
|
+
db.valid_connection?(c)
|
498
|
+
end.should be_false
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
482
502
|
describe "Database#execute" do
|
483
503
|
specify "should raise Sequel::NotImplemented" do
|
484
504
|
proc {Sequel::Database.new.execute('blah blah')}.should raise_error(Sequel::NotImplemented)
|
@@ -1649,6 +1669,14 @@ describe "Database#raise_error" do
|
|
1649
1669
|
specify "should convert the exception to a DatabaseDisconnectError if opts[:disconnect] is true" do
|
1650
1670
|
proc{@db.send(:raise_error, Interrupt.new(''), :disconnect=>true)}.should raise_error(Sequel::DatabaseDisconnectError)
|
1651
1671
|
end
|
1672
|
+
|
1673
|
+
specify "should convert the exception to an appropriate error if exception message matches regexp" do
|
1674
|
+
def @db.database_error_regexps
|
1675
|
+
{/foo/ => Sequel::DatabaseDisconnectError, /bar/ => Sequel::ConstraintViolation}
|
1676
|
+
end
|
1677
|
+
proc{@db.send(:raise_error, Interrupt.new('foo'))}.should raise_error(Sequel::DatabaseDisconnectError)
|
1678
|
+
proc{@db.send(:raise_error, Interrupt.new('bar'))}.should raise_error(Sequel::ConstraintViolation)
|
1679
|
+
end
|
1652
1680
|
end
|
1653
1681
|
|
1654
1682
|
describe "Database#typecast_value" do
|
@@ -2018,6 +2046,18 @@ describe "Database#schema_autoincrementing_primary_key?" do
|
|
2018
2046
|
end
|
2019
2047
|
end
|
2020
2048
|
|
2049
|
+
describe "Database#supports_deferrable_constraints?" do
|
2050
|
+
specify "should be false by default" do
|
2051
|
+
Sequel::Database.new.supports_deferrable_constraints?.should == false
|
2052
|
+
end
|
2053
|
+
end
|
2054
|
+
|
2055
|
+
describe "Database#supports_deferrable_foreign_key_constraints?" do
|
2056
|
+
specify "should be false by default" do
|
2057
|
+
Sequel::Database.new.supports_deferrable_foreign_key_constraints?.should == false
|
2058
|
+
end
|
2059
|
+
end
|
2060
|
+
|
2021
2061
|
describe "Database#supports_transactional_ddl?" do
|
2022
2062
|
specify "should be false by default" do
|
2023
2063
|
Sequel::Database.new.supports_transactional_ddl?.should == false
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -40,6 +40,14 @@ describe "Dataset" do
|
|
40
40
|
Sequel::Dataset.included_modules.should include(Enumerable)
|
41
41
|
end
|
42
42
|
|
43
|
+
specify "should yield rows to each" do
|
44
|
+
ds = Sequel.mock[:t]
|
45
|
+
ds._fetch = {:x=>1}
|
46
|
+
called = false
|
47
|
+
ds.each{|a| called = true; a.should == {:x=>1}}
|
48
|
+
called.should be_true
|
49
|
+
end
|
50
|
+
|
43
51
|
specify "should get quote_identifiers default from database" do
|
44
52
|
db = Sequel::Database.new(:quote_identifiers=>true)
|
45
53
|
db[:a].quote_identifiers?.should == true
|
@@ -4359,10 +4367,6 @@ describe "Dataset feature defaults" do
|
|
4359
4367
|
it "should not require placeholder type specifiers by default" do
|
4360
4368
|
Sequel::Database.new.dataset.requires_placeholder_type_specifiers?.should be_false
|
4361
4369
|
end
|
4362
|
-
|
4363
|
-
it "offset use should be returning a separate row number column by default" do
|
4364
|
-
Sequel::Database.new.dataset.send(:offset_returns_row_number_column?).should be_false
|
4365
|
-
end
|
4366
4370
|
end
|
4367
4371
|
|
4368
4372
|
describe "Dataset extensions" do
|
@@ -4465,6 +4469,11 @@ describe "Dataset#schema_and_table" do
|
|
4465
4469
|
@ds.schema_and_table('s').should == [nil, 's']
|
4466
4470
|
end
|
4467
4471
|
|
4472
|
+
it "should correctly handle literal strings" do
|
4473
|
+
s = Sequel.lit('s')
|
4474
|
+
@ds.schema_and_table(s).last.should equal(s)
|
4475
|
+
end
|
4476
|
+
|
4468
4477
|
it "should correctly handle identifiers" do
|
4469
4478
|
@ds.schema_and_table(Sequel.identifier(:s)).should == [nil, 's']
|
4470
4479
|
end
|
@@ -4521,3 +4530,81 @@ describe "Dataset#split_qualifiers" do
|
|
4521
4530
|
end
|
4522
4531
|
end
|
4523
4532
|
|
4533
|
+
describe "Dataset#paged_each" do
|
4534
|
+
before do
|
4535
|
+
@ds = Sequel.mock[:test].order(:x)
|
4536
|
+
@db = (0...10).map{|i| {:x=>i}}
|
4537
|
+
@ds._fetch = @db
|
4538
|
+
@rows = []
|
4539
|
+
@proc = lambda{|row| @rows << row}
|
4540
|
+
end
|
4541
|
+
|
4542
|
+
it "should yield rows to the passed block" do
|
4543
|
+
@ds.paged_each(&@proc)
|
4544
|
+
@rows.should == @db
|
4545
|
+
end
|
4546
|
+
|
4547
|
+
it "should respect the row_proc" do
|
4548
|
+
@ds.row_proc = lambda{|row| {:x=>row[:x]*2}}
|
4549
|
+
@ds.paged_each(&@proc)
|
4550
|
+
@rows.should == @db.map{|row| {:x=>row[:x]*2}}
|
4551
|
+
end
|
4552
|
+
|
4553
|
+
it "should use a transaction to ensure consistent results" do
|
4554
|
+
@ds.paged_each(&@proc)
|
4555
|
+
sqls = @ds.db.sqls
|
4556
|
+
sqls[0].should == 'BEGIN'
|
4557
|
+
sqls[-1].should == 'COMMIT'
|
4558
|
+
end
|
4559
|
+
|
4560
|
+
it "should use a limit and offset to go through the dataset in chunks at a time" do
|
4561
|
+
@ds.paged_each(&@proc)
|
4562
|
+
@ds.db.sqls[1...-1].should == ['SELECT * FROM test ORDER BY x LIMIT 1000 OFFSET 0']
|
4563
|
+
end
|
4564
|
+
|
4565
|
+
it "should accept a :rows_per_fetch option to change the number of rows per fetch" do
|
4566
|
+
@ds._fetch = @db.each_slice(3).to_a
|
4567
|
+
@ds.paged_each(:rows_per_fetch=>3, &@proc)
|
4568
|
+
@rows.should == @db
|
4569
|
+
@ds.db.sqls[1...-1].should == ['SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 0',
|
4570
|
+
'SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 3',
|
4571
|
+
'SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 6',
|
4572
|
+
'SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 9']
|
4573
|
+
end
|
4574
|
+
|
4575
|
+
it "should handle cases where the last query returns nothing" do
|
4576
|
+
@ds._fetch = @db.each_slice(5).to_a
|
4577
|
+
@ds.paged_each(:rows_per_fetch=>5, &@proc)
|
4578
|
+
@rows.should == @db
|
4579
|
+
@ds.db.sqls[1...-1].should == ['SELECT * FROM test ORDER BY x LIMIT 5 OFFSET 0',
|
4580
|
+
'SELECT * FROM test ORDER BY x LIMIT 5 OFFSET 5',
|
4581
|
+
'SELECT * FROM test ORDER BY x LIMIT 5 OFFSET 10']
|
4582
|
+
end
|
4583
|
+
|
4584
|
+
it "should respect an existing server option to use" do
|
4585
|
+
@ds = Sequel.mock(:servers=>{:foo=>{}})[:test].order(:x)
|
4586
|
+
@ds._fetch = @db
|
4587
|
+
@ds.server(:foo).paged_each(&@proc)
|
4588
|
+
@rows.should == @db
|
4589
|
+
@ds.db.sqls.should == ["BEGIN -- foo", "SELECT * FROM test ORDER BY x LIMIT 1000 OFFSET 0 -- foo", "COMMIT -- foo"]
|
4590
|
+
end
|
4591
|
+
|
4592
|
+
it "should require an order" do
|
4593
|
+
lambda{@ds.unordered.paged_each(&@proc)}.should raise_error(Sequel::Error)
|
4594
|
+
end
|
4595
|
+
|
4596
|
+
it "should handle an existing limit and/or offset" do
|
4597
|
+
@ds._fetch = @db.each_slice(3).to_a
|
4598
|
+
@ds.limit(5).paged_each(:rows_per_fetch=>3, &@proc)
|
4599
|
+
@ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 0", "SELECT * FROM test ORDER BY x LIMIT 2 OFFSET 3"]
|
4600
|
+
|
4601
|
+
@ds._fetch = @db.each_slice(3).to_a
|
4602
|
+
@ds.limit(5, 2).paged_each(:rows_per_fetch=>3, &@proc)
|
4603
|
+
@ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 2", "SELECT * FROM test ORDER BY x LIMIT 2 OFFSET 5"]
|
4604
|
+
|
4605
|
+
@ds._fetch = @db.each_slice(3).to_a
|
4606
|
+
@ds.limit(nil, 2).paged_each(:rows_per_fetch=>3, &@proc)
|
4607
|
+
@ds.db.sqls[1...-1].should == ["SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 2", "SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 5", "SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 8", "SELECT * FROM test ORDER BY x LIMIT 3 OFFSET 11"]
|
4608
|
+
end
|
4609
|
+
end
|
4610
|
+
|