sequel 5.23.0 → 5.28.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +62 -0
  3. data/README.rdoc +1 -1
  4. data/doc/cheat_sheet.rdoc +1 -0
  5. data/doc/postgresql.rdoc +2 -2
  6. data/doc/release_notes/5.24.0.txt +56 -0
  7. data/doc/release_notes/5.25.0.txt +32 -0
  8. data/doc/release_notes/5.26.0.txt +35 -0
  9. data/doc/release_notes/5.27.0.txt +21 -0
  10. data/doc/release_notes/5.28.0.txt +16 -0
  11. data/doc/testing.rdoc +11 -6
  12. data/lib/sequel/adapters/jdbc.rb +7 -1
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -13
  14. data/lib/sequel/adapters/mysql2.rb +0 -1
  15. data/lib/sequel/adapters/shared/mssql.rb +4 -2
  16. data/lib/sequel/adapters/shared/postgres.rb +30 -7
  17. data/lib/sequel/adapters/shared/sqlite.rb +7 -2
  18. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  19. data/lib/sequel/database/logging.rb +7 -1
  20. data/lib/sequel/database/schema_generator.rb +11 -2
  21. data/lib/sequel/database/schema_methods.rb +2 -0
  22. data/lib/sequel/dataset/features.rb +6 -0
  23. data/lib/sequel/dataset/query.rb +15 -2
  24. data/lib/sequel/dataset/sql.rb +17 -4
  25. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  26. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  27. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  28. data/lib/sequel/extensions/pg_enum.rb +4 -1
  29. data/lib/sequel/extensions/pg_json.rb +1 -1
  30. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  31. data/lib/sequel/extensions/pg_range.rb +9 -0
  32. data/lib/sequel/extensions/sql_comments.rb +2 -2
  33. data/lib/sequel/model/base.rb +12 -5
  34. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  35. data/lib/sequel/plugins/caching.rb +3 -0
  36. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  37. data/lib/sequel/plugins/dirty.rb +3 -9
  38. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  39. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  40. data/lib/sequel/plugins/sharding.rb +11 -5
  41. data/lib/sequel/plugins/static_cache.rb +8 -3
  42. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  43. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  44. data/lib/sequel/sql.rb +4 -1
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/postgres_spec.rb +145 -17
  47. data/spec/adapters/sqlite_spec.rb +1 -1
  48. data/spec/bin_spec.rb +1 -1
  49. data/spec/core/database_spec.rb +20 -1
  50. data/spec/core/expression_filters_spec.rb +26 -7
  51. data/spec/core/schema_spec.rb +18 -0
  52. data/spec/core/spec_helper.rb +1 -1
  53. data/spec/core_extensions_spec.rb +1 -1
  54. data/spec/extensions/any_not_empty_spec.rb +23 -0
  55. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  56. data/spec/extensions/dirty_spec.rb +33 -0
  57. data/spec/extensions/exclude_or_null_spec.rb +15 -0
  58. data/spec/extensions/insert_conflict_spec.rb +26 -0
  59. data/spec/extensions/nested_attributes_spec.rb +48 -0
  60. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  61. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  62. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  63. data/spec/extensions/pg_range_spec.rb +35 -21
  64. data/spec/extensions/sharding_spec.rb +8 -0
  65. data/spec/extensions/spec_helper.rb +1 -1
  66. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  67. data/spec/guards_helper.rb +1 -1
  68. data/spec/integration/dataset_test.rb +57 -17
  69. data/spec/integration/plugin_test.rb +1 -1
  70. data/spec/integration/schema_test.rb +9 -0
  71. data/spec/integration/spec_helper.rb +7 -1
  72. data/spec/model/spec_helper.rb +1 -1
  73. metadata +35 -3
@@ -162,6 +162,39 @@ describe "Sequel::Plugins::Dirty" do
162
162
  @o.save
163
163
  @o.column_changes.must_equal({})
164
164
  end
165
+
166
+ it "should work with the typecast_on_load plugin" do
167
+ @c.instance_variable_set(:@db_schema, :initial=>{:type=>:integer})
168
+ @c.plugin :typecast_on_load, :initial
169
+
170
+ @o = @c.call(:initial=>'1')
171
+ @o.column_changes.must_equal({})
172
+ @o.save
173
+ @o.previous_changes.must_equal({})
174
+ end
175
+
176
+ it "should have column_changes work with the typecast_on_load in after hooks" do
177
+ @c.instance_variable_set(:@db_schema, :initial=>{:type=>:integer})
178
+ @c.plugin :typecast_on_load, :initial
179
+
180
+ @o = @c.new
181
+ @o.initial = 1
182
+ @o.column_changes.must_equal({:initial=>[nil, 1]})
183
+ column_changes_in_after_save = nil
184
+ @o.define_singleton_method(:after_save) do
185
+ column_changes_in_after_save = column_changes
186
+ super()
187
+ end
188
+ @db.fetch = {:initial=>1}
189
+ @o.save
190
+ column_changes_in_after_save.must_equal({:initial=>[nil, 1]})
191
+
192
+ @o.initial = 2
193
+ @o.column_changes.must_equal({:initial=>[1, 2]})
194
+ @o.save
195
+ column_changes_in_after_save.must_equal({:initial=>[1, 2]})
196
+ @o.previous_changes.must_equal({:initial=>[1, 2]})
197
+ end
165
198
  end
166
199
 
167
200
  describe "with existing instance" do
@@ -0,0 +1,15 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "exclude_or_null extension" do
4
+ before do
5
+ @ds = Sequel.mock[:t].extension(:exclude_or_null)
6
+ end
7
+
8
+ it "should add condition where a is false or NULL" do
9
+ @ds.exclude_or_null(:a).sql.must_equal "SELECT * FROM t WHERE NOT coalesce(a, 'f')"
10
+ end
11
+
12
+ it "should not effect normal exclude" do
13
+ @ds.exclude(:a).sql.must_equal "SELECT * FROM t WHERE NOT a"
14
+ end
15
+ end
@@ -74,4 +74,30 @@ describe "insert_conflict plugin" do
74
74
  model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o",
75
75
  "SELECT * FROM t WHERE (id = 2) LIMIT 1"]
76
76
  end
77
+
78
+ it "should work if the prepared_statements plugin is loaded before" do
79
+ db = Sequel.mock(:host=>'sqlite', :fetch=>{:id=>1, :s=>2}, :autoid=>1, :numrows=>1)
80
+ db.extend_datasets{def quote_identifiers?; false end}
81
+ model = Class.new(Sequel::Model)
82
+ model.dataset = db[:t]
83
+ model.columns :id, :s
84
+ model.plugin :prepared_statements
85
+ model.plugin :insert_conflict
86
+ db.sqls
87
+ model.create(:s=>'a').update(:s=>'b')
88
+ db.sqls.must_equal ["INSERT INTO t (s) VALUES ('a')", "SELECT * FROM t WHERE (id = 1) LIMIT 1", "UPDATE t SET s = 'b' WHERE (id = 1)"]
89
+ end
90
+
91
+ it "should work if the prepared_statements plugin is loaded after" do
92
+ db = Sequel.mock(:host=>'postgres', :fetch=>{:id=>1, :s=>2}, :autoid=>1, :numrows=>1)
93
+ db.extend_datasets{def quote_identifiers?; false end}
94
+ model = Class.new(Sequel::Model)
95
+ model.dataset = db[:t]
96
+ model.columns :id, :s
97
+ model.plugin :insert_conflict
98
+ model.plugin :prepared_statements
99
+ db.sqls
100
+ model.create(:s=>'a').update(:s=>'b')
101
+ db.sqls.must_equal ["INSERT INTO t (s) VALUES ('a') RETURNING *", "UPDATE t SET s = 'b' WHERE (id = 1)"]
102
+ end
77
103
  end
@@ -492,6 +492,54 @@ describe "NestedAttributes plugin" do
492
492
  @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 10)"]
493
493
  end
494
494
 
495
+ it "should raise a NoExistingObject error if object to be updated no longer exists, if the :require_modification=>true option is used" do
496
+ @Artist.nested_attributes :albums, :require_modification=>true, :destroy=>true
497
+ al = @Album.load(:id=>10, :name=>'Al')
498
+ ar = @Artist.load(:id=>20, :name=>'Ar')
499
+ ar.associations[:albums] = [al]
500
+ ar.set(:albums_attributes=>[{:id=>10, :name=>'L'}])
501
+ @db.sqls.must_equal []
502
+ @db.numrows = [1, 0]
503
+ proc{ar.save}.must_raise Sequel::NoExistingObject
504
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "UPDATE albums SET name = 'L' WHERE (id = 10)"]
505
+ end
506
+
507
+ it "should not raise an Error if object to be updated no longer exists, if the :require_modification=>false option is used" do
508
+ @Artist.nested_attributes :albums, :require_modification=>false, :destroy=>true
509
+ al = @Album.load(:id=>10, :name=>'Al')
510
+ ar = @Artist.load(:id=>20, :name=>'Ar')
511
+ ar.associations[:albums] = [al]
512
+ ar.set(:albums_attributes=>[{:id=>10, :name=>'L'}])
513
+ @db.sqls.must_equal []
514
+ @db.numrows = [1, 0]
515
+ ar.save
516
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "UPDATE albums SET name = 'L' WHERE (id = 10)"]
517
+ end
518
+
519
+ it "should raise a NoExistingObject error if object to be deleted no longer exists, if the :require_modification=>true option is used" do
520
+ @Artist.nested_attributes :albums, :require_modification=>true, :destroy=>true
521
+ al = @Album.load(:id=>10, :name=>'Al')
522
+ ar = @Artist.load(:id=>20, :name=>'Ar')
523
+ ar.associations[:albums] = [al]
524
+ ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
525
+ @db.sqls.must_equal []
526
+ @db.numrows = [1, 0]
527
+ proc{ar.save}.must_raise Sequel::NoExistingObject
528
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "DELETE FROM albums WHERE (id = 10)"]
529
+ end
530
+
531
+ it "should not raise an Error if object to be deleted no longer exists, if the :require_modification=>false option is used" do
532
+ @Artist.nested_attributes :albums, :require_modification=>false, :destroy=>true
533
+ al = @Album.load(:id=>10, :name=>'Al')
534
+ ar = @Artist.load(:id=>20, :name=>'Ar')
535
+ ar.associations[:albums] = [al]
536
+ ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
537
+ @db.sqls.must_equal []
538
+ @db.numrows = [1, 0]
539
+ ar.save
540
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "DELETE FROM albums WHERE (id = 10)"]
541
+ end
542
+
495
543
  it "should not attempt to validate nested attributes twice for one_to_many associations when creating them" do
496
544
  @Artist.nested_attributes :albums
497
545
  validated = []
@@ -83,9 +83,9 @@ describe "Sequel::Postgres::ArrayOp" do
83
83
  end
84
84
 
85
85
  it "#to_string/join should use the array_to_string function" do
86
- @db.literal(@a.to_string).must_equal "array_to_string(a, '', NULL)"
87
- @db.literal(@a.join).must_equal "array_to_string(a, '', NULL)"
88
- @db.literal(@a.join(':')).must_equal "array_to_string(a, ':', NULL)"
86
+ @db.literal(@a.to_string).must_equal "array_to_string(a, '')"
87
+ @db.literal(@a.join).must_equal "array_to_string(a, '')"
88
+ @db.literal(@a.join(':')).must_equal "array_to_string(a, ':')"
89
89
  @db.literal(@a.join(':', '*')).must_equal "array_to_string(a, ':', '*')"
90
90
  end
91
91
 
@@ -169,4 +169,41 @@ describe "pg_auto_constraint_validations plugin" do
169
169
  proc{o.update(:i=>12)}.must_raise Sequel::ValidationFailed
170
170
  o.errors.must_equal(:i=>['foo bar'])
171
171
  end
172
+
173
+ it "should handle dumping cached metadata and loading metadata from cache" do
174
+ cache_file = "spec/files/pgacv-spec-#{$$}.cache"
175
+ begin
176
+ @ds = @db[:items]
177
+ @ds.send(:columns=, [:id, :i])
178
+ @db.fetch = @metadata_results.dup
179
+ c = Sequel::Model(@ds)
180
+ def c.name; 'Foo' end
181
+ @db.sqls
182
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
183
+ @db.sqls.length.must_equal 5
184
+
185
+ o = c.new(:i=>12)
186
+ @set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
187
+ proc{o.save}.must_raise Sequel::ValidationFailed
188
+
189
+ c.dump_pg_auto_constraint_validations_cache
190
+
191
+ @db.fetch = []
192
+ c = Sequel::Model(@ds)
193
+ def c.name; 'Foo' end
194
+ @db.sqls
195
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
196
+ @db.sqls.must_be_empty
197
+
198
+ o = c.new(:i=>12)
199
+ @set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
200
+ proc{o.save}.must_raise Sequel::ValidationFailed
201
+ ensure
202
+ File.delete(cache_file) if File.file?(cache_file)
203
+ end
204
+ end
205
+
206
+ it "should raise error if attempting to dump cached metadata when not using caching" do
207
+ proc{@c.dump_pg_auto_constraint_validations_cache}.must_raise Sequel::Error
208
+ end
172
209
  end
@@ -286,4 +286,71 @@ describe "Sequel::Postgres::JSONOp" do
286
286
  it "should allow transforming JSONBHash instances into ArrayOp instances" do
287
287
  @db.literal(Sequel.pg_jsonb('a'=>1).op['a']).must_equal "('{\"a\":1}'::jsonb -> 'a')"
288
288
  end
289
+
290
+ it "#path_exists should use the @? operator" do
291
+ @l[@jb.path_exists('$')].must_equal "(j @? '$')"
292
+ end
293
+
294
+ it "#path_exists result should be a boolean expression" do
295
+ @jb.path_exists('$').must_be_kind_of Sequel::SQL::BooleanExpression
296
+ end
297
+
298
+ it "#path_match should use the @@ operator" do
299
+ @l[@jb.path_match('$')].must_equal "(j @@ '$')"
300
+ end
301
+
302
+ it "#path_match result should be a boolean expression" do
303
+ @jb.path_match('$').must_be_kind_of Sequel::SQL::BooleanExpression
304
+ end
305
+
306
+ it "#path_exists! should use the jsonb_path_exists function" do
307
+ @l[@jb.path_exists!('$')].must_equal "jsonb_path_exists(j, '$')"
308
+ @l[@jb.path_exists!('$', '{"x":2}')].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}')"
309
+ @l[@jb.path_exists!('$', x: 2)].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}')"
310
+ @l[@jb.path_exists!('$', {x: 2}, true)].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}', true)"
311
+ end
312
+
313
+ it "#path_exists! result should be a boolean expression" do
314
+ @jb.path_exists!('$').must_be_kind_of Sequel::SQL::BooleanExpression
315
+ end
316
+
317
+ it "#path_match! should use the jsonb_path_match function" do
318
+ @l[@jb.path_match!('$')].must_equal "jsonb_path_match(j, '$')"
319
+ @l[@jb.path_match!('$', '{"x":2}')].must_equal "jsonb_path_match(j, '$', '{\"x\":2}')"
320
+ @l[@jb.path_match!('$', x: 2)].must_equal "jsonb_path_match(j, '$', '{\"x\":2}')"
321
+ @l[@jb.path_match!('$', {x: 2}, true)].must_equal "jsonb_path_match(j, '$', '{\"x\":2}', true)"
322
+ end
323
+
324
+ it "#path_match! result should be a boolean expression" do
325
+ @jb.path_match!('$').must_be_kind_of Sequel::SQL::BooleanExpression
326
+ end
327
+
328
+ it "#path_query should use the jsonb_path_query function" do
329
+ @l[@jb.path_query('$')].must_equal "jsonb_path_query(j, '$')"
330
+ @l[@jb.path_query('$', '{"x":2}')].must_equal "jsonb_path_query(j, '$', '{\"x\":2}')"
331
+ @l[@jb.path_query('$', x: 2)].must_equal "jsonb_path_query(j, '$', '{\"x\":2}')"
332
+ @l[@jb.path_query('$', {x: 2}, true)].must_equal "jsonb_path_query(j, '$', '{\"x\":2}', true)"
333
+ end
334
+
335
+ it "#path_query_array should use the jsonb_path_query_array function" do
336
+ @l[@jb.path_query_array('$')].must_equal "jsonb_path_query_array(j, '$')"
337
+ @l[@jb.path_query_array('$', '{"x":2}')].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}')"
338
+ @l[@jb.path_query_array('$', x: 2)].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}')"
339
+ @l[@jb.path_query_array('$', {x: 2}, true)].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}', true)"
340
+ end
341
+
342
+ it "#path_query_array result should be a JSONBOp" do
343
+ @l[@jb.path_query_array('$').path_query_array('$')].must_equal "jsonb_path_query_array(jsonb_path_query_array(j, '$'), '$')"
344
+ end
345
+
346
+ it "#path_query_first should use the jsonb_path_query_first function" do
347
+ @l[@jb.path_query_first('$')].must_equal "jsonb_path_query_first(j, '$')"
348
+ @l[@jb.path_query_first('$', '{"x":2}')].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}')"
349
+ @l[@jb.path_query_first('$', x: 2)].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}')"
350
+ @l[@jb.path_query_first('$', {x: 2}, true)].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}', true)"
351
+ end
352
+
353
+ it "#path_query_first result should be a JSONBOp" do
354
+ @l[@jb.path_query_first('$').path_query_first('$')].must_equal "jsonb_path_query_first(jsonb_path_query_first(j, '$'), '$')"
355
+ end
289
356
  end
@@ -52,13 +52,13 @@ describe "pg_range extension" do
52
52
  end
53
53
 
54
54
  it "should literalize endless Range instances to strings correctly" do
55
- @db.literal(eval('1..')).must_equal "'[1,]'"
56
- @db.literal(eval('1...')).must_equal "'[1,)'"
55
+ @db.literal(eval('(1..)')).must_equal "'[1,]'"
56
+ @db.literal(eval('(1...)')).must_equal "'[1,)'"
57
57
  end if endless_range_support
58
58
 
59
59
  it "should literalize startless Range instances to strings correctly" do
60
- @db.literal(eval('..1')).must_equal "'[,1]'"
61
- @db.literal(eval('...1')).must_equal "'[,1)'"
60
+ @db.literal(eval('(..1)')).must_equal "'[,1]'"
61
+ @db.literal(eval('(...1)')).must_equal "'[,1)'"
62
62
  end if startless_range_support
63
63
 
64
64
  it "should literalize startless, endless Range instances to strings correctly" do
@@ -90,13 +90,13 @@ describe "pg_range extension" do
90
90
  end
91
91
 
92
92
  it "should support using endless Range instances as bound variables" do
93
- @db.bound_variable_arg(eval('1..'), nil).must_equal "[1,]"
94
- @db.bound_variable_arg(eval('1...'), nil).must_equal "[1,)"
93
+ @db.bound_variable_arg(eval('(1..)'), nil).must_equal "[1,]"
94
+ @db.bound_variable_arg(eval('(1...)'), nil).must_equal "[1,)"
95
95
  end if endless_range_support
96
96
 
97
97
  it "should support using startless Range instances as bound variables" do
98
- @db.bound_variable_arg(eval('..1'), nil).must_equal "[,1]"
99
- @db.bound_variable_arg(eval('...1'), nil).must_equal "[,1)"
98
+ @db.bound_variable_arg(eval('(..1)'), nil).must_equal "[,1]"
99
+ @db.bound_variable_arg(eval('(...1)'), nil).must_equal "[,1)"
100
100
  end if startless_range_support
101
101
 
102
102
  it "should support using startless, endless Range instances as bound variables" do
@@ -113,7 +113,7 @@ describe "pg_range extension" do
113
113
  end
114
114
 
115
115
  it "should support using arrays of endless Range instances as bound variables" do
116
- @db.bound_variable_arg([eval('1..'), eval('2..')], nil).must_equal '{"[1,]","[2,]"}'
116
+ @db.bound_variable_arg([eval('(1..)'), eval('(2..)')], nil).must_equal '{"[1,]","[2,]"}'
117
117
  end if endless_range_support
118
118
 
119
119
  it "should support using PGRange instances as bound variables" do
@@ -136,6 +136,20 @@ describe "pg_range extension" do
136
136
  s[1][1][:ruby_default].must_equal Sequel::Postgres::PGRange.new(1, 5, :exclude_end=>true, :db_type=>'int4range')
137
137
  end
138
138
 
139
+ it "should work correctly in hashes" do
140
+ h = Hash.new(1)
141
+ h[@R.new(1, 2)] = 2
142
+ h[@R.new(nil, nil, :empty => true)] = 3
143
+ h[@R.new(1, 2)].must_equal 2
144
+ h[@R.new(1, 3)].must_equal 1
145
+ h[@R.new(2, 2)].must_equal 1
146
+ h[@R.new(1, 2, :exclude_begin => true)].must_equal 1
147
+ h[@R.new(1, 2, :exclude_end => true)].must_equal 1
148
+ h[@R.new(1, 2, :db_type => :int)].must_equal 1
149
+ h[@R.new(nil, nil, :empty => true)].must_equal 3
150
+ h[@R.new(nil, nil, :empty => true, :db_type => :int)].must_equal 1
151
+ end
152
+
139
153
  describe "database typecasting" do
140
154
  before do
141
155
  @o = @R.new(1, 2, :db_type=>'int4range')
@@ -456,21 +470,21 @@ describe "pg_range extension" do
456
470
  end
457
471
 
458
472
  it "should consider PGRanges equal with a endless Range they represent" do
459
- @R.new(1, nil).must_be :==, eval('1..')
460
- @R.new(1, nil, :exclude_end=>true).must_be :==, eval('1...')
461
- @R.new(1, nil).wont_be :==, eval('1...')
462
- @R.new(1, nil, :exclude_end=>true).wont_be :==, eval('1..')
463
- @R.new(1, nil).wont_be :==, eval('2..')
464
- @R.new(1, nil, :exclude_end=>true).wont_be :==, eval('2...')
473
+ @R.new(1, nil).must_be :==, eval('(1..)')
474
+ @R.new(1, nil, :exclude_end=>true).must_be :==, eval('(1...)')
475
+ @R.new(1, nil).wont_be :==, eval('(1...)')
476
+ @R.new(1, nil, :exclude_end=>true).wont_be :==, eval('(1..)')
477
+ @R.new(1, nil).wont_be :==, eval('(2..)')
478
+ @R.new(1, nil, :exclude_end=>true).wont_be :==, eval('(2...)')
465
479
  end if endless_range_support
466
480
 
467
481
  it "should consider PGRanges equal with a startless Range they represent" do
468
- @R.new(nil, 1).must_be :==, eval('..1')
469
- @R.new(nil, 1, :exclude_end=>true).must_be :==, eval('...1')
470
- @R.new(nil, 1).wont_be :==, eval('...1')
471
- @R.new(nil, 1, :exclude_end=>true).wont_be :==, eval('..1')
472
- @R.new(nil, 1).wont_be :==, eval('..2')
473
- @R.new(nil, 1, :exclude_end=>true).wont_be :==, eval('...2')
482
+ @R.new(nil, 1).must_be :==, eval('(..1)')
483
+ @R.new(nil, 1, :exclude_end=>true).must_be :==, eval('(...1)')
484
+ @R.new(nil, 1).wont_be :==, eval('(...1)')
485
+ @R.new(nil, 1, :exclude_end=>true).wont_be :==, eval('(..1)')
486
+ @R.new(nil, 1).wont_be :==, eval('(..2)')
487
+ @R.new(nil, 1, :exclude_end=>true).wont_be :==, eval('(...2)')
474
488
  end if startless_range_support
475
489
 
476
490
  it "should consider PGRanges equal with a startless, endless Range they represent" do
@@ -186,4 +186,12 @@ describe "sharding plugin" do
186
186
  ["UPDATE albums SET artist_id = 2, name = 'RF' WHERE (id = 1) -- s1", "UPDATE albums SET name = 'RF', artist_id = 2 WHERE (id = 1) -- s1"].must_include(sqls.slice!(2))
187
187
  sqls.must_equal ["SELECT * FROM albums LIMIT 1 -- s2", "BEGIN -- s1", "COMMIT -- s1"]
188
188
  end
189
+
190
+ it "should have objects retrieved from a specific shard using with_server from server_block extension" do
191
+ album = @db.extension(:server_block).with_server(:s1) do
192
+ @Album.first
193
+ end
194
+ album.update(:name=>'MO')
195
+ @db.sqls.must_equal ["SELECT * FROM albums LIMIT 1 -- s1", "UPDATE albums SET name = 'MO' WHERE (id = 1) -- s1"]
196
+ end
189
197
  end
@@ -7,7 +7,7 @@ end
7
7
 
8
8
  ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
9
9
  gem 'minitest'
10
- require 'minitest/autorun'
10
+ require 'minitest/global_expectations/autorun'
11
11
  require 'minitest/hooks/default'
12
12
  require 'minitest/shared_description'
13
13
 
@@ -0,0 +1,35 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "static_cache_cache plugin" do
4
+ before do
5
+ @db = Sequel.mock
6
+ @db.fetch = [{:id=>1, :name=>'A'}, {:id=>2, :name=>'B'}]
7
+ @c = Class.new(Sequel::Model(@db[:t]))
8
+ def @c.name; 'Foo' end
9
+ @c.columns :id, :name
10
+ @file = "spec/files/static_cache_cache-spec-#{$$}.cache"
11
+ end
12
+ after do
13
+ File.delete(@file) if File.file?(@file)
14
+ end
15
+
16
+ it "should allow dumping and loading static cache rows from a cache file" do
17
+ @c.plugin :static_cache_cache, @file
18
+ @db.sqls
19
+ @c.plugin :static_cache
20
+ @db.sqls.must_equal ['SELECT * FROM t']
21
+ @c.all.must_equal [@c.load(:id=>1, :name=>'A'), @c.load(:id=>2, :name=>'B')]
22
+
23
+ @c.dump_static_cache_cache
24
+
25
+ @db.fetch = []
26
+ c = Class.new(Sequel::Model(@db[:t]))
27
+ def c.name; 'Foo' end
28
+ c.columns :id, :name
29
+ @c.plugin :static_cache_cache, @file
30
+ @db.sqls
31
+ @c.plugin :static_cache
32
+ @db.sqls.must_be_empty
33
+ @c.all.must_equal [@c.load(:id=>1, :name=>'A'), @c.load(:id=>2, :name=>'B')]
34
+ end
35
+ end
@@ -1,6 +1,6 @@
1
1
  ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
2
2
  gem 'minitest'
3
- require 'minitest/autorun'
3
+ require 'minitest/global_expectations/autorun'
4
4
  require 'minitest/hooks/default'
5
5
  require 'minitest/shared_description'
6
6
 
@@ -92,6 +92,17 @@ describe "Simple Dataset operations" do
92
92
  @ds.from_self(:alias=>:items).graph(@ds.from_self, {:id=>:id}, :table_alias=>:b).extension(:graph_each).all.must_equal [{:items=>{:id=>1, :number=>10}, :b=>{:id=>1, :number=>10}}]
93
93
  end
94
94
 
95
+ cspecify "should have insert and update work with Sequel::DEFAULT", :sqlite do
96
+ @db.create_table!(:items) do
97
+ Integer :number, :default=>10
98
+ end
99
+ @ds.insert(:number=>Sequel::DEFAULT)
100
+ @ds.select_map(:number).must_equal [10]
101
+ @ds.insert(:number=>20)
102
+ @ds.update(:number=>Sequel::DEFAULT)
103
+ @ds.select_map(:number).must_equal [10, 10]
104
+ end
105
+
95
106
  cspecify "should have insert work correctly when inserting a row with all NULL values", :hsqldb do
96
107
  @db.create_table!(:items) do
97
108
  String :name
@@ -443,6 +454,20 @@ describe Sequel::Dataset do
443
454
  @d.extension(:sequel_4_dataset_methods).interval(:value).to_i.must_equal 6
444
455
  end
445
456
 
457
+ it "should support or emulate filtered aggregate functions" do
458
+ @d.insert(:name => 'abc', :value => 123)
459
+ @d.insert(:name => 'abc', :value => 456)
460
+ @d.insert(:name => 'def', :value => 324)
461
+ @d.get{count.function.*.filter{value > 100}}.must_equal 3
462
+ @d.get{count.function.*.filter{value > 200}}.must_equal 2
463
+ @d.get{count.function.*.filter{value > 400}}.must_equal 1
464
+ @d.get{count.function.*.filter{value > 500}}.must_equal 0
465
+ @d.get{count(:value).filter{value > 100}}.must_equal 3
466
+ @d.get{count(:value).filter{value > 200}}.must_equal 2
467
+ @d.get{count(:value).filter{value > 400}}.must_equal 1
468
+ @d.get{count(:value).filter{value > 500}}.must_equal 0
469
+ end
470
+
446
471
  it "should return the correct records" do
447
472
  @d.to_a.must_equal []
448
473
  @d.insert(:name => 'abc', :value => 123)
@@ -568,22 +593,21 @@ describe Sequel::Dataset do
568
593
  end
569
594
 
570
595
  describe "Simple Dataset operations" do
571
- before do
596
+ before(:all) do
572
597
  DB.create_table!(:items) do
573
598
  Integer :number
574
599
  TrueClass :flag
575
600
  end
576
- @ds = DB[:items]
601
+ @ds = DB[:items].order(:number)
602
+ @ds.insert(:number=>1, :flag=>true)
603
+ @ds.insert(:number=>2, :flag=>false)
604
+ @ds.insert(:number=>3, :flag=>nil)
577
605
  end
578
- after do
606
+ after(:all) do
579
607
  DB.drop_table?(:items)
580
608
  end
581
609
 
582
610
  it "should deal with boolean conditions correctly" do
583
- @ds.insert(:number=>1, :flag=>true)
584
- @ds.insert(:number=>2, :flag=>false)
585
- @ds.insert(:number=>3, :flag=>nil)
586
- @ds = @ds.order(:number)
587
611
  @ds.filter(:flag=>true).map(:number).must_equal [1]
588
612
  @ds.filter(:flag=>false).map(:number).must_equal [2]
589
613
  @ds.filter(:flag=>nil).map(:number).must_equal [3]
@@ -591,6 +615,22 @@ describe "Simple Dataset operations" do
591
615
  @ds.exclude(:flag=>false).map(:number).must_equal [1, 3]
592
616
  @ds.exclude(:flag=>nil).map(:number).must_equal [1, 2]
593
617
  end
618
+
619
+ cspecify "should deal with boolean equality conditions correctly", :derby do
620
+ @ds.filter(true=>:flag).map(:number).must_equal [1]
621
+ @ds.filter(false=>:flag).map(:number).must_equal [2]
622
+ @ds.filter(nil=>:flag).map(:number).must_equal []
623
+ @ds.exclude(true=>:flag).map(:number).must_equal [2]
624
+ @ds.exclude(false=>:flag).map(:number).must_equal [1]
625
+ @ds.exclude(nil=>:flag).map(:number).must_equal []
626
+ end
627
+
628
+ cspecify "should have exclude_or_null work correctly", :mssql, :derby, :oracle, :db2, :sqlanywhere do
629
+ @ds = @ds.extension(:exclude_or_null)
630
+ @ds.exclude_or_null(true=>:flag).map(:number).must_equal [2, 3]
631
+ @ds.exclude_or_null(false=>:flag).map(:number).must_equal [1, 3]
632
+ @ds.exclude_or_null(nil=>:flag).map(:number).must_equal [1, 2, 3]
633
+ end
594
634
  end
595
635
 
596
636
  describe "Simple Dataset operations in transactions" do
@@ -1553,20 +1593,20 @@ describe "Sequel::Dataset DSL support" do
1553
1593
 
1554
1594
  it "should work with endless ranges as hash values" do
1555
1595
  @ds.insert(20, 10)
1556
- @ds.filter(:a=>eval('30..')).all.must_equal []
1557
- @ds.filter(:a=>eval('20...')).all.must_equal [{:a=>20, :b=>10}]
1558
- @ds.filter(:a=>eval('20..')).all.must_equal [{:a=>20, :b=>10}]
1559
- @ds.filter(:a=>eval('10..')).all.must_equal [{:a=>20, :b=>10}]
1596
+ @ds.filter(:a=>eval('(30..)')).all.must_equal []
1597
+ @ds.filter(:a=>eval('(20...)')).all.must_equal [{:a=>20, :b=>10}]
1598
+ @ds.filter(:a=>eval('(20..)')).all.must_equal [{:a=>20, :b=>10}]
1599
+ @ds.filter(:a=>eval('(10..)')).all.must_equal [{:a=>20, :b=>10}]
1560
1600
  end if RUBY_VERSION >= '2.6'
1561
1601
 
1562
1602
  it "should work with startless ranges as hash values" do
1563
1603
  @ds.insert(20, 10)
1564
- @ds.filter(:a=>eval('..30')).all.must_equal [{:a=>20, :b=>10}]
1565
- @ds.filter(:a=>eval('...30')).all.must_equal [{:a=>20, :b=>10}]
1566
- @ds.filter(:a=>eval('..20')).all.must_equal [{:a=>20, :b=>10}]
1567
- @ds.filter(:a=>eval('...20')).all.must_equal []
1568
- @ds.filter(:a=>eval('..10')).all.must_equal []
1569
- @ds.filter(:a=>eval('...10')).all.must_equal []
1604
+ @ds.filter(:a=>eval('(..30)')).all.must_equal [{:a=>20, :b=>10}]
1605
+ @ds.filter(:a=>eval('(...30)')).all.must_equal [{:a=>20, :b=>10}]
1606
+ @ds.filter(:a=>eval('(..20)')).all.must_equal [{:a=>20, :b=>10}]
1607
+ @ds.filter(:a=>eval('(...20)')).all.must_equal []
1608
+ @ds.filter(:a=>eval('(..10)')).all.must_equal []
1609
+ @ds.filter(:a=>eval('(...10)')).all.must_equal []
1570
1610
 
1571
1611
  @ds.filter(:a=>eval('nil..nil')).all.must_equal [{:a=>20, :b=>10}]
1572
1612
  end if RUBY_VERSION >= '2.7'