sequel 5.19.0 → 5.24.0

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