sequel 5.9.0 → 5.10.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.
@@ -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
@@ -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.freeze.with_extend(m1, m2){def a; 4**super end}
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.freeze.with_row_proc(l)
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).freeze
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].freeze
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).freeze
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).freeze
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).freeze
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).freeze
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).freeze
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).freeze
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).freeze
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.freeze
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.freeze
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.freeze
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].freeze
5238
+ @ds = Sequel.mock[:test]
5229
5239
  end
5230
5240
 
5231
- it "should be returned by Dataset#freeze" do
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].freeze
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].freeze
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.freeze
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>"
@@ -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) FROM artists WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id = 1))))"
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) FROM artists WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id = 1)))) OR (tags.id IS NULL))"
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) FROM artists WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (7, 8)))))"
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) FROM artists WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (7, 8))))) OR (tags.id 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 _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 (tags.id IN (SELECT unnest(artists.tag_ids) FROM artists WHERE (id = 1)))"
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) FROM artists WHERE ((name = 'A') AND (artists.tag_ids IS NOT NULL) AND (artists.id IN (SELECT artists.id FROM artists WHERE (id = 1))))))"
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 ((tags.id NOT IN (SELECT unnest(artists.tag_ids) FROM artists WHERE (id = 1))) OR (tags.id IS NULL))"
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) FROM artists 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))"
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 ((tags.id * 3) IN (SELECT unnest(artists.tag_ids[1:2]) FROM artists WHERE (id = 1)))"
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) FROM items WHERE (items.foo_ids IS NOT NULL)"
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