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