sequel 3.26.0 → 3.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG +26 -0
  2. data/Rakefile +2 -3
  3. data/doc/mass_assignment.rdoc +54 -0
  4. data/doc/migration.rdoc +9 -533
  5. data/doc/prepared_statements.rdoc +8 -7
  6. data/doc/release_notes/3.27.0.txt +82 -0
  7. data/doc/schema_modification.rdoc +547 -0
  8. data/doc/testing.rdoc +64 -0
  9. data/lib/sequel/adapters/amalgalite.rb +4 -0
  10. data/lib/sequel/adapters/jdbc.rb +3 -1
  11. data/lib/sequel/adapters/jdbc/h2.rb +11 -5
  12. data/lib/sequel/adapters/mysql.rb +4 -122
  13. data/lib/sequel/adapters/mysql2.rb +4 -13
  14. data/lib/sequel/adapters/odbc.rb +4 -1
  15. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  16. data/lib/sequel/adapters/shared/mysql.rb +12 -0
  17. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  18. data/lib/sequel/adapters/tinytds.rb +122 -3
  19. data/lib/sequel/core.rb +4 -3
  20. data/lib/sequel/database/misc.rb +7 -10
  21. data/lib/sequel/dataset/misc.rb +1 -1
  22. data/lib/sequel/dataset/sql.rb +7 -0
  23. data/lib/sequel/model/associations.rb +2 -2
  24. data/lib/sequel/model/base.rb +60 -10
  25. data/lib/sequel/plugins/prepared_statements_safe.rb +17 -7
  26. data/lib/sequel/sql.rb +5 -0
  27. data/lib/sequel/timezones.rb +12 -3
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/mysql_spec.rb +25 -21
  30. data/spec/core/database_spec.rb +200 -0
  31. data/spec/core/dataset_spec.rb +6 -0
  32. data/spec/extensions/prepared_statements_safe_spec.rb +10 -0
  33. data/spec/extensions/schema_dumper_spec.rb +2 -2
  34. data/spec/integration/schema_test.rb +30 -1
  35. data/spec/integration/type_test.rb +10 -3
  36. data/spec/model/base_spec.rb +44 -0
  37. data/spec/model/model_spec.rb +14 -0
  38. data/spec/model/record_spec.rb +131 -12
  39. metadata +14 -4
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 26
6
+ MINOR = 27
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -956,7 +956,7 @@ describe "MySQL::Dataset#calc_found_rows" do
956
956
  end
957
957
  end
958
958
 
959
- if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc
959
+ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc or MYSQL_DB.adapter_scheme == :mysql2
960
960
  describe "MySQL Stored Procedures" do
961
961
  before do
962
962
  MYSQL_DB.create_table(:items){Integer :id; Integer :value}
@@ -977,26 +977,30 @@ if MYSQL_DB.adapter_scheme == :mysql or MYSQL_DB.adapter_scheme == :jdbc
977
977
  MYSQL_DB[:items].count.should == 0
978
978
  end
979
979
 
980
- specify "should be callable on the dataset object" do
981
- MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc(a INTEGER) BEGIN SELECT *, a AS b FROM items; END')
982
- MYSQL_DB[:items].delete
983
- @d = MYSQL_DB[:items]
984
- @d.call_sproc(:select, :test_sproc, 3).should == []
985
- @d.insert(:value=>1)
986
- @d.call_sproc(:select, :test_sproc, 4).should == [{:id=>nil, :value=>1, :b=>4}]
987
- @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
988
- @d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
989
- end
990
-
991
- specify "should be callable on the dataset object with multiple arguments" do
992
- MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc(a INTEGER, c INTEGER) BEGIN SELECT *, a AS b, c AS d FROM items; END')
993
- MYSQL_DB[:items].delete
994
- @d = MYSQL_DB[:items]
995
- @d.call_sproc(:select, :test_sproc, 3, 4).should == []
996
- @d.insert(:value=>1)
997
- @d.call_sproc(:select, :test_sproc, 4, 5).should == [{:id=>nil, :value=>1, :b=>4, :d=>5}]
998
- @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
999
- @d.call_sproc(:select, :test_sproc, 3, 4).should == [{:id=>nil, :value=>2, :b=>6, :d => 8}]
980
+ # Mysql2 doesn't support stored procedures that return result sets, probably because
981
+ # CLIENT_MULTI_RESULTS is not set.
982
+ unless MYSQL_DB.adapter_scheme == :mysql2
983
+ specify "should be callable on the dataset object" do
984
+ MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc(a INTEGER) BEGIN SELECT *, a AS b FROM items; END')
985
+ MYSQL_DB[:items].delete
986
+ @d = MYSQL_DB[:items]
987
+ @d.call_sproc(:select, :test_sproc, 3).should == []
988
+ @d.insert(:value=>1)
989
+ @d.call_sproc(:select, :test_sproc, 4).should == [{:id=>nil, :value=>1, :b=>4}]
990
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
991
+ @d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
992
+ end
993
+
994
+ specify "should be callable on the dataset object with multiple arguments" do
995
+ MYSQL_DB.execute_ddl('CREATE PROCEDURE test_sproc(a INTEGER, c INTEGER) BEGIN SELECT *, a AS b, c AS d FROM items; END')
996
+ MYSQL_DB[:items].delete
997
+ @d = MYSQL_DB[:items]
998
+ @d.call_sproc(:select, :test_sproc, 3, 4).should == []
999
+ @d.insert(:value=>1)
1000
+ @d.call_sproc(:select, :test_sproc, 4, 5).should == [{:id=>nil, :value=>1, :b=>4, :d=>5}]
1001
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
1002
+ @d.call_sproc(:select, :test_sproc, 3, 4).should == [{:id=>nil, :value=>2, :b=>6, :d => 8}]
1003
+ end
1000
1004
  end
1001
1005
 
1002
1006
  specify "should deal with nil values" do
@@ -1344,6 +1344,206 @@ describe "Database#typecast_value" do
1344
1344
  @db.typecast_value(:integer, "0x80").should == 128
1345
1345
  end
1346
1346
 
1347
+ specify "should typecast blobs as as Sequel::SQL::Blob" do
1348
+ v = @db.typecast_value(:blob, "0x013")
1349
+ v.should be_a_kind_of(Sequel::SQL::Blob)
1350
+ v.should == Sequel::SQL::Blob.new("0x013")
1351
+ @db.typecast_value(:blob, v).object_id.should == v.object_id
1352
+ end
1353
+
1354
+ specify "should typecast boolean values to true, false, or nil" do
1355
+ @db.typecast_value(:boolean, false).should be_false
1356
+ @db.typecast_value(:boolean, 0).should be_false
1357
+ @db.typecast_value(:boolean, "0").should be_false
1358
+ @db.typecast_value(:boolean, 'f').should be_false
1359
+ @db.typecast_value(:boolean, 'false').should be_false
1360
+ @db.typecast_value(:boolean, true).should be_true
1361
+ @db.typecast_value(:boolean, 1).should be_true
1362
+ @db.typecast_value(:boolean, '1').should be_true
1363
+ @db.typecast_value(:boolean, 't').should be_true
1364
+ @db.typecast_value(:boolean, 'true').should be_true
1365
+ @db.typecast_value(:boolean, '').should be_nil
1366
+ end
1367
+
1368
+ specify "should typecast date values to Date" do
1369
+ @db.typecast_value(:date, Date.today).should == Date.today
1370
+ @db.typecast_value(:date, DateTime.now).should == Date.today
1371
+ @db.typecast_value(:date, Time.now).should == Date.today
1372
+ @db.typecast_value(:date, Date.today.to_s).should == Date.today
1373
+ @db.typecast_value(:date, :year=>Date.today.year, :month=>Date.today.month, :day=>Date.today.day).should == Date.today
1374
+ end
1375
+
1376
+ specify "should typecast datetime values to Sequel.datetime_class with correct timezone handling" do
1377
+ t = Time.utc(2011, 1, 2, 3, 4, 5) # UTC Time
1378
+ t2 = Time.mktime(2011, 1, 2, 3, 4, 5) # Local Time
1379
+ t3 = Time.utc(2011, 1, 2, 3, 4, 5) - (t - t2) # Local Time in UTC Time
1380
+ t4 = Time.mktime(2011, 1, 2, 3, 4, 5) + (t - t2) # UTC Time in Local Time
1381
+ dt = DateTime.civil(2011, 1, 2, 3, 4, 5)
1382
+ r1 = defined?(Rational) ? Rational(t2.utc_offset, 86400) : t2.utc_offset/86400.0
1383
+ r2 = defined?(Rational) ? Rational((t - t2).to_i, 86400) : (t - t2).to_i/86400.0
1384
+ dt2 = DateTime.civil(2011, 1, 2, 3, 4, 5, r1)
1385
+ dt3 = DateTime.civil(2011, 1, 2, 3, 4, 5) - r2
1386
+ dt4 = DateTime.civil(2011, 1, 2, 3, 4, 5, r1) + r2
1387
+
1388
+ t.should == t4
1389
+ t2.should == t3
1390
+ dt.should == dt4
1391
+ dt2.should == dt3
1392
+
1393
+ check = proc do |i, o|
1394
+ v = @db.typecast_value(:datetime, i)
1395
+ v.should == o
1396
+ if o.is_a?(Time)
1397
+ v.utc_offset.should == o.utc_offset
1398
+ else
1399
+ v.offset.should == o.offset
1400
+ end
1401
+ end
1402
+ begin
1403
+ @db.typecast_value(:datetime, dt).should == t
1404
+ @db.typecast_value(:datetime, dt2).should == t2
1405
+ @db.typecast_value(:datetime, t).should == t
1406
+ @db.typecast_value(:datetime, t2).should == t2
1407
+ @db.typecast_value(:datetime, dt.to_s).should == t
1408
+ @db.typecast_value(:datetime, dt.strftime('%F %T')).should == t2
1409
+ @db.typecast_value(:datetime, Date.civil(2011, 1, 2)).should == Time.mktime(2011, 1, 2, 0, 0, 0)
1410
+ @db.typecast_value(:datetime, :year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec).should == t2
1411
+
1412
+ Sequel.datetime_class = DateTime
1413
+ @db.typecast_value(:datetime, dt).should == dt
1414
+ @db.typecast_value(:datetime, dt2).should == dt2
1415
+ @db.typecast_value(:datetime, t).should == dt
1416
+ @db.typecast_value(:datetime, t2).should == dt2
1417
+ @db.typecast_value(:datetime, dt.to_s).should == dt
1418
+ @db.typecast_value(:datetime, dt.strftime('%F %T')).should == dt
1419
+ @db.typecast_value(:datetime, Date.civil(2011, 1, 2)).should == DateTime.civil(2011, 1, 2, 0, 0, 0)
1420
+ @db.typecast_value(:datetime, :year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec).should == dt
1421
+
1422
+ Sequel.application_timezone = :utc
1423
+ Sequel.typecast_timezone = :local
1424
+ Sequel.datetime_class = Time
1425
+ check[dt, t]
1426
+ check[dt2, t3]
1427
+ check[t, t]
1428
+ check[t2, t3]
1429
+ check[dt.to_s, t]
1430
+ check[dt.strftime('%F %T'), t3]
1431
+ check[Date.civil(2011, 1, 2), Time.utc(2011, 1, 2, 0, 0, 0)]
1432
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, t3]
1433
+
1434
+ Sequel.datetime_class = DateTime
1435
+ check[dt, dt]
1436
+ check[dt2, dt3]
1437
+ check[t, dt]
1438
+ check[t2, dt3]
1439
+ check[dt.to_s, dt]
1440
+ check[dt.strftime('%F %T'), dt3]
1441
+ check[Date.civil(2011, 1, 2), DateTime.civil(2011, 1, 2, 0, 0, 0)]
1442
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, dt3]
1443
+
1444
+ Sequel.typecast_timezone = :utc
1445
+ Sequel.datetime_class = Time
1446
+ check[dt, t]
1447
+ check[dt2, t3]
1448
+ check[t, t]
1449
+ check[t2, t3]
1450
+ check[dt.to_s, t]
1451
+ check[dt.strftime('%F %T'), t]
1452
+ check[Date.civil(2011, 1, 2), Time.utc(2011, 1, 2, 0, 0, 0)]
1453
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, t]
1454
+
1455
+ Sequel.datetime_class = DateTime
1456
+ check[dt, dt]
1457
+ check[dt2, dt3]
1458
+ check[t, dt]
1459
+ check[t2, dt3]
1460
+ check[dt.to_s, dt]
1461
+ check[dt.strftime('%F %T'), dt]
1462
+ check[Date.civil(2011, 1, 2), DateTime.civil(2011, 1, 2, 0, 0, 0)]
1463
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, dt]
1464
+
1465
+ Sequel.application_timezone = :local
1466
+ Sequel.datetime_class = Time
1467
+ check[dt, t4]
1468
+ check[dt2, t2]
1469
+ check[t, t4]
1470
+ check[t2, t2]
1471
+ check[dt.to_s, t4]
1472
+ check[dt.strftime('%F %T'), t4]
1473
+ check[Date.civil(2011, 1, 2), Time.local(2011, 1, 2, 0, 0, 0)]
1474
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, t4]
1475
+
1476
+ Sequel.datetime_class = DateTime
1477
+ check[dt, dt4]
1478
+ check[dt2, dt2]
1479
+ check[t, dt4]
1480
+ check[t2, dt2]
1481
+ check[dt.to_s, dt4]
1482
+ check[dt.strftime('%F %T'), dt4]
1483
+ check[Date.civil(2011, 1, 2), DateTime.civil(2011, 1, 2, 0, 0, 0, r1)]
1484
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, dt4]
1485
+
1486
+ Sequel.typecast_timezone = :local
1487
+ Sequel.datetime_class = Time
1488
+ check[dt, t4]
1489
+ check[dt2, t2]
1490
+ check[t, t4]
1491
+ check[t2, t2]
1492
+ check[dt.to_s, t4]
1493
+ check[dt.strftime('%F %T'), t2]
1494
+ check[Date.civil(2011, 1, 2), Time.local(2011, 1, 2, 0, 0, 0)]
1495
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, t2]
1496
+
1497
+ Sequel.datetime_class = DateTime
1498
+ check[dt, dt4]
1499
+ check[dt2, dt2]
1500
+ check[t, dt4]
1501
+ check[t2, dt2]
1502
+ check[dt.to_s, dt4]
1503
+ check[dt.strftime('%F %T'), dt2]
1504
+ check[Date.civil(2011, 1, 2), DateTime.civil(2011, 1, 2, 0, 0, 0, r1)]
1505
+ check[{:year=>dt.year, :month=>dt.month, :day=>dt.day, :hour=>dt.hour, :minute=>dt.min, :second=>dt.sec}, dt2]
1506
+
1507
+ ensure
1508
+ Sequel.default_timezone = nil
1509
+ Sequel.datetime_class = Time
1510
+ end
1511
+ end
1512
+
1513
+ specify "should typecast decimal values to BigDecimal" do
1514
+ [1.0, 1, '1.0', BigDecimal('1.0')].each do |i|
1515
+ v = @db.typecast_value(:decimal, i)
1516
+ v.should be_a_kind_of(BigDecimal)
1517
+ v.should == BigDecimal.new('1.0')
1518
+ end
1519
+ end
1520
+
1521
+ specify "should typecast float values to Float" do
1522
+ [1.0, 1, '1.0', BigDecimal('1.0')].each do |i|
1523
+ v = @db.typecast_value(:float, i)
1524
+ v.should be_a_kind_of(Float)
1525
+ v.should == 1.0
1526
+ end
1527
+ end
1528
+
1529
+ specify "should typecast string values to String" do
1530
+ [1.0, '1.0', '1.0'.to_sequel_blob].each do |i|
1531
+ v = @db.typecast_value(:string, i)
1532
+ v.should be_an_instance_of(String)
1533
+ v.should == "1.0"
1534
+ end
1535
+ end
1536
+
1537
+ specify "should typecast time values to SQLTime" do
1538
+ t = Time.now
1539
+ st = Sequel::SQLTime.local(t.year, t.month, t.day, 1, 2, 3)
1540
+ [st, Time.utc(t.year, t.month, t.day, 1, 2, 3), Time.local(t.year, t.month, t.day, 1, 2, 3), '01:02:03', {:hour=>1, :minute=>2, :second=>3}].each do |i|
1541
+ v = @db.typecast_value(:time, i)
1542
+ v.should be_an_instance_of(Sequel::SQLTime)
1543
+ v.should == st
1544
+ end
1545
+ end
1546
+
1347
1547
  specify "should have an underlying exception class available at wrapped_exception" do
1348
1548
  begin
1349
1549
  @db.typecast_value(:date, 'a')
@@ -1020,6 +1020,12 @@ describe "Dataset#literal" do
1020
1020
  d.literal(d).should == "(#{d.sql})"
1021
1021
  end
1022
1022
 
1023
+ specify "should literalize Sequel::SQLTime properly" do
1024
+ t = Sequel::SQLTime.now
1025
+ s = t.strftime("'%H:%M:%S")
1026
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
1027
+ end
1028
+
1023
1029
  specify "should literalize Time properly" do
1024
1030
  t = Time.now
1025
1031
  s = t.strftime("'%Y-%m-%d %H:%M:%S")
@@ -66,4 +66,14 @@ describe "prepared_statements_safe plugin" do
66
66
  @p.update(:i=>3)
67
67
  @sqls[1].should =~ /UPDATE people SET (name = 'foo'|i = 3), (name = 'foo'|i = 3) WHERE \(id = 1\)/
68
68
  end
69
+
70
+ specify "should work with abstract classes" do
71
+ c = Class.new(Sequel::Model)
72
+ c.plugin :prepared_statements_safe
73
+ c1 = Class.new(c)
74
+ c1.meta_def(:get_db_schema){@db_schema = {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}}}
75
+ c1.set_dataset(:people)
76
+ c1.prepared_statements_column_defaults.should == {:name=>'foo'}
77
+ Class.new(c1).prepared_statements_column_defaults.should == {:name=>'foo'}
78
+ end
69
79
  end
@@ -263,8 +263,8 @@ END_MIG
263
263
  s.each{|_, c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
264
264
  s
265
265
  end
266
- @d.dump_table_schema(:t4).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal.new(\"0.1005E3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.parse(\"2008-10-29\")\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n Time :c9, :default=>Time.parse(\"10:20:30\"), :only_time=>true\n String :c10\nend"
267
- @d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal.new(\"0.1005E3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.parse(\"2008-10-29\")\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n column :c9, \"time\", :default=>Time.parse(\"10:20:30\")\n column :c10, \"interval\", :default=>\"'6 weeks'\".lit\nend"
266
+ @d.dump_table_schema(:t4).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal.new(\"0.1005E3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.parse(\"2008-10-29\")\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n Time :c9, :default=>Sequel::SQLTime.parse(\"10:20:30\"), :only_time=>true\n String :c10\nend"
267
+ @d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal.new(\"0.1005E3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.parse(\"2008-10-29\")\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n column :c9, \"time\", :default=>Sequel::SQLTime.parse(\"10:20:30\")\n column :c10, \"interval\", :default=>\"'6 weeks'\".lit\nend"
268
268
  end
269
269
 
270
270
  it "should not use a '...'.lit as a fallback if using MySQL with the :same_db option" do
@@ -246,6 +246,15 @@ describe "Database schema modifiers" do
246
246
  @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:number, :id]
247
247
  @ds.columns!.should == [:number, :id]
248
248
  @ds.map(:number).should == [10]
249
+ proc{@ds.insert(:id=>@ds.map(:id).first)}.should raise_error
250
+ end
251
+
252
+ cspecify "should drop primary key constraints from tables correctly", :sqlite do
253
+ @db.create_table!(:items){Integer :number; primary_key [:number], :name=>:items_pk}
254
+ @ds.insert(:number=>10)
255
+ @db.alter_table(:items){drop_constraint :items_pk, :type=>:primary_key}
256
+ @ds.map(:number).should == [10]
257
+ proc{@ds.insert(10)}.should_not raise_error
249
258
  end
250
259
 
251
260
  specify "should add foreign key columns to tables correctly" do
@@ -320,13 +329,33 @@ describe "Database schema modifiers" do
320
329
  end
321
330
 
322
331
  cspecify "should add unique constraints and foreign key table constraints correctly", :sqlite do
323
- @db.create_table!(:items){Integer :id; Integer :item_id}
332
+ @db.create_table!(:items, :engine=>:InnoDB){Integer :id; Integer :item_id}
324
333
  @db.alter_table(:items) do
325
334
  add_unique_constraint [:item_id, :id]
326
335
  add_foreign_key [:id, :item_id], :items, :key=>[:item_id, :id]
327
336
  end
328
337
  @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:id, :item_id]
329
338
  @ds.columns!.should == [:id, :item_id]
339
+ proc{@ds.insert(1, 1)}.should_not raise_error
340
+ proc{@ds.insert(1, 1)}.should raise_error
341
+ proc{@ds.insert(1, 2)}.should raise_error
342
+ end
343
+
344
+ cspecify "should drop unique constraints and foreign key table constraints correctly", :sqlite do
345
+ @db.create_table!(:items) do
346
+ Integer :id
347
+ Integer :item_id
348
+ unique [:item_id, :id], :name=>:items_uk
349
+ foreign_key [:id, :item_id], :items, :key=>[:item_id, :id], :name=>:items_fk
350
+ end
351
+ @db.alter_table(:items) do
352
+ drop_constraint(:items_fk, :type=>:foreign_key)
353
+ drop_constraint(:items_uk, :type=>:unique)
354
+ end
355
+ @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:id, :item_id]
356
+ @ds.columns!.should == [:id, :item_id]
357
+ proc{@ds.insert(1, 2)}.should_not raise_error
358
+ proc{@ds.insert(1, 2)}.should_not raise_error
330
359
  end
331
360
 
332
361
  cspecify "should remove columns from tables correctly", :h2, :mssql do
@@ -68,11 +68,18 @@ describe "Supported types" do
68
68
  ds.first[:dat].to_s.should == d.to_s
69
69
  end
70
70
 
71
- cspecify "should support generic time type", [:do], [:swift], [:odbc], [:jdbc, :mssql], [:tinytds] do
71
+ cspecify "should support generic time type", [:do], [:swift], [:odbc], [:jdbc, :mssql], [:jdbc, :postgres], [:mysql2], [:tinytds] do
72
72
  ds = create_items_table_with_column(:tim, Time, :only_time=>true)
73
- t = Time.now
73
+ t = Sequel::SQLTime.now
74
74
  ds.insert(:tim => t)
75
- ds.first[:tim].strftime('%H%M%S').should == t.strftime('%H%M%S')
75
+ v = ds.first[:tim]
76
+ ds.literal(v).should == ds.literal(t)
77
+ v.should be_a_kind_of(Sequel::SQLTime)
78
+ ds.delete
79
+ ds.insert(:tim => v)
80
+ v2 = ds.first[:tim]
81
+ ds.literal(v2).should == ds.literal(t)
82
+ v2.should be_a_kind_of(Sequel::SQLTime)
76
83
  end
77
84
 
78
85
  cspecify "should support generic datetime type", [:do, :sqlite], [:jdbc, :sqlite] do
@@ -131,6 +131,50 @@ describe Sequel::Model, ".def_dataset_method" do
131
131
  end
132
132
  end
133
133
 
134
+ describe Sequel::Model, ".dataset_module" do
135
+ before do
136
+ @c = Class.new(Sequel::Model(:items))
137
+ end
138
+
139
+ it "should extend the dataset with the module if the model has a dataset" do
140
+ @c.instance_eval{dataset_module{def return_3() 3 end}}
141
+ @c.dataset.return_3.should == 3
142
+ end
143
+
144
+ it "should add methods defined in the module to the class" do
145
+ @c.instance_eval{dataset_module{def return_3() 3 end}}
146
+ @c.return_3.should == 3
147
+ end
148
+
149
+ it "should cache calls and readd methods if set_dataset is used" do
150
+ @c.instance_eval{dataset_module{def return_3() 3 end}}
151
+ @c.set_dataset :items
152
+ @c.return_3.should == 3
153
+ @c.dataset.return_3.should == 3
154
+ end
155
+
156
+ it "should readd methods to subclasses, if set_dataset is used in a subclass" do
157
+ @c.instance_eval{dataset_module{def return_3() 3 end}}
158
+ c = Class.new(@c)
159
+ c.set_dataset :items
160
+ c.return_3.should == 3
161
+ c.dataset.return_3.should == 3
162
+ end
163
+
164
+ it "should only have a single dataset_module per class" do
165
+ @c.instance_eval{dataset_module{def return_3() 3 end}}
166
+ @c.instance_eval{dataset_module{def return_3() 3 + (begin; super; rescue NoMethodError; 1; end) end}}
167
+ @c.return_3.should == 4
168
+ end
169
+
170
+ it "should not have subclasses share the dataset_module" do
171
+ @c.instance_eval{dataset_module{def return_3() 3 end}}
172
+ c = Class.new(@c)
173
+ c.instance_eval{dataset_module{def return_3() 3 + (begin; super; rescue NoMethodError; 1; end) end}}
174
+ c.return_3.should == 6
175
+ end
176
+ end
177
+
134
178
  describe "A model class with implicit table name" do
135
179
  before do
136
180
  class Donkey < Sequel::Model
@@ -40,6 +40,13 @@ describe "Sequel::Model()" do
40
40
  c.table_name.should == :boo
41
41
  end
42
42
 
43
+ it "should return a model subclass with the given dataset if given a dataset using an SQL::Identifier" do
44
+ ds = @db[:blah.identifier]
45
+ c = Sequel::Model(ds)
46
+ c.superclass.should == Sequel::Model
47
+ c.dataset.should == ds
48
+ end
49
+
43
50
  it "should return a model subclass associated to the given database if given a database" do
44
51
  db = Sequel::Database.new
45
52
  c = Sequel::Model(db)
@@ -98,6 +105,13 @@ describe "Sequel::Model()" do
98
105
  class ::Album < Sequel::Model(@db[:table]); end
99
106
  end.should_not raise_error
100
107
  end
108
+
109
+ it "should work without raising an exception with a dataset with an SQL::Identifier" do
110
+ proc do
111
+ class ::Album < Sequel::Model(@db[:table.identifier]); end
112
+ class ::Album < Sequel::Model(@db[:table.identifier]); end
113
+ end.should_not raise_error
114
+ end
101
115
  end
102
116
  end
103
117