sequel 5.9.0 → 5.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/doc/release_notes/5.10.0.txt +84 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +20 -4
- data/lib/sequel/adapters/shared/postgres.rb +12 -2
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset/actions.rb +7 -6
- data/lib/sequel/dataset/misc.rb +14 -0
- data/lib/sequel/extensions/pg_array.rb +83 -79
- data/lib/sequel/extensions/pg_extended_date_support.rb +11 -4
- data/lib/sequel/extensions/pg_range.rb +4 -2
- data/lib/sequel/model/associations.rb +10 -2
- data/lib/sequel/plugins/list.rb +18 -8
- data/lib/sequel/plugins/pg_array_associations.rb +2 -2
- data/lib/sequel/plugins/tree.rb +28 -13
- data/lib/sequel/sql.rb +24 -4
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +94 -26
- data/spec/bin_spec.rb +2 -0
- data/spec/core/dataset_spec.rb +58 -32
- data/spec/core/expression_filters_spec.rb +16 -0
- data/spec/core/spec_helper.rb +1 -0
- data/spec/core_extensions_spec.rb +1 -0
- data/spec/extensions/list_spec.rb +16 -0
- data/spec/extensions/pg_array_associations_spec.rb +10 -10
- data/spec/extensions/pg_range_spec.rb +34 -2
- data/spec/extensions/spec_helper.rb +1 -0
- data/spec/extensions/tree_spec.rb +40 -0
- data/spec/guards_helper.rb +1 -0
- data/spec/model/associations_spec.rb +21 -0
- data/spec/model/spec_helper.rb +1 -0
- metadata +4 -2
data/spec/bin_spec.rb
CHANGED
@@ -23,6 +23,8 @@ File.delete(BIN_SPEC_DB2) if File.file?(BIN_SPEC_DB2)
|
|
23
23
|
DB = Sequel.connect("#{CONN_PREFIX}#{BIN_SPEC_DB}", :test=>false)
|
24
24
|
DB2 = Sequel.connect("#{CONN_PREFIX}#{BIN_SPEC_DB2}", :test=>false)
|
25
25
|
|
26
|
+
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
27
|
+
gem 'minitest'
|
26
28
|
require 'minitest/autorun'
|
27
29
|
|
28
30
|
describe "bin/sequel" do
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -1729,7 +1729,7 @@ describe "Dataset#with_extend" do
|
|
1729
1729
|
m2 = Module.new{def a; 3 end}
|
1730
1730
|
d.with_extend(m1, m2){def a; 4**super end}.a.must_equal 65536
|
1731
1731
|
d.respond_to?(:a).must_equal false
|
1732
|
-
ds = d.
|
1732
|
+
ds = d.with_extend(m1, m2){def a; 4**super end}
|
1733
1733
|
ds.a.must_equal 65536
|
1734
1734
|
ds.frozen?.must_equal true
|
1735
1735
|
end
|
@@ -1865,7 +1865,7 @@ describe "Dataset#with_row_proc" do
|
|
1865
1865
|
l = lambda{|r| r}
|
1866
1866
|
d.with_row_proc(l).row_proc.must_equal l
|
1867
1867
|
d.row_proc.must_be_nil
|
1868
|
-
ds = d.
|
1868
|
+
ds = d.with_row_proc(l)
|
1869
1869
|
ds.frozen?.must_equal true
|
1870
1870
|
ds.row_proc.must_equal l
|
1871
1871
|
end
|
@@ -2094,7 +2094,7 @@ end
|
|
2094
2094
|
describe "Dataset#count" do
|
2095
2095
|
before do
|
2096
2096
|
@db = Sequel.mock(:fetch=>{:count=>1})
|
2097
|
-
@dataset = @db.from(:test).columns(:count)
|
2097
|
+
@dataset = @db.from(:test).columns(:count)
|
2098
2098
|
end
|
2099
2099
|
|
2100
2100
|
it "should format SQL properly" do
|
@@ -2656,7 +2656,7 @@ end
|
|
2656
2656
|
|
2657
2657
|
describe "Dataset aggregate methods" do
|
2658
2658
|
before do
|
2659
|
-
@d = Sequel.mock(:fetch=>proc{|s| {1=>s}})[:test]
|
2659
|
+
@d = Sequel.mock(:fetch=>proc{|s| {1=>s}})[:test]
|
2660
2660
|
end
|
2661
2661
|
|
2662
2662
|
it "should include min" do
|
@@ -2715,15 +2715,25 @@ describe "Dataset #first and #last" do
|
|
2715
2715
|
end
|
2716
2716
|
|
2717
2717
|
it "should return a single record if no argument is given" do
|
2718
|
-
ds = @d.order(:a)
|
2718
|
+
ds = @d.order(:a)
|
2719
2719
|
3.times do
|
2720
2720
|
ds.first.must_equal(:s=>'SELECT * FROM test ORDER BY a LIMIT 1')
|
2721
2721
|
ds.last.must_equal(:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1')
|
2722
2722
|
end
|
2723
2723
|
end
|
2724
2724
|
|
2725
|
+
it "should handle empty arrays and hashes" do
|
2726
|
+
ds = @d.order(:a)
|
2727
|
+
3.times do
|
2728
|
+
ds.first({}).must_equal(:s=>'SELECT * FROM test ORDER BY a LIMIT 1')
|
2729
|
+
ds.last({}).must_equal(:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1')
|
2730
|
+
ds.first([]).must_equal(:s=>'SELECT * FROM test ORDER BY a LIMIT 1')
|
2731
|
+
ds.last([]).must_equal(:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1')
|
2732
|
+
end
|
2733
|
+
end
|
2734
|
+
|
2725
2735
|
it "should return the first/last matching record if argument is not an Integer" do
|
2726
|
-
ds = @d.order(:a)
|
2736
|
+
ds = @d.order(:a)
|
2727
2737
|
5.times do
|
2728
2738
|
ds.first(:z => 26).must_equal(:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1')
|
2729
2739
|
ds.first([[:z, 15]]).must_equal(:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1')
|
@@ -2733,7 +2743,7 @@ describe "Dataset #first and #last" do
|
|
2733
2743
|
end
|
2734
2744
|
|
2735
2745
|
it "should set the limit and return an array of records if the given number is > 1" do
|
2736
|
-
ds = @d.order(:a)
|
2746
|
+
ds = @d.order(:a)
|
2737
2747
|
5.times do
|
2738
2748
|
i = rand(10) + 10
|
2739
2749
|
ds.first(i).must_equal [{:s=>"SELECT * FROM test ORDER BY a LIMIT #{i}"}]
|
@@ -2742,7 +2752,7 @@ describe "Dataset #first and #last" do
|
|
2742
2752
|
end
|
2743
2753
|
|
2744
2754
|
it "should return the first matching record if a block is given without an argument" do
|
2745
|
-
ds = @d.order(:name)
|
2755
|
+
ds = @d.order(:name)
|
2746
2756
|
5.times do
|
2747
2757
|
@d.first{z > 26}.must_equal(:s=>'SELECT * FROM test WHERE (z > 26) LIMIT 1')
|
2748
2758
|
ds.last{z > 26}.must_equal(:s=>'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1')
|
@@ -2750,7 +2760,7 @@ describe "Dataset #first and #last" do
|
|
2750
2760
|
end
|
2751
2761
|
|
2752
2762
|
it "should combine block and standard argument filters if argument is not an Integer" do
|
2753
|
-
ds = @d.order(:name)
|
2763
|
+
ds = @d.order(:name)
|
2754
2764
|
5.times do
|
2755
2765
|
@d.first(:y=>25){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 25) AND (z > 26)) LIMIT 1')
|
2756
2766
|
ds.last(:y=>16){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 16) AND (z > 26)) ORDER BY name DESC LIMIT 1')
|
@@ -2758,7 +2768,7 @@ describe "Dataset #first and #last" do
|
|
2758
2768
|
end
|
2759
2769
|
|
2760
2770
|
it "should combine block and standard argument filters if argument is a literal string" do
|
2761
|
-
ds = @d.order(:name)
|
2771
|
+
ds = @d.order(:name)
|
2762
2772
|
5.times do
|
2763
2773
|
@d.first(Sequel.lit('y = 25')){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 25) AND (z > 26)) LIMIT 1')
|
2764
2774
|
ds.last(Sequel.lit('y = 16')){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 16) AND (z > 26)) ORDER BY name DESC LIMIT 1')
|
@@ -2768,7 +2778,7 @@ describe "Dataset #first and #last" do
|
|
2768
2778
|
end
|
2769
2779
|
|
2770
2780
|
it "should filter and return an array of records if an Integer argument is provided and a block is given" do
|
2771
|
-
ds = @d.order(:a)
|
2781
|
+
ds = @d.order(:a)
|
2772
2782
|
5.times do
|
2773
2783
|
i = rand(10) + 10
|
2774
2784
|
ds.first(i){z > 26}.must_equal [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"}]
|
@@ -3038,7 +3048,7 @@ describe "Dataset#get" do
|
|
3038
3048
|
end
|
3039
3049
|
|
3040
3050
|
it "should select the specified column and fetch its value" do
|
3041
|
-
@d
|
3051
|
+
@d
|
3042
3052
|
5.times do
|
3043
3053
|
@d.get(:name).must_equal "SELECT name FROM test LIMIT 1"
|
3044
3054
|
@d.get(:abc).must_equal "SELECT abc FROM test LIMIT 1"
|
@@ -3050,14 +3060,14 @@ describe "Dataset#get" do
|
|
3050
3060
|
end
|
3051
3061
|
|
3052
3062
|
it "should work with aliased fields" do
|
3053
|
-
@d
|
3063
|
+
@d
|
3054
3064
|
5.times do
|
3055
3065
|
@d.get(Sequel.expr(Sequel[:x][:b]).as(:name)).must_equal "SELECT x.b AS name FROM test LIMIT 1"
|
3056
3066
|
end
|
3057
3067
|
end
|
3058
3068
|
|
3059
3069
|
it "should work with plain strings" do
|
3060
|
-
@d
|
3070
|
+
@d
|
3061
3071
|
5.times do
|
3062
3072
|
@d.get('a').must_equal "SELECT 'a' AS v FROM test LIMIT 1"
|
3063
3073
|
end
|
@@ -5225,33 +5235,20 @@ end
|
|
5225
5235
|
|
5226
5236
|
describe "Frozen Datasets" do
|
5227
5237
|
before do
|
5228
|
-
@ds = Sequel.mock[:test]
|
5238
|
+
@ds = Sequel.mock[:test]
|
5229
5239
|
end
|
5230
5240
|
|
5231
|
-
it "should be
|
5241
|
+
it "datasets should be frozen by default" do
|
5232
5242
|
@ds.must_be :frozen?
|
5233
5243
|
end
|
5234
5244
|
|
5235
5245
|
it "should have Dataset#freeze return receiver" do
|
5236
|
-
@ds = Sequel.mock[:test]
|
5237
|
-
@ds.freeze.must_be_same_as(@ds)
|
5238
|
-
end
|
5239
|
-
|
5240
|
-
it "should have Dataset#freeze be a no-op" do
|
5241
5246
|
@ds.freeze.must_be_same_as(@ds)
|
5242
5247
|
end
|
5243
5248
|
|
5244
5249
|
it "should have clones be frozen" do
|
5245
5250
|
@ds.clone.must_be :frozen?
|
5246
5251
|
end
|
5247
|
-
|
5248
|
-
it "should be equal to unfrozen ones" do
|
5249
|
-
@ds.must_equal @ds.db[:test]
|
5250
|
-
end
|
5251
|
-
|
5252
|
-
it "should not raise an error when calling query methods" do
|
5253
|
-
@ds.select(:a).sql.must_equal 'SELECT a FROM test'
|
5254
|
-
end
|
5255
5252
|
end
|
5256
5253
|
|
5257
5254
|
describe "Dataset emulated complex expression operators" do
|
@@ -5379,7 +5376,7 @@ end
|
|
5379
5376
|
|
5380
5377
|
describe "Dataset#where_all" do
|
5381
5378
|
before do
|
5382
|
-
@ds = Sequel.mock(:fetch=>{:id=>1})[:items]
|
5379
|
+
@ds = Sequel.mock(:fetch=>{:id=>1})[:items]
|
5383
5380
|
end
|
5384
5381
|
|
5385
5382
|
it "should filter dataset with condition, and return related rows" do
|
@@ -5389,6 +5386,15 @@ describe "Dataset#where_all" do
|
|
5389
5386
|
end
|
5390
5387
|
end
|
5391
5388
|
|
5389
|
+
it "should handle empty arrays and hashes" do
|
5390
|
+
5.times do
|
5391
|
+
@ds.where_all([]).must_equal [{:id=>1}]
|
5392
|
+
@ds.db.sqls.must_equal ['SELECT * FROM items']
|
5393
|
+
@ds.where_all({}).must_equal [{:id=>1}]
|
5394
|
+
@ds.db.sqls.must_equal ['SELECT * FROM items']
|
5395
|
+
end
|
5396
|
+
end
|
5397
|
+
|
5392
5398
|
it "should yield each row to the given block" do
|
5393
5399
|
5.times do
|
5394
5400
|
a = []
|
@@ -5401,7 +5407,18 @@ end
|
|
5401
5407
|
|
5402
5408
|
describe "Dataset#where_each" do
|
5403
5409
|
before do
|
5404
|
-
@ds = Sequel.mock(:fetch=>{:id=>1})[:items]
|
5410
|
+
@ds = Sequel.mock(:fetch=>{:id=>1})[:items]
|
5411
|
+
end
|
5412
|
+
|
5413
|
+
it "should handle empty arrays and hashes" do
|
5414
|
+
[[], {}].each do |arg|
|
5415
|
+
5.times do
|
5416
|
+
a = []
|
5417
|
+
@ds.where_each(arg){|r| a << r}
|
5418
|
+
a.must_equal [{:id=>1}]
|
5419
|
+
@ds.db.sqls.must_equal ['SELECT * FROM items']
|
5420
|
+
end
|
5421
|
+
end
|
5405
5422
|
end
|
5406
5423
|
|
5407
5424
|
it "should yield each row to the given block" do
|
@@ -5418,7 +5435,16 @@ describe "Dataset#where_single_value" do
|
|
5418
5435
|
before do
|
5419
5436
|
@ds = Sequel.mock(:fetch=>{:id=>1})[:items].with_extend do
|
5420
5437
|
select :only_id, :id
|
5421
|
-
end
|
5438
|
+
end
|
5439
|
+
end
|
5440
|
+
|
5441
|
+
it "should handle empty arrays and hashes" do
|
5442
|
+
[[], {}].each do |arg|
|
5443
|
+
5.times do
|
5444
|
+
@ds.only_id.where_single_value(arg).must_equal 1
|
5445
|
+
@ds.db.sqls.must_equal ['SELECT id FROM items LIMIT 1']
|
5446
|
+
end
|
5447
|
+
end
|
5422
5448
|
end
|
5423
5449
|
|
5424
5450
|
it "should return single value" do
|
@@ -503,6 +503,12 @@ describe "Blockless Ruby Filters" do
|
|
503
503
|
end
|
504
504
|
dsc.new(@d.db).literal(Sequel.trim(:a)).must_equal 'trimFOO(lower(a))'
|
505
505
|
end
|
506
|
+
|
507
|
+
it "should endless ranges" do
|
508
|
+
endless = eval('1..')
|
509
|
+
@d.l{x =~ endless}.must_equal '(x >= 1)'
|
510
|
+
@d.l(:x => endless).must_equal '(x >= 1)'
|
511
|
+
end if RUBY_VERSION >= '2.6'
|
506
512
|
end
|
507
513
|
|
508
514
|
describe Sequel::SQL::VirtualRow do
|
@@ -1140,6 +1146,16 @@ describe "Sequel::SQLTime" do
|
|
1140
1146
|
Sequel::SQLTime.create(1, 2, 3).strftime('%Y-%m-%d').must_equal Date.new(2000).strftime('%Y-%m-%d')
|
1141
1147
|
end
|
1142
1148
|
|
1149
|
+
it ".parse should respect SQLTime.date setting" do
|
1150
|
+
Sequel::SQLTime.date = Date.new(2000, 2, 3)
|
1151
|
+
Sequel::SQLTime.parse('10:11:12').strftime('%F').must_equal "2000-02-03"
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
it ".parse should respect application_timezone setting" do
|
1155
|
+
Sequel::application_timezone = :utc
|
1156
|
+
Sequel::SQLTime.parse('10:11:12').utc_offset.must_equal 0
|
1157
|
+
end
|
1158
|
+
|
1143
1159
|
it "#inspect should show class and time by default" do
|
1144
1160
|
Sequel::SQLTime.create(1, 2, 3).inspect.must_equal "#<Sequel::SQLTime 01:02:03>"
|
1145
1161
|
Sequel::SQLTime.create(13, 24, 35).inspect.must_equal "#<Sequel::SQLTime 13:24:35>"
|
data/spec/core/spec_helper.rb
CHANGED
@@ -8,6 +8,7 @@ end
|
|
8
8
|
$:.unshift(File.join(File.dirname(File.expand_path(__FILE__)), "../../lib/"))
|
9
9
|
require_relative "../../lib/sequel/core"
|
10
10
|
|
11
|
+
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
11
12
|
gem 'minitest'
|
12
13
|
require 'minitest/autorun'
|
13
14
|
require 'minitest/hooks/default'
|
@@ -14,6 +14,7 @@ Sequel.extension :core_extensions
|
|
14
14
|
Sequel.extension :symbol_aref
|
15
15
|
Sequel.extension :virtual_row_method_block
|
16
16
|
|
17
|
+
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
17
18
|
gem 'minitest'
|
18
19
|
require 'minitest/autorun'
|
19
20
|
require 'minitest/hooks/default'
|
@@ -17,6 +17,8 @@ describe "List plugin" do
|
|
17
17
|
@o = @c.load(:id=>7, :position=>3)
|
18
18
|
@sc = klass(:scope=>:scope_id)
|
19
19
|
@so = @sc.load(:id=>7, :position=>3, :scope_id=>5)
|
20
|
+
@tc = klass(:top=>0)
|
21
|
+
@to = @tc.load(:id=>7, :position=>3)
|
20
22
|
@db.reset
|
21
23
|
end
|
22
24
|
|
@@ -43,6 +45,14 @@ describe "List plugin" do
|
|
43
45
|
klass(:scope=>proc{|o| o.model.dataset.filter(:active).filter(:scope_id=>o.scope_id)}).new(:scope_id=>4).list_dataset.sql.must_equal 'SELECT * FROM items WHERE (active AND (scope_id = 4)) ORDER BY position'
|
44
46
|
end
|
45
47
|
|
48
|
+
it "should default top of the list to 1" do
|
49
|
+
@c.top_of_list.must_equal 1
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should accept a :top option to set top of the list" do
|
53
|
+
@tc.top_of_list.must_equal 0
|
54
|
+
end
|
55
|
+
|
46
56
|
it "should modify the order when using the plugin" do
|
47
57
|
c = Class.new(Sequel::Model(:items))
|
48
58
|
c.dataset.sql.must_equal 'SELECT * FROM items'
|
@@ -199,6 +209,12 @@ describe "List plugin" do
|
|
199
209
|
"UPDATE items SET position = 1 WHERE (id = 7)"]
|
200
210
|
end
|
201
211
|
|
212
|
+
it "should have move_to_top use position 0 when :top_of_list is 0" do
|
213
|
+
@to.move_to_top
|
214
|
+
@db.sqls.must_equal ["UPDATE items SET position = (position + 1) WHERE ((position >= 0) AND (position < 3))",
|
215
|
+
"UPDATE items SET position = 0 WHERE (id = 7)"]
|
216
|
+
end
|
217
|
+
|
202
218
|
it "should have move_up without an argument move up a single position" do
|
203
219
|
@o.move_up.must_equal @o
|
204
220
|
@o.position.must_equal 2
|
@@ -72,7 +72,7 @@ describe Sequel::Model, "pg_array_associations" do
|
|
72
72
|
|
73
73
|
it "should allowing filtering by associations with :conditions" do
|
74
74
|
@c1.filter(:a_tags=>@o2).sql.must_equal "SELECT * FROM artists WHERE coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE ((name = 'A') AND (tags.id IS NOT NULL) AND (tags.id = 2)))), false)"
|
75
|
-
@c2.filter(:a_artists=>@o1).sql.must_equal "SELECT * FROM tags WHERE (tags.id IN (SELECT unnest(artists.tag_ids)
|
75
|
+
@c2.filter(:a_artists=>@o1).sql.must_equal "SELECT * FROM tags WHERE (tags.id IN (SELECT _smtopgaa_key_ FROM artists CROSS JOIN unnest(artists.tag_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id = 1))))"
|
76
76
|
end
|
77
77
|
|
78
78
|
it "should allowing excluding by associations" do
|
@@ -82,7 +82,7 @@ describe Sequel::Model, "pg_array_associations" do
|
|
82
82
|
|
83
83
|
it "should allowing excluding by associations with :conditions" do
|
84
84
|
@c1.exclude(:a_tags=>@o2).sql.must_equal "SELECT * FROM artists WHERE (NOT coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE ((name = 'A') AND (tags.id IS NOT NULL) AND (tags.id = 2)))), false) OR (artists.tag_ids IS NULL))"
|
85
|
-
@c2.exclude(:a_artists=>@o1).sql.must_equal "SELECT * FROM tags WHERE ((tags.id NOT IN (SELECT unnest(artists.tag_ids)
|
85
|
+
@c2.exclude(:a_artists=>@o1).sql.must_equal "SELECT * FROM tags WHERE ((tags.id NOT IN (SELECT _smtopgaa_key_ FROM artists CROSS JOIN unnest(artists.tag_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id = 1)))) OR (tags.id IS NULL))"
|
86
86
|
end
|
87
87
|
|
88
88
|
it "should allowing filtering by multiple associations" do
|
@@ -92,7 +92,7 @@ describe Sequel::Model, "pg_array_associations" do
|
|
92
92
|
|
93
93
|
it "should allowing filtering by multiple associations with :conditions" do
|
94
94
|
@c1.filter(:a_tags=>[@c2.load(:id=>1), @c2.load(:id=>2)]).sql.must_equal "SELECT * FROM artists WHERE coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE ((name = 'A') AND (tags.id IS NOT NULL) AND (tags.id IN (1, 2))))), false)"
|
95
|
-
@c2.filter(:a_artists=>[@c1.load(:id=>7, :tag_ids=>Sequel.pg_array([3, 4])), @c1.load(:id=>8, :tag_ids=>Sequel.pg_array([4, 5]))]).sql.must_equal "SELECT * FROM tags WHERE (tags.id IN (SELECT unnest(artists.tag_ids)
|
95
|
+
@c2.filter(:a_artists=>[@c1.load(:id=>7, :tag_ids=>Sequel.pg_array([3, 4])), @c1.load(:id=>8, :tag_ids=>Sequel.pg_array([4, 5]))]).sql.must_equal "SELECT * FROM tags WHERE (tags.id IN (SELECT _smtopgaa_key_ FROM artists CROSS JOIN unnest(artists.tag_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (7, 8)))))"
|
96
96
|
end
|
97
97
|
|
98
98
|
it "should allowing excluding by multiple associations" do
|
@@ -102,7 +102,7 @@ describe Sequel::Model, "pg_array_associations" do
|
|
102
102
|
|
103
103
|
it "should allowing excluding by multiple associations with :conditions" do
|
104
104
|
@c1.exclude(:a_tags=>[@c2.load(:id=>1), @c2.load(:id=>2)]).sql.must_equal "SELECT * FROM artists WHERE (NOT coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE ((name = 'A') AND (tags.id IS NOT NULL) AND (tags.id IN (1, 2))))), false) OR (artists.tag_ids IS NULL))"
|
105
|
-
@c2.exclude(:a_artists=>[@c1.load(:id=>7, :tag_ids=>Sequel.pg_array([3, 4])), @c1.load(:id=>8, :tag_ids=>Sequel.pg_array([4, 5]))]).sql.must_equal "SELECT * FROM tags WHERE ((tags.id NOT IN (SELECT unnest(artists.tag_ids)
|
105
|
+
@c2.exclude(:a_artists=>[@c1.load(:id=>7, :tag_ids=>Sequel.pg_array([3, 4])), @c1.load(:id=>8, :tag_ids=>Sequel.pg_array([4, 5]))]).sql.must_equal "SELECT * FROM tags WHERE ((tags.id NOT IN (SELECT _smtopgaa_key_ FROM artists CROSS JOIN unnest(artists.tag_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (7, 8))))) OR (tags.id IS NULL))"
|
106
106
|
end
|
107
107
|
|
108
108
|
it "should allowing filtering/excluding associations with NULL or empty values" do
|
@@ -120,22 +120,22 @@ describe Sequel::Model, "pg_array_associations" do
|
|
120
120
|
|
121
121
|
it "should allowing filtering by association datasets" do
|
122
122
|
@c1.filter(:tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE (id = 1))), false)"
|
123
|
-
@c2.filter(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (
|
123
|
+
@c2.filter(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (EXISTS (SELECT 1 FROM (SELECT artists.tag_ids AS key FROM artists WHERE (id = 1)) AS t1 WHERE (tags.id = any(key))))"
|
124
124
|
end
|
125
125
|
|
126
126
|
it "should allowing filtering by association datasets with :conditions" do
|
127
127
|
@c1.filter(:a_tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE ((name = 'A') AND (tags.id IS NOT NULL) AND (tags.id IN (SELECT tags.id FROM tags WHERE (id = 1)))))), false)"
|
128
|
-
@c2.filter(:a_artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (tags.id IN (SELECT unnest(artists.tag_ids)
|
128
|
+
@c2.filter(:a_artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (tags.id IN (SELECT _smtopgaa_key_ FROM artists CROSS JOIN unnest(artists.tag_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (SELECT artists.id FROM artists WHERE (id = 1))))))"
|
129
129
|
end
|
130
130
|
|
131
131
|
it "should allowing excluding by association datasets" do
|
132
132
|
@c1.exclude(:tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE (NOT coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE (id = 1))), false) OR (artists.tag_ids IS NULL))"
|
133
|
-
@c2.exclude(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE ((
|
133
|
+
@c2.exclude(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (NOT (EXISTS (SELECT 1 FROM (SELECT artists.tag_ids AS key FROM artists WHERE (id = 1)) AS t1 WHERE (tags.id = any(key)))) OR (tags.id IS NULL))"
|
134
134
|
end
|
135
135
|
|
136
136
|
it "should allowing excluding by association datasets with :conditions" do
|
137
137
|
@c1.exclude(:a_tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE (NOT coalesce((artists.tag_ids && (SELECT array_agg(tags.id) FROM tags WHERE ((name = 'A') AND (tags.id IS NOT NULL) AND (tags.id IN (SELECT tags.id FROM tags WHERE (id = 1)))))), false) OR (artists.tag_ids IS NULL))"
|
138
|
-
@c2.exclude(:a_artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE ((tags.id NOT IN (SELECT unnest(artists.tag_ids)
|
138
|
+
@c2.exclude(:a_artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE ((tags.id NOT IN (SELECT _smtopgaa_key_ FROM artists CROSS JOIN unnest(artists.tag_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (SELECT artists.id FROM artists WHERE (id = 1)))))) OR (tags.id IS NULL))"
|
139
139
|
end
|
140
140
|
|
141
141
|
it "filter by associations should respect key options" do
|
@@ -146,7 +146,7 @@ describe Sequel::Model, "pg_array_associations" do
|
|
146
146
|
@c1.filter(:tags=>@o2).sql.must_equal "SELECT * FROM artists WHERE (artists.tag_ids[1:2] @> ARRAY[6]::integer[])"
|
147
147
|
@c2.filter(:artists=>@o1).sql.must_equal "SELECT * FROM tags WHERE ((tags.id * 3) IN (3, 6, 9))"
|
148
148
|
@c1.filter(:tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE coalesce((artists.tag_ids[1:2] && (SELECT array_agg((tags.id * 3)) FROM tags WHERE (id = 1))), false)"
|
149
|
-
@c2.filter(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE ((
|
149
|
+
@c2.filter(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (EXISTS (SELECT 1 FROM (SELECT artists.tag_ids[1:2] AS key FROM artists WHERE (id = 1)) AS t1 WHERE ((tags.id * 3) = any(key))))"
|
150
150
|
end
|
151
151
|
|
152
152
|
it "should raise an error if associated model does not have a primary key, and :primary_key is not specified" do
|
@@ -793,7 +793,7 @@ describe "Sequel::Model.finalize_associations" do
|
|
793
793
|
r[:_dataset].sql.must_equal "SELECT * FROM items"
|
794
794
|
r[:associated_eager_dataset].sql.must_equal "SELECT * FROM items"
|
795
795
|
r.fetch(:_eager_limit_strategy).must_be_nil
|
796
|
-
r[:filter_by_associations_conditions_dataset].sql.must_equal "SELECT unnest(items.foo_ids)
|
796
|
+
r[:filter_by_associations_conditions_dataset].sql.must_equal "SELECT _smtopgaa_key_ FROM items CROSS JOIN unnest(items.foo_ids) AS _smtopgaa_(_smtopgaa_key_) WHERE (items.foo_ids IS NOT NULL)"
|
797
797
|
r[:predicate_key].must_equal Sequel.qualify(:items, :foo_ids)
|
798
798
|
r[:predicate_keys].must_equal [Sequel.qualify(:items, :foo_ids)]
|
799
799
|
r[:reciprocal].must_equal :foos
|
@@ -16,6 +16,8 @@ describe "pg_range extension" do
|
|
16
16
|
@db.extension(:pg_array, :pg_range)
|
17
17
|
end
|
18
18
|
|
19
|
+
endless_range_support = RUBY_VERSION >= '2.6'
|
20
|
+
|
19
21
|
it "should set up conversion procs correctly" do
|
20
22
|
cp = @db.conversion_procs
|
21
23
|
cp[3904].call("[1,2]").must_equal @R.new(1,2, :exclude_begin=>false, :exclude_end=>false, :db_type=>'int4range')
|
@@ -48,6 +50,10 @@ describe "pg_range extension" do
|
|
48
50
|
@db.literal(''..'()[]",\\2').must_equal "'[\"\",\\(\\)\\[\\]\\\"\\,\\\\2]'"
|
49
51
|
end
|
50
52
|
|
53
|
+
it "should literalize endless Range instances to strings correctly" do
|
54
|
+
@db.literal(eval('1..')).must_equal "'[1,]'"
|
55
|
+
end if endless_range_support
|
56
|
+
|
51
57
|
it "should literalize PGRange instances to strings correctly" do
|
52
58
|
@db.literal(@R.new(1, 2)).must_equal "'[1,2]'"
|
53
59
|
@db.literal(@R.new(true, false)).must_equal "'[true,false]'"
|
@@ -71,6 +77,10 @@ describe "pg_range extension" do
|
|
71
77
|
@db.bound_variable_arg(1..2, nil).must_equal "[1,2]"
|
72
78
|
end
|
73
79
|
|
80
|
+
it "should support using endless Range instances as bound variables" do
|
81
|
+
@db.bound_variable_arg(eval('1..'), nil).must_equal "[1,]"
|
82
|
+
end if endless_range_support
|
83
|
+
|
74
84
|
it "should support using PGRange instances as bound variables" do
|
75
85
|
@db.bound_variable_arg(@R.new(1, 2), nil).must_equal "[1,2]"
|
76
86
|
end
|
@@ -79,6 +89,10 @@ describe "pg_range extension" do
|
|
79
89
|
@db.bound_variable_arg([1..2,2...3], nil).must_equal '{"[1,2]","[2,3)"}'
|
80
90
|
end
|
81
91
|
|
92
|
+
it "should support using arrays of endless Range instances as bound variables" do
|
93
|
+
@db.bound_variable_arg([eval('1..'), eval('2..')], nil).must_equal '{"[1,]","[2,]"}'
|
94
|
+
end if endless_range_support
|
95
|
+
|
82
96
|
it "should support using PGRange instances as bound variables" do
|
83
97
|
@db.bound_variable_arg([@R.new(1, 2),@R.new(2, 3)], nil).must_equal '{"[1,2]","[2,3]"}'
|
84
98
|
end
|
@@ -415,6 +429,14 @@ describe "pg_range extension" do
|
|
415
429
|
@R.new(nil, nil).wont_be :==, (1..2)
|
416
430
|
end
|
417
431
|
|
432
|
+
it "should consider PGRanges equal with a endless Range they represent" do
|
433
|
+
@R.new(1, nil).must_be :==, eval('1..')
|
434
|
+
end if endless_range_support
|
435
|
+
|
436
|
+
it "should not consider a PGRange equal with a Range if it can't be expressed as a range" do
|
437
|
+
@R.new(1, nil).wont_be :==, eval('2..')
|
438
|
+
end if endless_range_support
|
439
|
+
|
418
440
|
it "should not consider a PGRange equal to other objects" do
|
419
441
|
@R.new(nil, nil).wont_equal 1
|
420
442
|
end
|
@@ -450,7 +472,6 @@ describe "pg_range extension" do
|
|
450
472
|
|
451
473
|
it "should have #to_range raise an exception if the PGRange cannot be represented by a Range" do
|
452
474
|
proc{@R.new(nil, 1).to_range}.must_raise(Sequel::Error)
|
453
|
-
proc{@R.new(1, nil).to_range}.must_raise(Sequel::Error)
|
454
475
|
proc{@R.new(0, 1, :exclude_begin=>true).to_range}.must_raise(Sequel::Error)
|
455
476
|
proc{@R.empty.to_range}.must_raise(Sequel::Error)
|
456
477
|
end
|
@@ -459,6 +480,14 @@ describe "pg_range extension" do
|
|
459
480
|
@r1.to_range.must_be :==, (1..2)
|
460
481
|
end
|
461
482
|
|
483
|
+
it "should have #to_range return the represented range for endless ranges" do
|
484
|
+
@R.new(1, nil).to_range.must_be :==, eval('1..')
|
485
|
+
end if endless_range_support
|
486
|
+
|
487
|
+
it "should have #to_range raise an exception for endless ranges" do
|
488
|
+
proc{@R.new(1, nil).to_range}.must_raise(Sequel::Error)
|
489
|
+
end unless endless_range_support
|
490
|
+
|
462
491
|
it "should have #to_range cache the returned value" do
|
463
492
|
@r1.to_range.must_be_same_as(@r1.to_range)
|
464
493
|
end
|
@@ -479,9 +508,12 @@ describe "pg_range extension" do
|
|
479
508
|
|
480
509
|
it "should have #valid_ruby_range? return false if the PGRange cannot be represented as a Range" do
|
481
510
|
@R.new(nil, 1).valid_ruby_range?.must_equal false
|
482
|
-
@R.new(1, nil).valid_ruby_range?.must_equal false
|
483
511
|
@R.new(0, 1, :exclude_begin=>true).valid_ruby_range?.must_equal false
|
484
512
|
@R.empty.valid_ruby_range?.must_equal false
|
485
513
|
end
|
514
|
+
|
515
|
+
it "should have #valid_ruby_range return #{endless_range_support} for endless ranges" do
|
516
|
+
@R.new(1, nil).valid_ruby_range?.must_equal(endless_range_support)
|
517
|
+
end
|
486
518
|
end
|
487
519
|
end
|