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,161 @@
1
+ module Sequel
2
+ # The offset of the current time zone from UTC, in seconds.
3
+ LOCAL_DATETIME_OFFSET_SECS = Time.now.utc_offset
4
+
5
+ # The offset of the current time zone from UTC, as a fraction of a day.
6
+ LOCAL_DATETIME_OFFSET = respond_to?(:Rational, true) ? Rational(LOCAL_DATETIME_OFFSET_SECS, 60*60*24) : LOCAL_DATETIME_OFFSET_SECS/60/60/24.0
7
+
8
+ @application_timezone = nil
9
+ @database_timezone = nil
10
+ @typecast_timezone = nil
11
+
12
+ module Timezones
13
+ attr_reader :application_timezone, :database_timezone, :typecast_timezone
14
+
15
+ %w'application database typecast'.each do |t|
16
+ class_eval("def #{t}_timezone=(tz); @#{t}_timezone = convert_timezone_setter_arg(tz) end", __FILE__, __LINE__)
17
+ end
18
+
19
+ # Convert the given Time/DateTime object into the database timezone, used when
20
+ # literalizing objects in an SQL string.
21
+ def application_to_database_timestamp(v)
22
+ convert_output_timestamp(v, Sequel.database_timezone)
23
+ end
24
+
25
+ # Convert the given object into an object of Sequel.datetime_class in the
26
+ # application_timezone. Used when coverting datetime/timestamp columns
27
+ # returned by the database.
28
+ def database_to_application_timestamp(v)
29
+ convert_timestamp(v, Sequel.database_timezone)
30
+ end
31
+
32
+ # Sets the database, application, and typecasting timezones to the given timezone.
33
+ def default_timezone=(tz)
34
+ self.database_timezone = tz
35
+ self.application_timezone = tz
36
+ self.typecast_timezone = tz
37
+ end
38
+
39
+ # Convert the given object into an object of Sequel.datetime_class in the
40
+ # application_timezone. Used when typecasting values when assigning them
41
+ # to model datetime attributes.
42
+ def typecast_to_application_timestamp(v)
43
+ convert_timestamp(v, Sequel.typecast_timezone)
44
+ end
45
+
46
+ private
47
+
48
+ # Convert the given DateTime to the given input_timezone, keeping the
49
+ # same time and just modifying the timezone.
50
+ def convert_input_datetime_no_offset(v, input_timezone)
51
+ case input_timezone
52
+ when :utc
53
+ v# DateTime assumes UTC if no offset is given
54
+ when :local
55
+ v.new_offset(LOCAL_DATETIME_OFFSET) - LOCAL_DATETIME_OFFSET
56
+ else
57
+ convert_input_datetime_other(v, input_timezone)
58
+ end
59
+ end
60
+
61
+ # Convert the given DateTime to the given input_timezone that is not supported
62
+ # by default (such as nil, :local, or :utc). Raises an error by default.
63
+ # Can be overridden in extensions.
64
+ def convert_input_datetime_other(v, input_timezone)
65
+ raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}"
66
+ end
67
+
68
+ # Converts the object from a String, Array, Date, DateTime, or Time into an
69
+ # instance of Sequel.datetime_class. If given an array or a string that doesn't
70
+ # contain an offset, assume that the array/string is already in the given input_timezone.
71
+ def convert_input_timestamp(v, input_timezone)
72
+ case v
73
+ when String
74
+ v2 = Sequel.string_to_datetime(v)
75
+ if !input_timezone || Date._parse(v).has_key?(:offset)
76
+ v2
77
+ else
78
+ # Correct for potentially wrong offset if string doesn't include offset
79
+ if v2.is_a?(DateTime)
80
+ v2 = convert_input_datetime_no_offset(v2, input_timezone)
81
+ else
82
+ # Time assumes local time if no offset is given
83
+ v2 = v2.getutc + LOCAL_DATETIME_OFFSET_SECS if input_timezone == :utc
84
+ end
85
+ v2
86
+ end
87
+ when Array
88
+ y, mo, d, h, mi, s = v
89
+ if datetime_class == DateTime
90
+ convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s, 0), input_timezone)
91
+ else
92
+ Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s)
93
+ end
94
+ when Time
95
+ if datetime_class == DateTime
96
+ v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601)
97
+ else
98
+ v
99
+ end
100
+ when DateTime
101
+ if datetime_class == DateTime
102
+ v
103
+ else
104
+ v.respond_to?(:to_time) ? v.to_time : string_to_datetime(v.to_s)
105
+ end
106
+ when Date
107
+ convert_input_timestamp(v.to_s, input_timezone)
108
+ else
109
+ raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
110
+ end
111
+ end
112
+
113
+ # Convert the given DateTime to the given output_timezone that is not supported
114
+ # by default (such as nil, :local, or :utc). Raises an error by default.
115
+ # Can be overridden in extensions.
116
+ def convert_output_datetime_other(v, output_timezone)
117
+ raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}"
118
+ end
119
+
120
+ # Converts the object to the given output_timezone.
121
+ def convert_output_timestamp(v, output_timezone)
122
+ if output_timezone
123
+ if v.is_a?(DateTime)
124
+ case output_timezone
125
+ when :utc
126
+ v.new_offset(0)
127
+ when :local
128
+ v.new_offset(LOCAL_DATETIME_OFFSET)
129
+ else
130
+ convert_output_datetime_other(v, output_timezone)
131
+ end
132
+ else
133
+ v.send(output_timezone == :utc ? :getutc : :getlocal)
134
+ end
135
+ else
136
+ v
137
+ end
138
+ end
139
+
140
+ # Converts the given object from the given input timezone to the
141
+ # application timezone using convert_input_timestamp and
142
+ # convert_output_timestamp.
143
+ def convert_timestamp(v, input_timezone)
144
+ begin
145
+ convert_output_timestamp(convert_input_timestamp(v, input_timezone), Sequel.application_timezone)
146
+ rescue InvalidValue
147
+ raise
148
+ rescue => e
149
+ raise convert_exception_class(e, InvalidValue)
150
+ end
151
+ end
152
+
153
+ # Convert the timezone setter argument. Returns argument given by default,
154
+ # exists for easier overriding in extensions.
155
+ def convert_timezone_setter_arg(tz)
156
+ tz
157
+ end
158
+ end
159
+
160
+ extend Timezones
161
+ end
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  MAJOR = 3
3
- MINOR = 4
3
+ MINOR = 5
4
4
  TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join('.')
@@ -0,0 +1,262 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ unless defined?(MSSQL_DB)
4
+ MSSQL_URL = 'jdbc:sqlserver://localhost;integratedSecurity=true;database=sandbox' unless defined? MSSQL_URL
5
+ MSSQL_DB = Sequel.connect(ENV['SEQUEL_MSSQL_SPEC_DB']||MSSQL_URL)
6
+ end
7
+ INTEGRATION_DB = MSSQL_DB unless defined?(INTEGRATION_DB)
8
+
9
+ context "A MSSQL database" do
10
+ before do
11
+ @db = MSSQL_DB
12
+ end
13
+
14
+ cspecify "should be able to read fractional part of timestamp", :odbc do
15
+ rs = @db["select getutcdate() as full_date, cast(datepart(millisecond, getutcdate()) as int) as milliseconds"].first
16
+ rs[:milliseconds].should == rs[:full_date].usec/1000
17
+ end
18
+
19
+ cspecify "should be able to write fractional part of timestamp", :odbc do
20
+ t = Time.utc(2001, 12, 31, 23, 59, 59, 997000)
21
+ (t.usec/1000).should == @db["select cast(datepart(millisecond, ?) as int) as milliseconds", t].get
22
+ end
23
+
24
+ specify "should not raise an error when getting the server version" do
25
+ proc{@db.server_version}.should_not raise_error
26
+ proc{@db.dataset.server_version}.should_not raise_error
27
+ end
28
+ end
29
+
30
+ context "MSSQL Dataset#output" do
31
+ before do
32
+ @db = MSSQL_DB
33
+ @db.create_table!(:items){String :name; Integer :value}
34
+ @db.create_table!(:out){String :name; Integer :value}
35
+ @ds = @db[:items]
36
+ end
37
+ after do
38
+ @db.drop_table(:items)
39
+ @db.drop_table(:out)
40
+ end
41
+
42
+ specify "should format OUTPUT clauses for DELETE statements" do
43
+ @ds.output(:out, [:deleted__name, :deleted__value]).delete_sql.should =~
44
+ /DELETE FROM ITEMS OUTPUT DELETED.(NAME|VALUE), DELETED.(NAME|VALUE) INTO OUT/
45
+ @ds.output(:out, {:name => :deleted__name, :value => :deleted__value}).delete_sql.should =~
46
+ /DELETE FROM ITEMS OUTPUT DELETED.(NAME|VALUE), DELETED.(NAME|VALUE) INTO OUT \((NAME|VALUE), (NAME|VALUE)\)/
47
+ end
48
+
49
+ specify "should format OUTPUT clauses for INSERT statements" do
50
+ @ds.output(:out, [:inserted__name, :inserted__value]).insert_sql(:name => "name", :value => 1).should =~
51
+ /INSERT INTO ITEMS \((NAME|VALUE), (NAME|VALUE)\) OUTPUT INSERTED.(NAME|VALUE), INSERTED.(NAME|VALUE) INTO OUT VALUES \((N'name'|1), (N'name'|1)\)/
52
+ @ds.output(:out, {:name => :inserted__name, :value => :inserted__value}).insert_sql(:name => "name", :value => 1).should =~
53
+ /INSERT INTO ITEMS \((NAME|VALUE), (NAME|VALUE)\) OUTPUT INSERTED.(NAME|VALUE), INSERTED.(NAME|VALUE) INTO OUT \((NAME|VALUE), (NAME|VALUE)\) VALUES \((N'name'|1), (N'name'|1)\)/
54
+ end
55
+
56
+ specify "should format OUTPUT clauses for UPDATE statements" do
57
+ @ds.output(:out, [:inserted__name, :deleted__value]).update_sql(:value => 2).should =~
58
+ /UPDATE ITEMS SET VALUE = 2 OUTPUT (INSERTED.NAME|DELETED.VALUE), (INSERTED.NAME|DELETED.VALUE) INTO OUT/
59
+ @ds.output(:out, {:name => :inserted__name, :value => :deleted__value}).update_sql(:value => 2).should =~
60
+ /UPDATE ITEMS SET VALUE = 2 OUTPUT (INSERTED.NAME|DELETED.VALUE), (INSERTED.NAME|DELETED.VALUE) INTO OUT \((NAME|VALUE), (NAME|VALUE)\)/
61
+ end
62
+
63
+ specify "should execute OUTPUT clauses in DELETE statements" do
64
+ @ds.insert(:name => "name", :value => 1)
65
+ @ds.output(:out, [:deleted__name, :deleted__value]).delete
66
+ @db[:out].all.should == [{:name => "name", :value => 1}]
67
+ @ds.insert(:name => "name", :value => 2)
68
+ @ds.output(:out, {:name => :deleted__name, :value => :deleted__value}).delete
69
+ @db[:out].all.should == [{:name => "name", :value => 1}, {:name => "name", :value => 2}]
70
+ end
71
+
72
+ specify "should execute OUTPUT clauses in INSERT statements" do
73
+ @ds.output(:out, [:inserted__name, :inserted__value]).insert(:name => "name", :value => 1)
74
+ @db[:out].all.should == [{:name => "name", :value => 1}]
75
+ @ds.output(:out, {:name => :inserted__name, :value => :inserted__value}).insert(:name => "name", :value => 2)
76
+ @db[:out].all.should == [{:name => "name", :value => 1}, {:name => "name", :value => 2}]
77
+ end
78
+
79
+ specify "should execute OUTPUT clauses in UPDATE statements" do
80
+ @ds.insert(:name => "name", :value => 1)
81
+ @ds.output(:out, [:inserted__name, :deleted__value]).update(:value => 2)
82
+ @db[:out].all.should == [{:name => "name", :value => 1}]
83
+ @ds.output(:out, {:name => :inserted__name, :value => :deleted__value}).update(:value => 3)
84
+ @db[:out].all.should == [{:name => "name", :value => 1}, {:name => "name", :value => 2}]
85
+ end
86
+ end
87
+
88
+ context "MSSQL dataset" do
89
+ before do
90
+ @db = MSSQL_DB
91
+ @ds = MSSQL_DB[:t]
92
+ end
93
+
94
+ context "using #with and #with_recursive" do
95
+ before do
96
+ @ds1 = @ds.with(:t, @db[:x])
97
+ @ds2 = @ds.with_recursive(:t, @db[:x], @db[:t])
98
+ end
99
+
100
+ specify "should prepend UPDATE statements with WITH clause" do
101
+ @ds1.update_sql(:x => :y).should == 'WITH T AS (SELECT * FROM X) UPDATE T SET X = Y'
102
+ @ds2.update_sql(:x => :y).should == 'WITH T AS (SELECT * FROM X UNION ALL SELECT * FROM T) UPDATE T SET X = Y'
103
+ end
104
+
105
+ specify "should prepend DELETE statements with WITH clause" do
106
+ @ds1.filter(:y => 1).delete_sql.should == 'WITH T AS (SELECT * FROM X) DELETE FROM T WHERE (Y = 1)'
107
+ @ds2.filter(:y => 1).delete_sql.should == 'WITH T AS (SELECT * FROM X UNION ALL SELECT * FROM T) DELETE FROM T WHERE (Y = 1)'
108
+ end
109
+
110
+ specify "should prepend INSERT statements with WITH clause" do
111
+ @ds1.insert_sql(@db[:t]).should == 'WITH T AS (SELECT * FROM X) INSERT INTO T SELECT * FROM T'
112
+ @ds2.insert_sql(@db[:t]).should == 'WITH T AS (SELECT * FROM X UNION ALL SELECT * FROM T) INSERT INTO T SELECT * FROM T'
113
+ end
114
+
115
+ context "on #import" do
116
+ before do
117
+ @db = @db.clone
118
+ class << @db
119
+ attr_reader :sqls
120
+
121
+ def execute(sql, opts={})
122
+ @sqls ||= []
123
+ @sqls << sql
124
+ end
125
+ alias execute_dui execute
126
+
127
+ def transaction(opts={})
128
+ @sqls ||= []
129
+ @sqls << 'BEGIN'
130
+ yield
131
+ @sqls << 'COMMIT'
132
+ end
133
+ end
134
+ end
135
+
136
+ specify "should prepend INSERT statements with WITH clause" do
137
+ @db[:items].with(:items, @db[:inventory].group(:type)).import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
138
+ @db.sqls.should == [
139
+ 'BEGIN',
140
+ "WITH ITEMS AS (SELECT * FROM INVENTORY GROUP BY TYPE) INSERT INTO ITEMS (X, Y) SELECT 1, 2 UNION ALL SELECT 3, 4",
141
+ 'COMMIT',
142
+ 'BEGIN',
143
+ "WITH ITEMS AS (SELECT * FROM INVENTORY GROUP BY TYPE) INSERT INTO ITEMS (X, Y) SELECT 5, 6",
144
+ 'COMMIT'
145
+ ]
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ context "MSSQL joined datasets" do
152
+ before do
153
+ @db = MSSQL_DB
154
+ end
155
+
156
+ specify "should format DELETE statements" do
157
+ @db[:t1].inner_join(:t2, :t1__pk => :t2__pk).delete_sql.should ==
158
+ "DELETE FROM T1 FROM T1 INNER JOIN T2 ON (T1.PK = T2.PK)"
159
+ end
160
+
161
+ specify "should format UPDATE statements" do
162
+ @db[:t1].inner_join(:t2, :t1__pk => :t2__pk).update_sql(:pk => :t2__pk).should ==
163
+ "UPDATE T1 SET PK = T2.PK FROM T1 INNER JOIN T2 ON (T1.PK = T2.PK)"
164
+ end
165
+ end
166
+
167
+ describe "Offset support" do
168
+ before do
169
+ @db = MSSQL_DB
170
+ @db.create_table!(:i){Integer :id; Integer :parent_id}
171
+ @ds = @db[:i].order(:id)
172
+ @hs = []
173
+ @ds.row_proc = proc{|r| @hs << r.dup; r[:id] *= 2; r[:parent_id] *= 3; r}
174
+ @ds.import [:id, :parent_id], [[1,nil],[2,nil],[3,1],[4,1],[5,3],[6,5]]
175
+ end
176
+ after do
177
+ @db.drop_table(:i)
178
+ end
179
+
180
+ specify "should return correct rows" do
181
+ @ds.limit(2, 2).all.should == [{:id=>6, :parent_id=>3}, {:id=>8, :parent_id=>3}]
182
+ end
183
+
184
+ specify "should not include offset column in hashes passed to row_proc" do
185
+ @ds.limit(2, 2).all
186
+ @hs.should == [{:id=>3, :parent_id=>1}, {:id=>4, :parent_id=>1}]
187
+ end
188
+ end
189
+
190
+ describe "Common Table Expressions" do
191
+ before do
192
+ @db = MSSQL_DB
193
+ @db.create_table!(:i1){Integer :id; Integer :parent_id}
194
+ @db.create_table!(:i2){Integer :id; Integer :parent_id}
195
+ @ds = @db[:i1]
196
+ @ds2 = @db[:i2]
197
+ @ds.import [:id, :parent_id], [[1,nil],[2,nil],[3,1],[4,1],[5,3],[6,5]]
198
+ end
199
+ after do
200
+ @db.drop_table(:i1)
201
+ @db.drop_table(:i2)
202
+ end
203
+
204
+ specify "using #with should be able to update" do
205
+ @ds.insert(:id=>1)
206
+ @ds2.insert(:id=>2, :parent_id=>1)
207
+ @ds2.insert(:id=>3, :parent_id=>2)
208
+ @ds.with(:t, @ds2).filter(:id => @db[:t].select(:id)).update(:parent_id => @db[:t].filter(:id => :i1__id).select(:parent_id).limit(1))
209
+ @ds[:id => 1].should == {:id => 1, :parent_id => nil}
210
+ @ds[:id => 2].should == {:id => 2, :parent_id => 1}
211
+ @ds[:id => 3].should == {:id => 3, :parent_id => 2}
212
+ @ds[:id => 4].should == {:id => 4, :parent_id => 1}
213
+ end
214
+
215
+ specify "using #with_recursive should be able to update" do
216
+ ds = @ds.with_recursive(:t, @ds.filter(:parent_id=>1).or(:id => 1), @ds.join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id), :args=>[:i, :pi])
217
+ ds.filter(~{:id => @db[:t].select(:i)}).update(:parent_id => 1)
218
+ @ds[:id => 1].should == {:id => 1, :parent_id => nil}
219
+ @ds[:id => 2].should == {:id => 2, :parent_id => 1}
220
+ @ds[:id => 5].should == {:id => 5, :parent_id => 3}
221
+ end
222
+
223
+ specify "using #with should be able to insert" do
224
+ @ds2.insert(:id=>7)
225
+ @ds.with(:t, @ds2).insert(@db[:t])
226
+ @ds[:id => 7].should == {:id => 7, :parent_id => nil}
227
+ end
228
+
229
+ specify "using #with_recursive should be able to insert" do
230
+ ds = @ds2.with_recursive(:t, @ds.filter(:parent_id=>1), @ds.join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id), :args=>[:i, :pi])
231
+ ds.insert @db[:t]
232
+ @ds2.all.should == [{:id => 3, :parent_id => 1}, {:id => 4, :parent_id => 1}, {:id => 5, :parent_id => 3}, {:id => 6, :parent_id => 5}]
233
+ end
234
+
235
+ specify "using #with should be able to delete" do
236
+ @ds2.insert(:id=>6)
237
+ @ds2.insert(:id=>5)
238
+ @ds2.insert(:id=>4)
239
+ @ds.with(:t, @ds2).filter(:id => @db[:t].select(:id)).delete
240
+ @ds.all.should == [{:id => 1, :parent_id => nil}, {:id => 2, :parent_id => nil}, {:id => 3, :parent_id => 1}]
241
+ end
242
+
243
+ specify "using #with_recursive should be able to delete" do
244
+ @ds.insert(:id=>7, :parent_id=>2)
245
+ ds = @ds.with_recursive(:t, @ds.filter(:parent_id=>1), @ds.join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id), :args=>[:i, :pi])
246
+ ds.filter(:i1__id => @db[:t].select(:i)).delete
247
+ @ds.all.should == [{:id => 1, :parent_id => nil}, {:id => 2, :parent_id => nil}, {:id => 7, :parent_id => 2}]
248
+ end
249
+
250
+ specify "using #with should be able to import" do
251
+ @ds2.insert(:id=>7)
252
+ @ds.with(:t, @ds2).import [:id, :parent_id], @db[:t].select(:id, :parent_id)
253
+ @ds[:id => 7].should == {:id => 7, :parent_id => nil}
254
+ end
255
+
256
+ specify "using #with_recursive should be able to import" do
257
+ ds = @ds2.with_recursive(:t, @ds.filter(:parent_id=>1), @ds.join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id), :args=>[:i, :pi])
258
+ ds.import [:id, :parent_id], @db[:t].select(:i, :pi)
259
+ @ds2.all.should == [{:id => 3, :parent_id => 1}, {:id => 4, :parent_id => 1}, {:id => 5, :parent_id => 3}, {:id => 6, :parent_id => 5}]
260
+ end
261
+ end
262
+
@@ -449,6 +449,12 @@ context "A MySQL database" do
449
449
  @db[:items].insert(2**40)
450
450
  @db[:items].all.should == [{:id=>2**40}]
451
451
  end
452
+
453
+ specify "should have set_column_type pass through options" do
454
+ @db.create_table(:items){integer :id; enum :list, :elements=>%w[one]}
455
+ @db.alter_table(:items){set_column_type :id, :int, :unsigned=>true, :size=>8; set_column_type :list, :enum, :elements=>%w[two]}
456
+ @db.sqls.should == ["CREATE TABLE items (id integer, list enum('one'))", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id id int(8) UNSIGNED NULL", "ALTER TABLE items CHANGE COLUMN list list enum('two') NULL"]
457
+ end
452
458
 
453
459
  specify "should have set_column_default support keep existing options" do
454
460
  @db.create_table(:items){Integer :id, :null=>false, :default=>5}
@@ -479,14 +485,6 @@ context "A MySQL database" do
479
485
  @db << 'DELETE FROM items'
480
486
  @db[:items].first.should == nil
481
487
  end
482
-
483
- specify "should handle multiple select statements at once" do
484
- @db.create_table(:items){String :name; Integer :value}
485
- @db[:items].delete
486
- @db[:items].insert(:name => 'tutu', :value => 1234)
487
- @db["SELECT * FROM items; SELECT * FROM items"].all.should == \
488
- [{:name => 'tutu', :value => 1234}, {:name => 'tutu', :value => 1234}]
489
- end
490
488
  end
491
489
 
492
490
  # Socket tests should only be run if the MySQL server is on localhost
@@ -906,4 +904,44 @@ if MYSQL_DB.class.adapter_scheme == :mysql
906
904
  MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == '25:00:00'
907
905
  end
908
906
  end
907
+
908
+ context "MySQL multiple result sets" do
909
+ before do
910
+ MYSQL_DB.create_table!(:a){Integer :a}
911
+ MYSQL_DB.create_table!(:b){Integer :b}
912
+ @ds = MYSQL_DB['SELECT * FROM a; SELECT * FROM b']
913
+ MYSQL_DB[:a].insert(10)
914
+ MYSQL_DB[:a].insert(15)
915
+ MYSQL_DB[:b].insert(20)
916
+ MYSQL_DB[:b].insert(25)
917
+ end
918
+ after do
919
+ MYSQL_DB.drop_table(:a, :b)
920
+ end
921
+
922
+ specify "should combine all results by default" do
923
+ @ds.all.should == [{:a=>10}, {:a=>15}, {:b=>20}, {:b=>25}]
924
+ end
925
+
926
+ specify "should split results returned into arrays if split_multiple_result_sets is used" do
927
+ @ds.split_multiple_result_sets.all.should == [[{:a=>10}, {:a=>15}], [{:b=>20}, {:b=>25}]]
928
+ end
929
+
930
+ specify "should have regular row_procs work when splitting multiple result sets" do
931
+ @ds.row_proc = proc{|x| x[x.keys.first] *= 2; x}
932
+ @ds.split_multiple_result_sets.all.should == [[{:a=>20}, {:a=>30}], [{:b=>40}, {:b=>50}]]
933
+ end
934
+
935
+ specify "should use the columns from the first result set when splitting result sets" do
936
+ @ds.split_multiple_result_sets.columns.should == [:a]
937
+ end
938
+
939
+ specify "should not allow graphing a dataset that splits multiple statements" do
940
+ proc{@ds.split_multiple_result_sets.graph(:b, :b=>:a)}.should raise_error(Sequel::Error)
941
+ end
942
+
943
+ specify "should not allow splitting a graphed dataset" do
944
+ proc{MYSQL_DB[:a].graph(:b, :b=>:a).split_multiple_result_sets}.should raise_error(Sequel::Error)
945
+ end
946
+ end
909
947
  end