sequel 3.43.0 → 3.44.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|