sequel 3.4.0 → 3.5.0

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