sequel 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +84 -0
- data/Rakefile +1 -1
- data/doc/cheat_sheet.rdoc +5 -2
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/lib/sequel/adapters/ado.rb +3 -1
- data/lib/sequel/adapters/ado/mssql.rb +2 -2
- data/lib/sequel/adapters/do.rb +2 -11
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -2
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/informix.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +3 -3
- data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
- data/lib/sequel/adapters/mysql.rb +60 -21
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +1 -5
- data/lib/sequel/adapters/postgres.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +142 -33
- data/lib/sequel/adapters/shared/mysql.rb +54 -31
- data/lib/sequel/adapters/shared/oracle.rb +17 -6
- data/lib/sequel/adapters/shared/postgres.rb +7 -7
- data/lib/sequel/adapters/shared/progress.rb +3 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -17
- data/lib/sequel/connection_pool.rb +4 -6
- data/lib/sequel/core.rb +29 -113
- data/lib/sequel/database.rb +14 -12
- data/lib/sequel/dataset.rb +8 -21
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/graph.rb +9 -2
- data/lib/sequel/dataset/sql.rb +170 -104
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/schema_dumper.rb +7 -1
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -4
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/model/associations.rb +105 -45
- data/lib/sequel/model/base.rb +37 -28
- data/lib/sequel/plugins/active_model.rb +35 -0
- data/lib/sequel/plugins/association_dependencies.rb +96 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/force_encoding.rb +61 -0
- data/lib/sequel/plugins/many_through_many.rb +32 -11
- data/lib/sequel/plugins/nested_attributes.rb +7 -2
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +61 -0
- data/lib/sequel/sql.rb +31 -30
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +262 -0
- data/spec/adapters/mysql_spec.rb +46 -8
- data/spec/adapters/postgres_spec.rb +6 -3
- data/spec/adapters/spec_helper.rb +21 -0
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +1 -1
- data/spec/core/database_spec.rb +27 -1
- data/spec/core/dataset_spec.rb +63 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -0
- data/spec/extensions/active_model_spec.rb +47 -0
- data/spec/extensions/association_dependencies_spec.rb +108 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/force_encoding_spec.rb +75 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +60 -2
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +29 -1
- data/spec/extensions/schema_dumper_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +60 -0
- data/spec/integration/database_test.rb +8 -0
- data/spec/integration/dataset_test.rb +9 -9
- data/spec/integration/plugin_test.rb +139 -0
- data/spec/integration/schema_test.rb +7 -7
- data/spec/integration/spec_helper.rb +32 -1
- data/spec/integration/timezone_test.rb +3 -3
- data/spec/integration/transaction_test.rb +1 -1
- data/spec/integration/type_test.rb +6 -6
- data/spec/model/association_reflection_spec.rb +18 -0
- data/spec/model/associations_spec.rb +169 -9
- data/spec/model/base_spec.rb +2 -0
- data/spec/model/eager_loading_spec.rb +82 -2
- data/spec/model/model_spec.rb +8 -1
- data/spec/model/record_spec.rb +52 -9
- 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
|
data/lib/sequel/version.rb
CHANGED
@@ -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
|
+
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -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
|