sequel 3.4.0 → 3.5.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 (93) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +1 -1
  3. data/doc/cheat_sheet.rdoc +5 -2
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.5.0.txt +510 -0
  6. data/lib/sequel/adapters/ado.rb +3 -1
  7. data/lib/sequel/adapters/ado/mssql.rb +2 -2
  8. data/lib/sequel/adapters/do.rb +2 -11
  9. data/lib/sequel/adapters/do/mysql.rb +7 -0
  10. data/lib/sequel/adapters/do/postgres.rb +2 -2
  11. data/lib/sequel/adapters/firebird.rb +3 -3
  12. data/lib/sequel/adapters/informix.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +3 -3
  14. data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
  15. data/lib/sequel/adapters/mysql.rb +60 -21
  16. data/lib/sequel/adapters/odbc.rb +1 -1
  17. data/lib/sequel/adapters/openbase.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -5
  19. data/lib/sequel/adapters/postgres.rb +3 -3
  20. data/lib/sequel/adapters/shared/mssql.rb +142 -33
  21. data/lib/sequel/adapters/shared/mysql.rb +54 -31
  22. data/lib/sequel/adapters/shared/oracle.rb +17 -6
  23. data/lib/sequel/adapters/shared/postgres.rb +7 -7
  24. data/lib/sequel/adapters/shared/progress.rb +3 -3
  25. data/lib/sequel/adapters/shared/sqlite.rb +3 -17
  26. data/lib/sequel/connection_pool.rb +4 -6
  27. data/lib/sequel/core.rb +29 -113
  28. data/lib/sequel/database.rb +14 -12
  29. data/lib/sequel/dataset.rb +8 -21
  30. data/lib/sequel/dataset/convenience.rb +1 -1
  31. data/lib/sequel/dataset/graph.rb +9 -2
  32. data/lib/sequel/dataset/sql.rb +170 -104
  33. data/lib/sequel/exceptions.rb +3 -0
  34. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  35. data/lib/sequel/extensions/named_timezones.rb +61 -0
  36. data/lib/sequel/extensions/schema_dumper.rb +7 -1
  37. data/lib/sequel/extensions/sql_expr.rb +122 -0
  38. data/lib/sequel/extensions/string_date_time.rb +4 -4
  39. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  40. data/lib/sequel/model/associations.rb +105 -45
  41. data/lib/sequel/model/base.rb +37 -28
  42. data/lib/sequel/plugins/active_model.rb +35 -0
  43. data/lib/sequel/plugins/association_dependencies.rb +96 -0
  44. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  45. data/lib/sequel/plugins/force_encoding.rb +61 -0
  46. data/lib/sequel/plugins/many_through_many.rb +32 -11
  47. data/lib/sequel/plugins/nested_attributes.rb +7 -2
  48. data/lib/sequel/plugins/subclasses.rb +45 -0
  49. data/lib/sequel/plugins/touch.rb +118 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +61 -0
  51. data/lib/sequel/sql.rb +31 -30
  52. data/lib/sequel/timezones.rb +161 -0
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/mssql_spec.rb +262 -0
  55. data/spec/adapters/mysql_spec.rb +46 -8
  56. data/spec/adapters/postgres_spec.rb +6 -3
  57. data/spec/adapters/spec_helper.rb +21 -0
  58. data/spec/adapters/sqlite_spec.rb +1 -1
  59. data/spec/core/connection_pool_spec.rb +1 -1
  60. data/spec/core/database_spec.rb +27 -1
  61. data/spec/core/dataset_spec.rb +63 -1
  62. data/spec/core/object_graph_spec.rb +1 -1
  63. data/spec/core/schema_spec.rb +1 -0
  64. data/spec/extensions/active_model_spec.rb +47 -0
  65. data/spec/extensions/association_dependencies_spec.rb +108 -0
  66. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  67. data/spec/extensions/force_encoding_spec.rb +75 -0
  68. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  69. data/spec/extensions/many_through_many_spec.rb +60 -2
  70. data/spec/extensions/named_timezones_spec.rb +72 -0
  71. data/spec/extensions/nested_attributes_spec.rb +29 -1
  72. data/spec/extensions/schema_dumper_spec.rb +10 -0
  73. data/spec/extensions/spec_helper.rb +1 -1
  74. data/spec/extensions/sql_expr_spec.rb +89 -0
  75. data/spec/extensions/subclasses_spec.rb +52 -0
  76. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  77. data/spec/extensions/touch_spec.rb +155 -0
  78. data/spec/extensions/typecast_on_load_spec.rb +60 -0
  79. data/spec/integration/database_test.rb +8 -0
  80. data/spec/integration/dataset_test.rb +9 -9
  81. data/spec/integration/plugin_test.rb +139 -0
  82. data/spec/integration/schema_test.rb +7 -7
  83. data/spec/integration/spec_helper.rb +32 -1
  84. data/spec/integration/timezone_test.rb +3 -3
  85. data/spec/integration/transaction_test.rb +1 -1
  86. data/spec/integration/type_test.rb +6 -6
  87. data/spec/model/association_reflection_spec.rb +18 -0
  88. data/spec/model/associations_spec.rb +169 -9
  89. data/spec/model/base_spec.rb +2 -0
  90. data/spec/model/eager_loading_spec.rb +82 -2
  91. data/spec/model/model_spec.rb +8 -1
  92. data/spec/model/record_spec.rb +52 -9
  93. metadata +33 -23
@@ -0,0 +1,52 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "Subclasses plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model)
6
+ @c.plugin :subclasses
7
+ end
8
+
9
+ specify "#subclasses should record direct subclasses of the given model" do
10
+ @c.subclasses.should == []
11
+
12
+ sc1 = Class.new(@c)
13
+ @c.subclasses.should == [sc1]
14
+ sc1.subclasses.should == []
15
+
16
+ sc2 = Class.new(@c)
17
+ @c.subclasses.should == [sc1, sc2]
18
+ sc1.subclasses.should == []
19
+ sc2.subclasses.should == []
20
+
21
+ ssc1 = Class.new(sc1)
22
+ @c.subclasses.should == [sc1, sc2]
23
+ sc1.subclasses.should == [ssc1]
24
+ sc2.subclasses.should == []
25
+ end
26
+
27
+ specify "#descendents should record all descendent subclasses of the given model" do
28
+ @c.descendents.should == []
29
+
30
+ sc1 = Class.new(@c)
31
+ @c.descendents.should == [sc1]
32
+ sc1.descendents.should == []
33
+
34
+ sc2 = Class.new(@c)
35
+ @c.descendents.should == [sc1, sc2]
36
+ sc1.descendents.should == []
37
+ sc2.descendents.should == []
38
+
39
+ ssc1 = Class.new(sc1)
40
+ @c.descendents.should == [sc1, ssc1, sc2]
41
+ sc1.descendents.should == [ssc1]
42
+ sc2.descendents.should == []
43
+ ssc1.descendents.should == []
44
+
45
+ sssc1 = Class.new(ssc1)
46
+ @c.descendents.should == [sc1, ssc1, sssc1, sc2]
47
+ sc1.descendents.should == [ssc1, sssc1]
48
+ sc2.descendents.should == []
49
+ ssc1.descendents.should == [sssc1]
50
+ sssc1.descendents.should == []
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Sequel thread_local_timezones extension" do
4
+ after do
5
+ Sequel.default_timezone = nil
6
+ Sequel.thread_application_timezone = nil
7
+ Sequel.thread_database_timezone = nil
8
+ Sequel.thread_typecast_timezone = nil
9
+ end
10
+
11
+ it "should allow specifying thread local timezones via thread_*_timezone=" do
12
+ proc{Sequel.thread_application_timezone = :local}.should_not raise_error
13
+ proc{Sequel.thread_database_timezone = :utc}.should_not raise_error
14
+ proc{Sequel.thread_typecast_timezone = nil}.should_not raise_error
15
+ end
16
+
17
+ it "should use thread local timezone if available" do
18
+ Sequel.thread_application_timezone = :local
19
+ Sequel.application_timezone.should == :local
20
+ Sequel.thread_database_timezone = :utc
21
+ Sequel.database_timezone.should == :utc
22
+ Sequel.thread_typecast_timezone = nil
23
+ Sequel.typecast_timezone.should == nil
24
+ end
25
+
26
+ it "should fallback to default timezone if no thread_local timezone" do
27
+ Sequel.default_timezone = :utc
28
+ Sequel.application_timezone.should == :utc
29
+ Sequel.database_timezone.should == :utc
30
+ Sequel.typecast_timezone.should == :utc
31
+ end
32
+
33
+ it "should use a nil thread_local_timezone if set instead of falling back to the default timezone if thread_local_timezone is set to :nil" do
34
+ Sequel.typecast_timezone = :utc
35
+ Sequel.thread_typecast_timezone = nil
36
+ Sequel.typecast_timezone.should == :utc
37
+ Sequel.thread_typecast_timezone = :nil
38
+ Sequel.typecast_timezone.should == nil
39
+ end
40
+
41
+ it "should be thread safe" do
42
+ [Thread.new{Sequel.thread_application_timezone = :utc; sleep 0.03; Sequel.application_timezone.should == :utc},
43
+ Thread.new{sleep 0.01; Sequel.thread_application_timezone = :local; sleep 0.01; Sequel.application_timezone.should == :local}].each{|x| x.join}
44
+ end
45
+ end
@@ -0,0 +1,155 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Touch plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model) do
6
+ def _refresh(*); end
7
+ end
8
+ p = proc{def touch_instance_value; touch_association_value; end}
9
+ @Artist = Class.new(@c, &p).set_dataset(:artists)
10
+ @Album = Class.new(@c, &p).set_dataset(:albums)
11
+
12
+ @Artist.columns :id, :updated_at, :modified_on
13
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
14
+
15
+ @Album.columns :id, :updated_at, :modified_on, :artist_id, :original_album_id
16
+ @Album.one_to_many :followup_albums, :class=>@Album, :key=>:original_album_id
17
+ @Album.many_to_one :artist, :class=>@Artist
18
+
19
+ @a = @Artist.load(:id=>1)
20
+ MODEL_DB.reset
21
+ end
22
+
23
+ specify "should default to using Time.now when setting the column values for model instances" do
24
+ c = Class.new(Sequel::Model).set_dataset(:a)
25
+ c.plugin :touch
26
+ c.columns :id, :updated_at
27
+ c.load(:id=>1).touch
28
+ MODEL_DB.sqls.first.should =~ /UPDATE a SET updated_at = '[-0-9 :.]+' WHERE \(id = 1\)/
29
+ end
30
+
31
+ specify "should allow #touch instance method for updating the updated_at column" do
32
+ @Artist.plugin :touch
33
+ @a.touch
34
+ MODEL_DB.sqls.should == ["UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (id = 1)"]
35
+ end
36
+
37
+ specify "should have #touch take an argument for the column to touch" do
38
+ @Artist.plugin :touch
39
+ @a.touch(:modified_on)
40
+ MODEL_DB.sqls.should == ["UPDATE artists SET modified_on = CURRENT_TIMESTAMP WHERE (id = 1)"]
41
+ end
42
+
43
+ specify "should be able to specify the default column to touch in the plugin call using the :column option" do
44
+ @Artist.plugin :touch, :column=>:modified_on
45
+ @a.touch
46
+ MODEL_DB.sqls.should == ["UPDATE artists SET modified_on = CURRENT_TIMESTAMP WHERE (id = 1)"]
47
+ end
48
+
49
+ specify "should be able to specify the default column to touch using the touch_column model accessor" do
50
+ @Artist.plugin :touch
51
+ @Artist.touch_column = :modified_on
52
+ @a.touch
53
+ MODEL_DB.sqls.should == ["UPDATE artists SET modified_on = CURRENT_TIMESTAMP WHERE (id = 1)"]
54
+ end
55
+
56
+ specify "should be able to specify the associations to touch in the plugin call using the :associations option" do
57
+ @Artist.plugin :touch, :associations=>:albums
58
+ @a.touch
59
+ MODEL_DB.sqls.should == ["UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (id = 1)",
60
+ "UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE (albums.artist_id = 1)"]
61
+ end
62
+
63
+ specify "should be able to give an array to the :associations option specifying multiple associations" do
64
+ @Album.plugin :touch, :associations=>[:artist, :followup_albums]
65
+ @Album.load(:id=>4, :artist_id=>1).touch
66
+ MODEL_DB.sqls.shift.should == "UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE (id = 4)"
67
+ MODEL_DB.sqls.sort.should == ["UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE (albums.original_album_id = 4)",
68
+ "UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (artists.id = 1)"]
69
+ end
70
+
71
+ specify "should be able to give a hash to the :associations option specifying the column to use for each association" do
72
+ @Artist.plugin :touch, :associations=>{:albums=>:modified_on}
73
+ @a.touch
74
+ MODEL_DB.sqls.should == ["UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (id = 1)",
75
+ "UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.artist_id = 1)"]
76
+ end
77
+
78
+ specify "should default to using the touch_column as the default touch column for associations" do
79
+ @Artist.plugin :touch, :column=>:modified_on, :associations=>:albums
80
+ @a.touch
81
+ MODEL_DB.sqls.should == ["UPDATE artists SET modified_on = CURRENT_TIMESTAMP WHERE (id = 1)",
82
+ "UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.artist_id = 1)"]
83
+ end
84
+
85
+ specify "should allow the mixed use of symbols and hashes inside an array for the :associations option" do
86
+ @Album.plugin :touch, :associations=>[:artist, {:followup_albums=>:modified_on}]
87
+ @Album.load(:id=>4, :artist_id=>1).touch
88
+ MODEL_DB.sqls.shift.should == "UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE (id = 4)"
89
+ MODEL_DB.sqls.sort.should == ["UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.original_album_id = 4)",
90
+ "UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (artists.id = 1)"]
91
+ end
92
+
93
+ specify "should be able to specify the associations to touch via a touch_associations_method" do
94
+ @Album.plugin :touch
95
+ @Album.touch_associations(:artist, {:followup_albums=>:modified_on})
96
+ @Album.load(:id=>4, :artist_id=>1).touch
97
+ MODEL_DB.sqls.shift.should == "UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE (id = 4)"
98
+ MODEL_DB.sqls.sort.should == ["UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.original_album_id = 4)",
99
+ "UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (artists.id = 1)"]
100
+ end
101
+
102
+ specify "should touch associated objects when destroying an object" do
103
+ @Album.plugin :touch
104
+ @Album.touch_associations(:artist, {:followup_albums=>:modified_on})
105
+ @Album.load(:id=>4, :artist_id=>1).destroy
106
+ MODEL_DB.sqls.shift.should == "DELETE FROM albums WHERE (id = 4)"
107
+ MODEL_DB.sqls.sort.should == ["UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.original_album_id = 4)",
108
+ "UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (artists.id = 1)"]
109
+ end
110
+
111
+ specify "should not update a column that doesn't exist" do
112
+ @Album.plugin :touch, :column=>:x
113
+ a = @Album.load(:id=>1)
114
+ a.touch
115
+ MODEL_DB.sqls.should == []
116
+ a.artist_id = 1
117
+ a.touch
118
+ MODEL_DB.sqls.should == ['UPDATE albums SET artist_id = 1 WHERE (id = 1)']
119
+ end
120
+
121
+ specify "should raise an error if given a column argument in touch that doesn't exist" do
122
+ @Artist.plugin :touch
123
+ proc{@a.touch(:x)}.should raise_error(Sequel::Error)
124
+ end
125
+
126
+ specify "should raise an Error when a nonexistent association is given" do
127
+ @Artist.plugin :touch
128
+ proc{@Artist.plugin :touch, :associations=>:blah}.should raise_error(Sequel::Error)
129
+ end
130
+
131
+ specify "should work correctly in subclasses" do
132
+ @Artist.plugin :touch
133
+ c1 = Class.new(@Artist)
134
+ c1.load(:id=>4).touch
135
+ MODEL_DB.sqls.should == ["UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (id = 4)"]
136
+ MODEL_DB.reset
137
+
138
+ c1.touch_column = :modified_on
139
+ c1.touch_associations :albums
140
+ c1.load(:id=>1).touch
141
+ MODEL_DB.sqls.should == ["UPDATE artists SET modified_on = CURRENT_TIMESTAMP WHERE (id = 1)",
142
+ "UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.artist_id = 1)"]
143
+ MODEL_DB.reset
144
+
145
+ @a.touch
146
+ MODEL_DB.sqls.should == ["UPDATE artists SET updated_at = CURRENT_TIMESTAMP WHERE (id = 1)"]
147
+ MODEL_DB.reset
148
+
149
+ @Artist.plugin :touch, :column=>:modified_on, :associations=>:albums
150
+ c2 = Class.new(@Artist)
151
+ c2.load(:id=>4).touch
152
+ MODEL_DB.sqls.should == ["UPDATE artists SET modified_on = CURRENT_TIMESTAMP WHERE (id = 4)",
153
+ "UPDATE albums SET modified_on = CURRENT_TIMESTAMP WHERE (albums.artist_id = 4)"]
154
+ end
155
+ end
@@ -0,0 +1,60 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe Sequel::Model, "TypecastOnLoad plugin" do
4
+ before do
5
+ @db = Sequel::Database.new({})
6
+ def @db.schema(*args)
7
+ [[:id, {}], [:y, {:type=>:boolean, :db_type=>'tinyint(1)'}], [:b, {:type=>:integer, :db_type=>'integer'}]]
8
+ end
9
+ @c = Class.new(Sequel::Model(@db[:items])) do
10
+ attr_accessor :bset
11
+ def b=(x)
12
+ self.bset = true
13
+ super
14
+ end
15
+ end
16
+ @c.instance_eval do
17
+ @columns = [:id, :b, :y]
18
+ def columns; @columns; end
19
+ end
20
+ end
21
+
22
+ specify "should call setter method with value when loading the object, for all given columns" do
23
+ @c.plugin :typecast_on_load, :b
24
+ o = @c.load(:id=>1, :b=>"1", :y=>"0")
25
+ o.values.should == {:id=>1, :b=>1, :y=>"0"}
26
+ o.bset.should == true
27
+ end
28
+
29
+ specify "should allowing setting columns separately via add_typecast_on_load_columns" do
30
+ @c.plugin :typecast_on_load
31
+ @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>"1", :y=>"0"}
32
+ @c.add_typecast_on_load_columns :b
33
+ @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>1, :y=>"0"}
34
+ @c.add_typecast_on_load_columns :y
35
+ @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>1, :y=>false}
36
+ end
37
+
38
+ specify "should work with subclasses" do
39
+ @c.plugin :typecast_on_load
40
+ @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>"1", :y=>"0"}
41
+
42
+ c1 = Class.new(@c)
43
+ @c.add_typecast_on_load_columns :b
44
+ @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>1, :y=>"0"}
45
+ c1.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>"1", :y=>"0"}
46
+
47
+ c2 = Class.new(@c)
48
+ @c.add_typecast_on_load_columns :y
49
+ @c.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>1, :y=>false}
50
+ c2.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>1, :y=>"0"}
51
+
52
+ c1.add_typecast_on_load_columns :y
53
+ c1.load(:id=>1, :b=>"1", :y=>"0").values.should == {:id=>1, :b=>"1", :y=>false}
54
+ end
55
+
56
+ specify "should not mark the object as modified" do
57
+ @c.plugin :typecast_on_load, :b
58
+ @c.load(:id=>1, :b=>"1", :y=>"0").modified?.should == false
59
+ end
60
+ end
@@ -12,6 +12,14 @@ describe Sequel::Database do
12
12
  proc{INTEGRATION_DB << "SELECT"}.should raise_error(Sequel::DatabaseError)
13
13
  end
14
14
 
15
+ specify "should store underlying wrapped exception in Sequel::DatabaseError" do
16
+ begin
17
+ INTEGRATION_DB << "SELECT"
18
+ rescue Sequel::DatabaseError=>e
19
+ e.wrapped_exception.should be_a_kind_of(Exception)
20
+ end
21
+ end
22
+
15
23
  specify "should not have the connection pool swallow non-StandardError based exceptions" do
16
24
  proc{INTEGRATION_DB.pool.hold{raise Interrupt, "test"}}.should raise_error(Interrupt)
17
25
  end
@@ -23,7 +23,7 @@ describe "Simple Dataset operations" do
23
23
  {:id => 3, :number=>30} ]
24
24
  end
25
25
 
26
- specify "should insert with a primary key specified" do
26
+ cspecify "should insert with a primary key specified", :mssql do
27
27
  @ds.insert(:id=>100, :number=>20)
28
28
  sqls_should_be(/INSERT INTO items \((number, id|id, number)\) VALUES \((100, 20|20, 100)\)/)
29
29
  @ds.count.should == 2
@@ -74,7 +74,7 @@ describe "Simple Dataset operations" do
74
74
  @ds.order(:id).limit(2, 1).all.should == [{:id=>2, :number=>20}]
75
75
  end
76
76
 
77
- specify "should fetch correctly with a limit and offset without an order" do
77
+ cspecify "should fetch correctly with a limit and offset without an order", :mssql do
78
78
  @ds.limit(2, 1).all.should == []
79
79
  end
80
80
 
@@ -244,7 +244,7 @@ describe "Simple Dataset operations in transactions" do
244
244
  INTEGRATION_DB.drop_table(:items_insert_in_transaction)
245
245
  end
246
246
 
247
- specify "should insert correctly with a primary key specified inside a transaction" do
247
+ cspecify "should insert correctly with a primary key specified inside a transaction", :mssql do
248
248
  INTEGRATION_DB.transaction do
249
249
  @ds.insert(:id=>100, :number=>20)
250
250
  sqls_should_be('BEGIN', /INSERT INTO items_insert_in_transaction \((number, id|id, number)\) VALUES \((100, 20|20, 100)\)/)
@@ -284,7 +284,7 @@ describe "Dataset UNION, EXCEPT, and INTERSECT" do
284
284
  end
285
285
  end
286
286
 
287
- specify "should give the correct results for UNION, EXCEPT, and INTERSECT when used with ordering and limits" do
287
+ cspecify "should give the correct results for UNION, EXCEPT, and INTERSECT when used with ordering and limits", :mssql do
288
288
  @ds1.insert(:number=>8)
289
289
  @ds2.insert(:number=>9)
290
290
  @ds1.insert(:number=>38)
@@ -400,14 +400,14 @@ if INTEGRATION_DB.dataset.supports_window_functions?
400
400
  [{:rank=>1, :id=>1}, {:rank=>2, :id=>2}, {:rank=>3, :id=>3}, {:rank=>4, :id=>4}, {:rank=>5, :id=>5}, {:rank=>6, :id=>6}]
401
401
  end
402
402
 
403
- specify "should give correct results for aggregate window functions with orders" do
403
+ cspecify "should give correct results for aggregate window functions with orders", :mssql do
404
404
  @ds.select(:id){sum(:over, :args=>amount, :partition=>group_id, :order=>id){}.as(:sum)}.all.should ==
405
405
  [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
406
406
  @ds.select(:id){sum(:over, :args=>amount, :order=>id){}.as(:sum)}.all.should ==
407
407
  [{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
408
408
  end
409
409
 
410
- specify "should give correct results for aggregate window functions with frames" do
410
+ cspecify "should give correct results for aggregate window functions with frames", :mssql do
411
411
  @ds.select(:id){sum(:over, :args=>amount, :partition=>group_id, :order=>id, :frame=>:all){}.as(:sum)}.all.should ==
412
412
  [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
413
413
  @ds.select(:id){sum(:over, :args=>amount, :partition=>group_id, :frame=>:all){}.as(:sum)}.all.should ==
@@ -449,19 +449,19 @@ describe Sequel::SQL::Constants do
449
449
  @db.drop_table(:constants)
450
450
  end
451
451
 
452
- it "should have working CURRENT_DATE" do
452
+ cspecify "should have working CURRENT_DATE", [:odbc, :mssql], [:jdbc, :sqlite] do
453
453
  @db.create_table!(:constants){Date :d}
454
454
  @ds.insert(:d=>Sequel::CURRENT_DATE)
455
455
  Date.today.should == @c2[@ds.get(:d)]
456
456
  end
457
457
 
458
- it "should have working CURRENT_TIME" do
458
+ cspecify "should have working CURRENT_TIME", [:do, :mysql], [:jdbc, :sqlite] do
459
459
  @db.create_table!(:constants){Time :t, :only_time=>true}
460
460
  @ds.insert(:t=>Sequel::CURRENT_TIME)
461
461
  (Time.now - @c[@ds.get(:t)]).should be_close(0, 1)
462
462
  end
463
463
 
464
- it "should have working CURRENT_TIMESTAMP" do
464
+ cspecify "should have working CURRENT_TIMESTAMP", [:jdbc, :sqlite] do
465
465
  @db.create_table!(:constants){DateTime :ts}
466
466
  @ds.insert(:ts=>Sequel::CURRENT_TIMESTAMP)
467
467
  (Time.now - @c[@ds.get(:ts)]).should be_close(0, 1)
@@ -0,0 +1,139 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ # H2 and MSSQL don't support USING joins
4
+ unless [:h2, :mssql].include?(INTEGRATION_DB.database_type)
5
+ describe "Class Table Inheritance Plugin" do
6
+ before do
7
+ @db = INTEGRATION_DB
8
+ @db.instance_variable_set(:@schemas, {})
9
+ @db.create_table!(:employees) do
10
+ primary_key :id
11
+ String :name
12
+ String :kind
13
+ end
14
+ @db.create_table!(:managers) do
15
+ foreign_key :id, :employees, :primary_key=>true
16
+ Integer :num_staff
17
+ end
18
+ @db.create_table!(:executives) do
19
+ foreign_key :id, :managers, :primary_key=>true
20
+ Integer :num_managers
21
+ end
22
+ @db.create_table!(:staff) do
23
+ foreign_key :id, :employees, :primary_key=>true
24
+ foreign_key :manager_id, :managers
25
+ end
26
+ class ::Employee < Sequel::Model(@db)
27
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
28
+ end
29
+ class ::Manager < Employee
30
+ one_to_many :staff_members, :class=>:Staff
31
+ end
32
+ class ::Executive < Manager
33
+ end
34
+ class ::Staff < Employee
35
+ many_to_one :manager
36
+ end
37
+
38
+ @i1 =@db[:employees].insert(:name=>'E', :kind=>'Employee')
39
+ @i2 = @db[:employees].insert(:name=>'S', :kind=>'Staff')
40
+ @i3 = @db[:employees].insert(:name=>'M', :kind=>'Manager')
41
+ @i4 = @db[:employees].insert(:name=>'Ex', :kind=>'Executive')
42
+ @db[:managers].insert(:id=>@i3, :num_staff=>7)
43
+ @db[:managers].insert(:id=>@i4, :num_staff=>5)
44
+ @db[:executives].insert(:id=>@i4, :num_managers=>6)
45
+ @db[:staff].insert(:id=>@i2, :manager_id=>@i4)
46
+
47
+ clear_sqls
48
+ end
49
+ after do
50
+ @db.drop_table :executives, :managers, :staff, :employees
51
+ [:Executive, :Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s)}
52
+ end
53
+
54
+ specify "should return rows as subclass instances" do
55
+ Employee.order(:id).all.should == [
56
+ Employee.load(:id=>@i1, :name=>'E', :kind=>'Employee'),
57
+ Staff.load(:id=>@i2, :name=>'S', :kind=>'Staff'),
58
+ Manager.load(:id=>@i3, :name=>'M', :kind=>'Manager'),
59
+ Executive.load(:id=>@i4, :name=>'Ex', :kind=>'Executive')
60
+ ]
61
+ end
62
+
63
+ specify "should lazily load columns in subclass tables" do
64
+ a = Employee.order(:id).all
65
+ a[1][:manager_id].should == nil
66
+ a[1].manager_id.should == @i4
67
+ end
68
+
69
+ specify "should include schema for columns for tables for ancestor classes" do
70
+ Employee.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :name]
71
+ Staff.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :manager_id, :name]
72
+ Manager.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :name, :num_staff]
73
+ Executive.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :name, :num_managers, :num_staff]
74
+ end
75
+
76
+ specify "should include columns for tables for ancestor classes" do
77
+ Employee.columns.should == [:id, :name, :kind]
78
+ Staff.columns.should == [:id, :name, :kind, :manager_id]
79
+ Manager.columns.should == [:id, :name, :kind, :num_staff]
80
+ Executive.columns.should == [:id, :name, :kind, :num_staff, :num_managers]
81
+ end
82
+
83
+ specify "should delete rows from all tables" do
84
+ e = Executive.first
85
+ i = e.id
86
+ e.staff_members_dataset.destroy
87
+ e.destroy
88
+ @db[:executives][:id=>i].should == nil
89
+ @db[:managers][:id=>i].should == nil
90
+ @db[:employees][:id=>i].should == nil
91
+ end
92
+
93
+ # See http://www.sqlite.org/src/tktview/3338b3fa19ac4abee6c475126a2e6d9d61f26ab1
94
+ cspecify "should insert rows into all tables", :sqlite do
95
+ e = Executive.create(:name=>'Ex2', :num_managers=>8, :num_staff=>9)
96
+ i = e.id
97
+ @db[:employees][:id=>i].should == {:id=>i, :name=>'Ex2', :kind=>'Executive'}
98
+ @db[:managers][:id=>i].should == {:id=>i, :num_staff=>9}
99
+ @db[:executives][:id=>i].should == {:id=>i, :num_managers=>8}
100
+ end
101
+
102
+ specify "should update rows in all tables" do
103
+ Executive.first.update(:name=>'Ex2', :num_managers=>8, :num_staff=>9)
104
+ @db[:employees][:id=>@i4].should == {:id=>@i4, :name=>'Ex2', :kind=>'Executive'}
105
+ @db[:managers][:id=>@i4].should == {:id=>@i4, :num_staff=>9}
106
+ @db[:executives][:id=>@i4].should == {:id=>@i4, :num_managers=>8}
107
+ end
108
+
109
+ cspecify "should handle many_to_one relationships", :sqlite do
110
+ m = Staff.first.manager
111
+ m.should == Manager[@i4]
112
+ m.should be_a_kind_of(Executive)
113
+ end
114
+
115
+ cspecify "should handle eagerly loading many_to_one relationships", :sqlite do
116
+ Staff.limit(1).eager(:manager).all.map{|x| x.manager}.should == [Manager[@i4]]
117
+ end
118
+
119
+ cspecify "should handle eagerly graphing many_to_one relationships", :sqlite do
120
+ ss = Staff.eager_graph(:manager).all
121
+ ss.should == [Staff[@i2]]
122
+ ss.map{|x| x.manager}.should == [Manager[@i4]]
123
+ end
124
+
125
+ specify "should handle one_to_many relationships" do
126
+ Executive.first.staff_members.should == [Staff[@i2]]
127
+ end
128
+
129
+ specify "should handle eagerly loading one_to_many relationships" do
130
+ Executive.limit(1).eager(:staff_members).first.staff_members.should == [Staff[@i2]]
131
+ end
132
+
133
+ cspecify "should handle eagerly graphing one_to_many relationships", :sqlite do
134
+ es = Executive.limit(1).eager_graph(:staff_members).all
135
+ es.should == [Executive[@i4]]
136
+ es.map{|x| x.staff_members}.should == [[Staff[@i2]]]
137
+ end
138
+ end
139
+ end