sequel 5.19.0 → 5.24.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +102 -0
  3. data/doc/dataset_filtering.rdoc +15 -0
  4. data/doc/opening_databases.rdoc +5 -1
  5. data/doc/release_notes/5.20.0.txt +89 -0
  6. data/doc/release_notes/5.21.0.txt +87 -0
  7. data/doc/release_notes/5.22.0.txt +48 -0
  8. data/doc/release_notes/5.23.0.txt +56 -0
  9. data/doc/release_notes/5.24.0.txt +56 -0
  10. data/doc/sharding.rdoc +2 -0
  11. data/doc/testing.rdoc +1 -0
  12. data/doc/transactions.rdoc +38 -0
  13. data/lib/sequel/adapters/ado.rb +27 -19
  14. data/lib/sequel/adapters/jdbc.rb +7 -1
  15. data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  18. data/lib/sequel/adapters/mysql2.rb +2 -3
  19. data/lib/sequel/adapters/shared/mssql.rb +7 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +37 -19
  21. data/lib/sequel/adapters/shared/sqlite.rb +27 -3
  22. data/lib/sequel/adapters/sqlite.rb +1 -1
  23. data/lib/sequel/adapters/tinytds.rb +12 -0
  24. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
  25. data/lib/sequel/database/logging.rb +7 -1
  26. data/lib/sequel/database/query.rb +1 -1
  27. data/lib/sequel/database/schema_generator.rb +12 -3
  28. data/lib/sequel/database/schema_methods.rb +2 -0
  29. data/lib/sequel/database/transactions.rb +57 -5
  30. data/lib/sequel/dataset.rb +4 -2
  31. data/lib/sequel/dataset/actions.rb +3 -2
  32. data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
  33. data/lib/sequel/dataset/query.rb +5 -1
  34. data/lib/sequel/dataset/sql.rb +11 -7
  35. data/lib/sequel/extensions/named_timezones.rb +52 -8
  36. data/lib/sequel/extensions/pg_array.rb +4 -0
  37. data/lib/sequel/extensions/pg_json.rb +387 -123
  38. data/lib/sequel/extensions/pg_range.rb +3 -2
  39. data/lib/sequel/extensions/pg_row.rb +3 -1
  40. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  41. data/lib/sequel/extensions/server_block.rb +15 -4
  42. data/lib/sequel/model/associations.rb +35 -9
  43. data/lib/sequel/model/plugins.rb +104 -0
  44. data/lib/sequel/plugins/association_dependencies.rb +3 -3
  45. data/lib/sequel/plugins/association_pks.rb +14 -4
  46. data/lib/sequel/plugins/association_proxies.rb +3 -2
  47. data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
  48. data/lib/sequel/plugins/composition.rb +13 -9
  49. data/lib/sequel/plugins/finder.rb +2 -2
  50. data/lib/sequel/plugins/hook_class_methods.rb +17 -5
  51. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  52. data/lib/sequel/plugins/inverted_subsets.rb +2 -2
  53. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
  54. data/lib/sequel/plugins/rcte_tree.rb +6 -0
  55. data/lib/sequel/plugins/static_cache.rb +8 -3
  56. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  57. data/lib/sequel/plugins/subset_conditions.rb +2 -2
  58. data/lib/sequel/plugins/validation_class_methods.rb +5 -3
  59. data/lib/sequel/sql.rb +15 -3
  60. data/lib/sequel/timezones.rb +50 -11
  61. data/lib/sequel/version.rb +1 -1
  62. data/spec/adapters/mssql_spec.rb +24 -0
  63. data/spec/adapters/mysql_spec.rb +0 -5
  64. data/spec/adapters/postgres_spec.rb +319 -1
  65. data/spec/bin_spec.rb +1 -1
  66. data/spec/core/database_spec.rb +123 -2
  67. data/spec/core/dataset_spec.rb +33 -1
  68. data/spec/core/expression_filters_spec.rb +25 -1
  69. data/spec/core/schema_spec.rb +24 -0
  70. data/spec/extensions/class_table_inheritance_spec.rb +30 -8
  71. data/spec/extensions/core_refinements_spec.rb +1 -1
  72. data/spec/extensions/hook_class_methods_spec.rb +22 -0
  73. data/spec/extensions/insert_conflict_spec.rb +103 -0
  74. data/spec/extensions/migration_spec.rb +13 -0
  75. data/spec/extensions/named_timezones_spec.rb +109 -2
  76. data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
  77. data/spec/extensions/pg_json_spec.rb +218 -29
  78. data/spec/extensions/pg_range_spec.rb +76 -9
  79. data/spec/extensions/rcte_tree_spec.rb +6 -0
  80. data/spec/extensions/s_spec.rb +1 -1
  81. data/spec/extensions/schema_dumper_spec.rb +4 -2
  82. data/spec/extensions/server_block_spec.rb +38 -0
  83. data/spec/extensions/spec_helper.rb +8 -1
  84. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  85. data/spec/integration/dataset_test.rb +25 -9
  86. data/spec/integration/plugin_test.rb +42 -0
  87. data/spec/integration/schema_test.rb +7 -2
  88. data/spec/integration/transaction_test.rb +50 -0
  89. data/spec/model/associations_spec.rb +84 -4
  90. data/spec/model/plugins_spec.rb +111 -0
  91. metadata +16 -2
@@ -86,7 +86,7 @@ Begin creating indexes
86
86
  Finished creating indexes
87
87
  Begin adding foreign key constraints
88
88
  Finished adding foreign key constraints
89
- Database copy finished in \\d\\.\\d+ seconds
89
+ Database copy finished in \\d+\\.\\d+ seconds
90
90
  END
91
91
  DB2.tables.sort_by{|t| t.to_s}.must_equal [:a, :b]
92
92
  DB[:a].all.must_equal [{:a=>1, :name=>'foo'}]
@@ -264,6 +264,25 @@ describe "Database#log_connection_yield" do
264
264
  @o.logs.first.first.must_equal :info
265
265
  @o.logs.first.last.must_match(/\A\(\d\.\d{6}s\) blah; \[1, 2\]\z/)
266
266
  end
267
+
268
+ it "should log without a logger defined by forcing skip_logging? to return false" do
269
+ @db.logger = nil
270
+ @db.extend(Module.new do
271
+ def skip_logging?
272
+ false
273
+ end
274
+
275
+ def log_duration(*)
276
+ self.did_log = true
277
+ end
278
+
279
+ attr_accessor :did_log
280
+ end)
281
+
282
+ @db.log_connection_yield('some sql', @conn) {}
283
+
284
+ @db.did_log.must_equal true
285
+ end
267
286
  end
268
287
 
269
288
  describe "Database#uri" do
@@ -1140,15 +1159,86 @@ describe "Database#transaction with savepoint support" do
1140
1159
  end
1141
1160
 
1142
1161
  it "should support after_rollback inside savepoints" do
1143
- @db.transaction do
1162
+ @db.transaction(:rollback=>:always) do
1144
1163
  @db.after_rollback{@db.execute('foo')}
1145
1164
  @db.transaction(:savepoint=>true){@db.after_rollback{@db.execute('bar')}}
1146
1165
  @db.after_rollback{@db.execute('baz')}
1147
- raise Sequel::Rollback
1148
1166
  end
1149
1167
  @db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'RELEASE SAVEPOINT autopoint_1', 'ROLLBACK', 'foo', 'bar', 'baz']
1150
1168
  end
1151
1169
 
1170
+ it "should run after_commit if savepoint rolled back" do
1171
+ @db.transaction do
1172
+ @db.after_commit{@db.execute('foo')}
1173
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.after_commit{@db.execute('bar')}}
1174
+ end
1175
+ @db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT', 'foo', 'bar']
1176
+ end
1177
+
1178
+ it "should not run after_commit if savepoint rolled back and :savepoint option used" do
1179
+ @db.transaction do
1180
+ @db.after_commit{@db.execute('foo')}
1181
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.after_commit(:savepoint=>true){@db.execute('bar')}}
1182
+ end
1183
+ @db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT', 'foo']
1184
+ end
1185
+
1186
+ it "should not run after_commit if higher-level savepoint rolled back and :savepoint option used" do
1187
+ @db.transaction do
1188
+ @db.after_commit{@db.execute('foo')}
1189
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.transaction(:savepoint=>true){@db.after_commit(:savepoint=>true){@db.execute('bar')}}}
1190
+ end
1191
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "SAVEPOINT autopoint_2", "RELEASE SAVEPOINT autopoint_2", "ROLLBACK TO SAVEPOINT autopoint_1", "COMMIT", "foo"]
1192
+ end
1193
+
1194
+ it "should not run after_commit if transaction rolled back and :savepoint option used" do
1195
+ @db.transaction(:rollback=>:always) do
1196
+ @db.after_commit{@db.execute('foo')}
1197
+ @db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.after_commit(:savepoint=>true){@db.execute('bar')}}}
1198
+ end
1199
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "SAVEPOINT autopoint_2", "RELEASE SAVEPOINT autopoint_2", "RELEASE SAVEPOINT autopoint_1", "ROLLBACK"]
1200
+ end
1201
+
1202
+ it "should run after_rollback if savepoint rolls back" do
1203
+ @db.transaction(:rollback=>:always) do
1204
+ @db.after_rollback{@db.execute('foo')}
1205
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.after_rollback{@db.execute('bar')}}
1206
+ @db.after_rollback{@db.execute('baz')}
1207
+ end
1208
+ @db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'ROLLBACK TO SAVEPOINT autopoint_1', 'ROLLBACK', 'foo', 'bar', 'baz']
1209
+ end
1210
+
1211
+ it "should run after_rollback when savepoint rolls back if :savepoint option used" do
1212
+ @db.transaction(:rollback=>:always) do
1213
+ @db.after_rollback{@db.execute('foo')}
1214
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.after_rollback(:savepoint=>true){@db.execute('bar')}}
1215
+ @db.after_rollback{@db.execute('baz')}
1216
+ end
1217
+ @db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'ROLLBACK TO SAVEPOINT autopoint_1', 'bar', 'ROLLBACK', 'foo', 'baz']
1218
+ end
1219
+
1220
+ it "should run after_rollback if savepoint rolled back and :savepoint option used, even if transaction commits" do
1221
+ @db.transaction do
1222
+ @db.after_commit{@db.execute('foo')}
1223
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.after_rollback(:savepoint=>true){@db.execute('bar')}}
1224
+ end
1225
+ @db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'ROLLBACK TO SAVEPOINT autopoint_1', 'bar', 'COMMIT', 'foo']
1226
+ end
1227
+
1228
+ it "should run after_rollback if higher-level savepoint rolled back and :savepoint option used" do
1229
+ @db.transaction do
1230
+ @db.transaction(:savepoint=>true, :rollback=>:always){@db.transaction(:savepoint=>true){@db.after_rollback(:savepoint=>true){@db.execute('bar')}}}
1231
+ end
1232
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "SAVEPOINT autopoint_2", "RELEASE SAVEPOINT autopoint_2", "ROLLBACK TO SAVEPOINT autopoint_1", "bar", "COMMIT"]
1233
+ end
1234
+
1235
+ it "should run after_rollback if transaction rolled back and :savepoint option used" do
1236
+ @db.transaction(:rollback=>:always) do
1237
+ @db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.after_rollback(:savepoint=>true){@db.execute('bar')}}}
1238
+ end
1239
+ @db.sqls.must_equal ["BEGIN", "SAVEPOINT autopoint_1", "SAVEPOINT autopoint_2", "RELEASE SAVEPOINT autopoint_2", "RELEASE SAVEPOINT autopoint_1", "ROLLBACK", "bar"]
1240
+ end
1241
+
1152
1242
  it "should raise an error if you attempt to use after_commit inside a savepoint in a prepared transaction" do
1153
1243
  @db.define_singleton_method(:supports_prepared_transactions?){true}
1154
1244
  proc{@db.transaction(:prepare=>'XYZ'){@db.transaction(:savepoint=>true){@db.after_commit{@db.execute('foo')}}}}.must_raise(Sequel::Error)
@@ -2002,6 +2092,18 @@ describe "Database#typecast_value" do
2002
2092
  proc{@db.typecast_value(:datetime, 4)}.must_raise(Sequel::InvalidValue)
2003
2093
  end
2004
2094
 
2095
+ it "should raise an InvalidValue when given an invalid timezone value" do
2096
+ begin
2097
+ Sequel.default_timezone = :blah
2098
+ proc{@db.typecast_value(:datetime, [2019, 2, 3, 4, 5, 6])}.must_raise(Sequel::InvalidValue)
2099
+ Sequel.datetime_class = DateTime
2100
+ proc{@db.typecast_value(:datetime, [2019, 2, 3, 4, 5, 6])}.must_raise(Sequel::InvalidValue)
2101
+ ensure
2102
+ Sequel.default_timezone = nil
2103
+ Sequel.datetime_class = Time
2104
+ end
2105
+ end
2106
+
2005
2107
  it "should handle integers with leading 0 as base 10" do
2006
2108
  @db.typecast_value(:integer, "013").must_equal 13
2007
2109
  @db.typecast_value(:integer, "08").must_equal 8
@@ -2246,6 +2348,17 @@ describe "Database#typecast_value" do
2246
2348
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal Time.local(2011, 10, 11, 12, 13, 14)
2247
2349
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal Time.local(2011, 10, 11, 12, 13, 14, 500000)
2248
2350
 
2351
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :offset=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14, 43200)
2352
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14.5, 43200)
2353
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14, 43200)
2354
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14.5, 43200)
2355
+
2356
+ Sequel.default_timezone = :utc
2357
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal Time.utc(2011, 10, 11, 12, 13, 14)
2358
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal Time.utc(2011, 10, 11, 12, 13, 14, 500000)
2359
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal Time.utc(2011, 10, 11, 12, 13, 14)
2360
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal Time.utc(2011, 10, 11, 12, 13, 14, 500000)
2361
+
2249
2362
  Sequel.datetime_class = DateTime
2250
2363
  @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14)
2251
2364
  @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2))
@@ -2255,8 +2368,16 @@ describe "Database#typecast_value" do
2255
2368
  @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), Rational(1, 2))
2256
2369
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, Rational(1, 2))
2257
2370
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), Rational(1, 2))
2371
+
2372
+ Sequel.default_timezone = :local
2373
+ offset = Rational(Time.local(2011, 10, 11, 12, 13, 14).utc_offset, 86400)
2374
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, offset)
2375
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), offset)
2376
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, offset)
2377
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), offset)
2258
2378
  ensure
2259
2379
  Sequel.datetime_class = Time
2380
+ Sequel.default_timezone = nil
2260
2381
  end
2261
2382
  end
2262
2383
 
@@ -3434,6 +3434,16 @@ describe "Dataset#multi_insert" do
3434
3434
  'COMMIT']
3435
3435
  end
3436
3436
 
3437
+ it "should handle :return=>:primary_key option if dataset has a row_proc" do
3438
+ @db.autoid = 1
3439
+ @ds.with_row_proc(lambda{|h| Object.new}).multi_insert(@list, :return=>:primary_key).must_equal [1, 2, 3]
3440
+ @db.sqls.must_equal ['BEGIN',
3441
+ "INSERT INTO items (name) VALUES ('abc')",
3442
+ "INSERT INTO items (name) VALUES ('def')",
3443
+ "INSERT INTO items (name) VALUES ('ghi')",
3444
+ 'COMMIT']
3445
+ end
3446
+
3437
3447
  with_symbol_splitting "should handle splittable symbols for tables" do
3438
3448
  @ds = @ds.from(:sch__tab)
3439
3449
  @ds.multi_insert(@list)
@@ -3715,7 +3725,7 @@ end
3715
3725
 
3716
3726
  describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
3717
3727
  before do
3718
- @db = Sequel.mock(:servers=>{:read_only=>{}}, :autoid=>1)
3728
+ @db = Sequel.mock(:servers=>{:read_only=>{}, :r1=>{}}, :autoid=>1)
3719
3729
  @ds = @db[:items]
3720
3730
  end
3721
3731
 
@@ -3753,6 +3763,18 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #ex
3753
3763
  @ds.for_update.send(:execute, 'SELECT 1')
3754
3764
  @db.sqls.must_equal ["SELECT 1"]
3755
3765
  end
3766
+
3767
+ [:execute, :execute_dui, :execute_insert, :execute_ddl].each do |meth|
3768
+ it "##{meth} should respect explicit :server option" do
3769
+ @ds.send(meth, 'SELECT 1', :server=>:r1)
3770
+ @db.sqls.must_equal ["SELECT 1 -- r1"]
3771
+ end
3772
+
3773
+ it "##{meth} should respect dataset's :server option if :server option not given" do
3774
+ @ds.server(:r1).send(meth, 'SELECT 1')
3775
+ @db.sqls.must_equal ["SELECT 1 -- r1"]
3776
+ end
3777
+ end
3756
3778
  end
3757
3779
 
3758
3780
  describe "Dataset#with_sql_*" do
@@ -4483,6 +4505,16 @@ describe "Sequel timezone support" do
4483
4505
  proc{Sequel.database_to_application_timestamp(Object.new)}.must_raise(Sequel::InvalidValue)
4484
4506
  end
4485
4507
 
4508
+ it "should raise an InvalidValue error when the Time class is used and when a bad application timezone is used when attempting to convert timestamps" do
4509
+ Sequel.application_timezone = :blah
4510
+ proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.must_raise(Sequel::InvalidValue)
4511
+ end
4512
+
4513
+ it "should raise an InvalidValue error when the Time class is used and when a bad database timezone is used when attempting to convert timestamps" do
4514
+ Sequel.database_timezone = :blah
4515
+ proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.must_raise(Sequel::InvalidValue)
4516
+ end
4517
+
4486
4518
  it "should raise an InvalidValue error when the DateTime class is used and when a bad application timezone is used when attempting to convert timestamps" do
4487
4519
  Sequel.application_timezone = :blah
4488
4520
  Sequel.datetime_class = DateTime
@@ -526,11 +526,35 @@ describe "Blockless Ruby Filters" do
526
526
  dsc.new(@d.db).literal(Sequel.trim(:a)).must_equal 'trimFOO(lower(a))'
527
527
  end
528
528
 
529
- it "should endless ranges" do
529
+ it "should handle endless ranges" do
530
530
  endless = eval('1..')
531
531
  @d.l{x =~ endless}.must_equal '(x >= 1)'
532
532
  @d.l(:x => endless).must_equal '(x >= 1)'
533
+
534
+ endless = eval('1...')
535
+ @d.l{x =~ endless}.must_equal '(x >= 1)'
536
+ @d.l(:x => endless).must_equal '(x >= 1)'
533
537
  end if RUBY_VERSION >= '2.6'
538
+
539
+ it "should handle startless ranges" do
540
+ endless = eval('..1')
541
+ @d.l{x =~ endless}.must_equal '(x <= 1)'
542
+ @d.l(:x => endless).must_equal '(x <= 1)'
543
+
544
+ endless = eval('...1')
545
+ @d.l{x =~ endless}.must_equal '(x < 1)'
546
+ @d.l(:x => endless).must_equal '(x < 1)'
547
+ end if RUBY_VERSION >= '2.7'
548
+
549
+ it "should handle startless, endless ranges" do
550
+ endless = eval('nil..nil')
551
+ @d.l{x =~ endless}.must_equal '(1 = 1)'
552
+ @d.l(:x => endless).must_equal '(1 = 1)'
553
+
554
+ endless = eval('nil...nil')
555
+ @d.l{x =~ endless}.must_equal '(1 = 1)'
556
+ @d.l(:x => endless).must_equal '(1 = 1)'
557
+ end if RUBY_VERSION >= '2.7'
534
558
  end
535
559
 
536
560
  describe Sequel::SQL::VirtualRow do
@@ -240,6 +240,24 @@ describe "DB#create_table" do
240
240
  @db.sqls.must_equal ["CREATE TABLE cats (id integer, name text, UNIQUE (name) DEFERRABLE INITIALLY IMMEDIATE)"]
241
241
  end
242
242
 
243
+ it "should handle deferred unique column constraints" do
244
+ @db.create_table(:cats) do
245
+ integer :id, :unique=>true, :unique_deferrable=>true
246
+ integer :i, :unique=>true, :unique_deferrable=>:immediate
247
+ integer :j, :unique=>true, :unique_deferrable=>false
248
+ end
249
+ @db.sqls.must_equal ["CREATE TABLE cats (id integer UNIQUE DEFERRABLE INITIALLY DEFERRED, i integer UNIQUE DEFERRABLE INITIALLY IMMEDIATE, j integer UNIQUE NOT DEFERRABLE)"]
250
+ end
251
+
252
+ it "should handle deferred primary key column constraints" do
253
+ @db.create_table(:cats) do
254
+ integer :id, :primary_key=>true, :primary_key_deferrable=>true
255
+ integer :i, :primary_key=>true, :primary_key_deferrable=>:immediate
256
+ integer :j, :primary_key=>true, :primary_key_deferrable=>false
257
+ end
258
+ @db.sqls.must_equal ["CREATE TABLE cats (id integer PRIMARY KEY DEFERRABLE INITIALLY DEFERRED, i integer PRIMARY KEY DEFERRABLE INITIALLY IMMEDIATE, j integer PRIMARY KEY NOT DEFERRABLE)"]
259
+ end
260
+
243
261
  it "should accept unsigned definition" do
244
262
  @db.create_table(:cats) do
245
263
  integer :value, :unsigned => true
@@ -1777,17 +1795,21 @@ describe "Schema Parser" do
1777
1795
  @db.schema(:text).first.last[:type].must_equal :string
1778
1796
  @db.schema(:date).first.last[:type].must_equal :date
1779
1797
  @db.schema(:datetime).first.last[:type].must_equal :datetime
1798
+ @db.schema(:smalldatetime).first.last[:type].must_equal :datetime
1780
1799
  @db.schema(:timestamp).first.last[:type].must_equal :datetime
1781
1800
  @db.schema(:"timestamp with time zone").first.last[:type].must_equal :datetime
1782
1801
  @db.schema(:"timestamp without time zone").first.last[:type].must_equal :datetime
1783
1802
  @db.schema(:time).first.last[:type].must_equal :time
1784
1803
  @db.schema(:"time with time zone").first.last[:type].must_equal :time
1785
1804
  @db.schema(:"time without time zone").first.last[:type].must_equal :time
1805
+ @db.schema(:bool).first.last[:type].must_equal :boolean
1786
1806
  @db.schema(:boolean).first.last[:type].must_equal :boolean
1787
1807
  @db.schema(:real).first.last[:type].must_equal :float
1788
1808
  @db.schema(:float).first.last[:type].must_equal :float
1809
+ @db.schema(:"float unsigned").first.last[:type].must_equal :float
1789
1810
  @db.schema(:double).first.last[:type].must_equal :float
1790
1811
  @db.schema(:"double(1,2)").first.last[:type].must_equal :float
1812
+ @db.schema(:"double(1,2) unsigned").first.last[:type].must_equal :float
1791
1813
  @db.schema(:"double precision").first.last[:type].must_equal :float
1792
1814
  @db.schema(:number).first.last[:type].must_equal :decimal
1793
1815
  @db.schema(:numeric).first.last[:type].must_equal :decimal
@@ -1801,6 +1823,8 @@ describe "Schema Parser" do
1801
1823
  @db.schema(:nchar).first.last[:type].must_equal :string
1802
1824
  @db.schema(:nvarchar).first.last[:type].must_equal :string
1803
1825
  @db.schema(:ntext).first.last[:type].must_equal :string
1826
+ @db.schema(:clob).first.last[:type].must_equal :string
1827
+ @db.schema(:ntext).first.last[:type].must_equal :string
1804
1828
  @db.schema(:smalldatetime).first.last[:type].must_equal :datetime
1805
1829
  @db.schema(:binary).first.last[:type].must_equal :blob
1806
1830
  @db.schema(:varbinary).first.last[:type].must_equal :blob
@@ -5,8 +5,8 @@ describe "class_table_inheritance plugin" do
5
5
  @db = Sequel.mock(:numrows=>1, :autoid=>proc{|sql| 1})
6
6
  def @db.supports_schema_parsing?() true end
7
7
  def @db.schema(table, opts={})
8
- {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
9
- :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}] ],
8
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string, :allow_null=>false}], [:kind, {:type=>:string}]],
9
+ :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer, :allow_null=>false}] ],
10
10
  :executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
11
11
  :staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
12
12
  }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
@@ -23,7 +23,9 @@ describe "class_table_inheritance plugin" do
23
23
  }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
24
24
  end
25
25
  end
26
- class ::Employee < Sequel::Model(@db)
26
+ base = Sequel::Model(@db)
27
+ base.plugin :auto_validations if @use_auto_validations
28
+ class ::Employee < base
27
29
  def _save_refresh; @values[:id] = 1 end
28
30
  def self.columns
29
31
  dataset.columns || dataset.opts[:from].first.expression.columns
@@ -114,6 +116,26 @@ describe "class_table_inheritance plugin" do
114
116
  "SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN ('Ceo'))) AS employees WHERE (id = 1) LIMIT 1"]
115
117
  end
116
118
 
119
+ describe "with auto_validations plugin" do
120
+ before(:all) do
121
+ @use_auto_validations = true
122
+ end
123
+
124
+ it "should work" do
125
+ e = Employee.new
126
+ e.valid?.must_equal false
127
+ e.errors.must_equal(:name=>["is not present"])
128
+
129
+ e = Manager.new
130
+ e.valid?.must_equal false
131
+ e.errors.must_equal(:name=>["is not present"], :num_staff=>["is not present"])
132
+
133
+ e = Executive.new
134
+ e.valid?.must_equal false
135
+ e.errors.must_equal(:name=>["is not present"], :num_staff=>["is not present"])
136
+ end
137
+ end
138
+
117
139
  it "should return rows with the current class if sti_key is nil" do
118
140
  Employee.plugin :class_table_inheritance
119
141
  Employee.dataset.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}, {:kind=>'Intern'}]).all.map{|x| x.class}.must_equal [Employee, Employee, Employee, Employee, Employee, Employee]
@@ -232,11 +254,11 @@ describe "class_table_inheritance plugin" do
232
254
  end
233
255
 
234
256
  it "should include schema for columns for tables for ancestor classes" do
235
- Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string})
236
- Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer})
237
- Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer}, :num_managers=>{:type=>:integer})
238
- Staff.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :manager_id=>{:type=>:integer})
239
- Intern.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string})
257
+ Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string, :allow_null=>false}, :kind=>{:type=>:string})
258
+ Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string, :allow_null=>false}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer, :allow_null=>false})
259
+ Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string, :allow_null=>false}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer, :allow_null=>false}, :num_managers=>{:type=>:integer})
260
+ Staff.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string, :allow_null=>false}, :kind=>{:type=>:string}, :manager_id=>{:type=>:integer})
261
+ Intern.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string, :allow_null=>false}, :kind=>{:type=>:string})
240
262
  end
241
263
 
242
264
  it "should use the correct primary key (which should have the same name in all subclasses)" do
@@ -1,6 +1,6 @@
1
1
  require_relative "spec_helper"
2
2
 
3
- if (RUBY_VERSION >= '2.0.0' && RUBY_ENGINE == 'ruby') # || (RUBY_VERSION >= '2.3.0' && RUBY_ENGINE == 'jruby')
3
+ if (RUBY_VERSION >= '2.0.0' && RUBY_ENGINE == 'ruby') || (RUBY_ENGINE == 'jruby' && (JRUBY_VERSION >= '9.3' || (JRUBY_VERSION.match(/\A9\.2\.(\d+)/) && $1.to_i >= 7)))
4
4
  Sequel.extension :core_refinements, :pg_array, :pg_hstore, :pg_row, :pg_range, :pg_row_ops, :pg_range_ops, :pg_array_ops, :pg_hstore_ops, :pg_json, :pg_json_ops
5
5
  using Sequel::CoreRefinements
6
6
 
@@ -22,6 +22,28 @@ describe Sequel::Model, "hook_class_methods plugin" do
22
22
  hooks.values.all?(&:frozen?).must_equal true
23
23
  end
24
24
 
25
+ deprecated ".hook_blocks method should yield each hook block" do
26
+ c = model_class.call Sequel::Model
27
+ a = []
28
+ c.hook_blocks(:before_save){|b| a << b}
29
+ a.must_equal []
30
+
31
+ pr = proc{adds << 'hi'}
32
+ c.before_save(&pr)
33
+ a = []
34
+ c.hook_blocks(:before_save){|b| a << b}
35
+ a.must_equal [pr]
36
+
37
+ c.before_save(&pr)
38
+ a = []
39
+ c.hook_blocks(:before_save){|b| a << b}
40
+ a.must_equal [pr, pr]
41
+
42
+ a = []
43
+ c.hook_blocks(:after_save){|b| a << b}
44
+ a.must_equal []
45
+ end
46
+
25
47
  it "should be definable using a block" do
26
48
  adds = []
27
49
  c = model_class.call Sequel::Model do
@@ -0,0 +1,103 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "insert_conflict plugin" do
4
+ def model_class(adapter)
5
+ db = Sequel.mock(:host=>adapter, :fetch=>{:id=>1, :s=>2}, :autoid=>1)
6
+ db.extend_datasets{def quote_identifiers?; false end}
7
+ model = Class.new(Sequel::Model)
8
+ model.dataset = db[:t]
9
+ model.columns :id, :s, :o
10
+ model.plugin :insert_conflict
11
+ db.sqls
12
+ model
13
+ end
14
+
15
+ def model_class_plugin_first(adapter)
16
+ model = Class.new(Sequel::Model)
17
+ model.plugin :insert_conflict
18
+ model = Class.new(model)
19
+ db = Sequel.mock(:host=>adapter, :fetch=>{:id=>1, :s=>2}, :autoid=>1)
20
+ db.extend_datasets{def quote_identifiers?; false end}
21
+ model.dataset = db[:t]
22
+ model.columns :id, :s, :o
23
+ db.sqls
24
+ model
25
+ end
26
+
27
+ it "should use INSERT ON CONFLICT when inserting on PostgreSQL" do
28
+ model = model_class(:postgres)
29
+ model.new(:s=>'A', :o=>1).insert_conflict.save
30
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING RETURNING *"]
31
+
32
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
33
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o RETURNING *"]
34
+ end
35
+
36
+ it "should use INSERT ON CONFLICT when inserting on SQLITE" do
37
+ model = model_class(:sqlite)
38
+ model.new(:s=>'A', :o=>1).insert_conflict.save
39
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING",
40
+ "SELECT * FROM t WHERE (id = 1) LIMIT 1"]
41
+
42
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
43
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o",
44
+ "SELECT * FROM t WHERE (id = 2) LIMIT 1"]
45
+ end
46
+
47
+ it "should raise Error if calling insert_conflict on a model instance that isn't new" do
48
+ m = model_class(:postgres).load(:s=>'A', :o=>1)
49
+ proc{m.insert_conflict}.must_raise Sequel::Error
50
+ end
51
+
52
+ it "should raise if loading plugin into a model class with a dataset that doesn't support insert_conflict" do
53
+ model = Class.new(Sequel::Model)
54
+ model.dataset = Sequel.mock[:t]
55
+ proc{model.plugin :insert_conflict}.must_raise Sequel::Error
56
+ end
57
+
58
+ it "should work if loading into a model class without a dataset on PostgreSQL" do
59
+ model = model_class_plugin_first(:postgres)
60
+ model.new(:s=>'A', :o=>1).insert_conflict.save
61
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING RETURNING *"]
62
+
63
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
64
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o RETURNING *"]
65
+ end
66
+
67
+ it "should work if loading into a model class without a dataset on SQLITE" do
68
+ model = model_class_plugin_first(:sqlite)
69
+ model.new(:s=>'A', :o=>1).insert_conflict.save
70
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING",
71
+ "SELECT * FROM t WHERE (id = 1) LIMIT 1"]
72
+
73
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
74
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o",
75
+ "SELECT * FROM t WHERE (id = 2) LIMIT 1"]
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
103
+ end