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,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