sequel 3.26.0 → 3.27.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +26 -0
- data/Rakefile +2 -3
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +9 -533
- data/doc/prepared_statements.rdoc +8 -7
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/testing.rdoc +64 -0
- data/lib/sequel/adapters/amalgalite.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +3 -1
- data/lib/sequel/adapters/jdbc/h2.rb +11 -5
- data/lib/sequel/adapters/mysql.rb +4 -122
- data/lib/sequel/adapters/mysql2.rb +4 -13
- data/lib/sequel/adapters/odbc.rb +4 -1
- data/lib/sequel/adapters/odbc/db2.rb +21 -0
- data/lib/sequel/adapters/shared/mysql.rb +12 -0
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
- data/lib/sequel/adapters/tinytds.rb +122 -3
- data/lib/sequel/core.rb +4 -3
- data/lib/sequel/database/misc.rb +7 -10
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/sql.rb +7 -0
- data/lib/sequel/model/associations.rb +2 -2
- data/lib/sequel/model/base.rb +60 -10
- data/lib/sequel/plugins/prepared_statements_safe.rb +17 -7
- data/lib/sequel/sql.rb +5 -0
- data/lib/sequel/timezones.rb +12 -3
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +25 -21
- data/spec/core/database_spec.rb +200 -0
- data/spec/core/dataset_spec.rb +6 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +10 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/integration/schema_test.rb +30 -1
- data/spec/integration/type_test.rb +10 -3
- data/spec/model/base_spec.rb +44 -0
- data/spec/model/model_spec.rb +14 -0
- data/spec/model/record_spec.rb +131 -12
- metadata +14 -4
@@ -32,9 +32,32 @@ module Sequel
|
|
32
32
|
begin
|
33
33
|
m = opts[:return]
|
34
34
|
r = nil
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
if (args = opts[:arguments]) && !args.empty?
|
36
|
+
types = []
|
37
|
+
values = []
|
38
|
+
args.each_with_index do |(k, v), i|
|
39
|
+
v, type = ps_arg_type(v)
|
40
|
+
types << "@#{k} #{type}"
|
41
|
+
values << "@#{k} = #{v}"
|
42
|
+
end
|
43
|
+
case m
|
44
|
+
when :do
|
45
|
+
sql = "#{sql}; SELECT @@ROWCOUNT AS AffectedRows"
|
46
|
+
single_value = true
|
47
|
+
when :insert
|
48
|
+
sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
|
49
|
+
single_value = true
|
50
|
+
end
|
51
|
+
sql = "EXEC sp_executesql N'#{c.escape(sql)}', N'#{c.escape(types.join(', '))}', #{values.join(', ')}"
|
52
|
+
log_yield(sql) do
|
53
|
+
r = c.execute(sql)
|
54
|
+
r.each{|row| return row.values.first} if single_value
|
55
|
+
end
|
56
|
+
else
|
57
|
+
log_yield(sql) do
|
58
|
+
r = c.execute(sql)
|
59
|
+
return r.send(m) if m
|
60
|
+
end
|
38
61
|
end
|
39
62
|
yield(r) if block_given?
|
40
63
|
rescue TinyTds::Error => e
|
@@ -84,14 +107,98 @@ module Sequel
|
|
84
107
|
c.close
|
85
108
|
end
|
86
109
|
|
110
|
+
# Return true if the :conn argument is present and not active.
|
87
111
|
def disconnect_error?(e, opts)
|
88
112
|
super || (opts[:conn] && !opts[:conn].active?)
|
89
113
|
end
|
114
|
+
|
115
|
+
# Return a 2 element array with the literal value and type to use
|
116
|
+
# in the prepared statement call for the given value and connection.
|
117
|
+
def ps_arg_type(v)
|
118
|
+
case v
|
119
|
+
when Fixnum
|
120
|
+
[v, 'int']
|
121
|
+
when Bignum
|
122
|
+
[v, 'bigint']
|
123
|
+
when Float
|
124
|
+
[v, 'double precision']
|
125
|
+
when Numeric
|
126
|
+
[v, 'numeric']
|
127
|
+
when SQLTime
|
128
|
+
[literal(v), 'time']
|
129
|
+
when DateTime, Time
|
130
|
+
[literal(v), 'datetime']
|
131
|
+
when Date
|
132
|
+
[literal(v), 'date']
|
133
|
+
when nil
|
134
|
+
['NULL', 'nvarchar(max)']
|
135
|
+
when true
|
136
|
+
['1', 'int']
|
137
|
+
when false
|
138
|
+
['0', 'int']
|
139
|
+
when SQL::Blob
|
140
|
+
[literal(v), 'varbinary(max)']
|
141
|
+
else
|
142
|
+
[literal(v), 'nvarchar(max)']
|
143
|
+
end
|
144
|
+
end
|
90
145
|
end
|
91
146
|
|
92
147
|
class Dataset < Sequel::Dataset
|
93
148
|
include Sequel::MSSQL::DatasetMethods
|
94
149
|
|
150
|
+
# SQLite already supports named bind arguments, so use directly.
|
151
|
+
module ArgumentMapper
|
152
|
+
include Sequel::Dataset::ArgumentMapper
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
# Return a hash with the same values as the given hash,
|
157
|
+
# but with the keys converted to strings.
|
158
|
+
def map_to_prepared_args(hash)
|
159
|
+
args = {}
|
160
|
+
hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
|
161
|
+
args
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# SQLite uses a : before the name of the argument for named
|
167
|
+
# arguments.
|
168
|
+
def prepared_arg(k)
|
169
|
+
LiteralString.new("@#{k.to_s.gsub('.', '__')}")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Always assume a prepared argument.
|
173
|
+
def prepared_arg?(k)
|
174
|
+
true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# SQLite prepared statement uses a new prepared statement each time
|
179
|
+
# it is called, but it does use the bind arguments.
|
180
|
+
module PreparedStatementMethods
|
181
|
+
include ArgumentMapper
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
# Run execute_select on the database with the given SQL and the stored
|
186
|
+
# bind arguments.
|
187
|
+
def execute(sql, opts={}, &block)
|
188
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
192
|
+
def execute_dui(sql, opts={}, &block)
|
193
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
197
|
+
def execute_insert(sql, opts={}, &block)
|
198
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
95
202
|
# Yield hashes with symbol keys, attempting to optimize for
|
96
203
|
# various cases.
|
97
204
|
def fetch_rows(sql)
|
@@ -123,6 +230,18 @@ module Sequel
|
|
123
230
|
self
|
124
231
|
end
|
125
232
|
|
233
|
+
# Create a named prepared statement that is stored in the
|
234
|
+
# database (and connection) for reuse.
|
235
|
+
def prepare(type, name=nil, *values)
|
236
|
+
ps = to_prepared_statement(type, values)
|
237
|
+
ps.extend(PreparedStatementMethods)
|
238
|
+
if name
|
239
|
+
ps.prepared_statement_name = name
|
240
|
+
db.prepared_statements[name] = ps
|
241
|
+
end
|
242
|
+
ps
|
243
|
+
end
|
244
|
+
|
126
245
|
private
|
127
246
|
|
128
247
|
# Properly escape the given string +v+.
|
data/lib/sequel/core.rb
CHANGED
@@ -228,12 +228,13 @@ module Sequel
|
|
228
228
|
end
|
229
229
|
end
|
230
230
|
|
231
|
-
# Converts the given +string+ into a
|
231
|
+
# Converts the given +string+ into a <tt>Sequel::SQLTime</tt> object.
|
232
232
|
#
|
233
|
-
# Sequel.string_to_time('10:20:30') #
|
233
|
+
# v = Sequel.string_to_time('10:20:30') # Sequel::SQLTime.parse('10:20:30')
|
234
|
+
# DB.literal(v) # => '10:20:30'
|
234
235
|
def self.string_to_time(string)
|
235
236
|
begin
|
236
|
-
|
237
|
+
SQLTime.parse(string)
|
237
238
|
rescue => e
|
238
239
|
raise convert_exception_class(e, InvalidValue)
|
239
240
|
end
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -215,10 +215,10 @@ module Sequel
|
|
215
215
|
# Typecast the value to a Date
|
216
216
|
def typecast_value_date(value)
|
217
217
|
case value
|
218
|
-
when Date
|
219
|
-
value
|
220
218
|
when DateTime, Time
|
221
219
|
Date.new(value.year, value.month, value.day)
|
220
|
+
when Date
|
221
|
+
value
|
222
222
|
when String
|
223
223
|
Sequel.string_to_date(value)
|
224
224
|
when Hash
|
@@ -230,12 +230,7 @@ module Sequel
|
|
230
230
|
|
231
231
|
# Typecast the value to a DateTime or Time depending on Sequel.datetime_class
|
232
232
|
def typecast_value_datetime(value)
|
233
|
-
|
234
|
-
if value.is_a?(Hash)
|
235
|
-
klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
236
|
-
else
|
237
|
-
Sequel.typecast_to_application_timestamp(value)
|
238
|
-
end
|
233
|
+
Sequel.typecast_to_application_timestamp(value)
|
239
234
|
end
|
240
235
|
|
241
236
|
# Typecast the value to a BigDecimal
|
@@ -280,13 +275,15 @@ module Sequel
|
|
280
275
|
# Typecast the value to a Time
|
281
276
|
def typecast_value_time(value)
|
282
277
|
case value
|
283
|
-
when
|
278
|
+
when SQLTime
|
284
279
|
value
|
280
|
+
when Time
|
281
|
+
SQLTime.local(value.year, value.month, value.day, value.hour, value.min, value.sec, value.respond_to?(:nsec) ? value.nsec : value.usec)
|
285
282
|
when String
|
286
283
|
Sequel.string_to_time(value)
|
287
284
|
when Hash
|
288
285
|
t = Time.now
|
289
|
-
|
286
|
+
SQLTime.local(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
290
287
|
else
|
291
288
|
raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
|
292
289
|
end
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -107,7 +107,7 @@ module Sequel
|
|
107
107
|
# Define a hash value such that datasets with the same DB, opts, and SQL
|
108
108
|
# will have the same hash value
|
109
109
|
def hash
|
110
|
-
[db, opts.sort_by{|k| k.to_s}, sql].hash
|
110
|
+
[db, opts.sort_by{|k, v| k.to_s}, sql].hash
|
111
111
|
end
|
112
112
|
|
113
113
|
# The String instance method to call on identifiers before sending them to
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -100,6 +100,8 @@ module Sequel
|
|
100
100
|
literal_false
|
101
101
|
when Array
|
102
102
|
literal_array(v)
|
103
|
+
when SQLTime
|
104
|
+
literal_sqltime(v)
|
103
105
|
when Time
|
104
106
|
literal_time(v)
|
105
107
|
when DateTime
|
@@ -723,6 +725,11 @@ module Sequel
|
|
723
725
|
end
|
724
726
|
end
|
725
727
|
|
728
|
+
# SQL fragment for Sequel::SQLTime, containing just the time part
|
729
|
+
def literal_sqltime(v)
|
730
|
+
v.strftime("'%H:%M:%S#{format_timestamp_usec(v.usec) if supports_timestamp_usecs?}'")
|
731
|
+
end
|
732
|
+
|
726
733
|
# SQL fragment for String. Doubles \ and ' by default.
|
727
734
|
def literal_string(v)
|
728
735
|
"'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
|
@@ -1037,8 +1037,8 @@ module Sequel
|
|
1037
1037
|
@associations ||= {}
|
1038
1038
|
end
|
1039
1039
|
|
1040
|
-
#
|
1041
|
-
# an Error if the model has no primary key.
|
1040
|
+
# Formally used internally by the associations code, like pk but doesn't raise
|
1041
|
+
# an Error if the model has no primary key. Not used any longer, deprecated.
|
1042
1042
|
def pk_or_nil
|
1043
1043
|
key = primary_key
|
1044
1044
|
key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
|
data/lib/sequel/model/base.rb
CHANGED
@@ -148,6 +148,26 @@ module Sequel
|
|
148
148
|
def dataset=(ds)
|
149
149
|
set_dataset(ds)
|
150
150
|
end
|
151
|
+
|
152
|
+
# Extend the dataset with an anonymous module, similar to adding
|
153
|
+
# a plugin with the methods defined in DatasetMethods. If a block
|
154
|
+
# is given, it is module_evaled.
|
155
|
+
#
|
156
|
+
# Artist.dataset_module do
|
157
|
+
# def foo
|
158
|
+
# :bar
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
# Artist.dataset.foo
|
162
|
+
# # => :bar
|
163
|
+
# Artist.foo
|
164
|
+
# # => :bar
|
165
|
+
def dataset_module
|
166
|
+
@dataset_module ||= Module.new
|
167
|
+
@dataset_module.module_eval(&Proc.new) if block_given?
|
168
|
+
dataset_extend(@dataset_module)
|
169
|
+
@dataset_module
|
170
|
+
end
|
151
171
|
|
152
172
|
# Returns the database associated with the Model class.
|
153
173
|
# If this model doesn't have a database associated with it,
|
@@ -248,6 +268,13 @@ module Sequel
|
|
248
268
|
find(cond) || create(cond, &block)
|
249
269
|
end
|
250
270
|
|
271
|
+
# Clear the setter_methods cache when a module is included, as it
|
272
|
+
# may contain setter methods.
|
273
|
+
def include(mod)
|
274
|
+
clear_setter_methods_cache
|
275
|
+
super
|
276
|
+
end
|
277
|
+
|
251
278
|
# If possible, set the dataset for the model subclass as soon as it
|
252
279
|
# is created. Also, make sure the inherited class instance variables
|
253
280
|
# are copied into the subclass.
|
@@ -328,12 +355,7 @@ module Sequel
|
|
328
355
|
m.apply(self, *args, &blk) if m.respond_to?(:apply)
|
329
356
|
include(m::InstanceMethods) if plugin_module_defined?(m, :InstanceMethods)
|
330
357
|
extend(m::ClassMethods)if plugin_module_defined?(m, :ClassMethods)
|
331
|
-
if plugin_module_defined?(m, :DatasetMethods)
|
332
|
-
dataset.extend(m::DatasetMethods) if @dataset
|
333
|
-
dataset_method_modules << m::DatasetMethods
|
334
|
-
meths = m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
|
335
|
-
def_dataset_method(*meths) unless meths.empty?
|
336
|
-
end
|
358
|
+
dataset_extend(m::DatasetMethods) if plugin_module_defined?(m, :DatasetMethods)
|
337
359
|
end
|
338
360
|
m.configure(self, *args, &blk) if m.respond_to?(:configure)
|
339
361
|
end
|
@@ -553,6 +575,16 @@ module Sequel
|
|
553
575
|
end
|
554
576
|
end
|
555
577
|
|
578
|
+
# Add the module to the class's dataset_method_modules. Extend the dataset with the
|
579
|
+
# module if the model has a dataset. Add dataset methods to the class for all
|
580
|
+
# public dataset methods.
|
581
|
+
def dataset_extend(mod)
|
582
|
+
dataset.extend(mod) if @dataset
|
583
|
+
dataset_method_modules << mod
|
584
|
+
meths = mod.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
|
585
|
+
def_dataset_method(*meths) unless meths.empty?
|
586
|
+
end
|
587
|
+
|
556
588
|
# Create a column accessor for a column with a method name that is hard to use in ruby code.
|
557
589
|
def def_bad_column_accessor(column)
|
558
590
|
overridable_methods_module.module_eval do
|
@@ -705,7 +737,7 @@ module Sequel
|
|
705
737
|
# a model object, Sequel will call +around_destory+, which will call +before_destroy+, do
|
706
738
|
# the destroy, and then call +after_destroy+.
|
707
739
|
# * The following instance_methods all call the class method of the same
|
708
|
-
# name: columns,
|
740
|
+
# name: columns, db, primary_key, db_schema.
|
709
741
|
# * All of the methods in +BOOLEAN_SETTINGS+ create attr_writers allowing you
|
710
742
|
# to set values for the attribute. It also creates instnace getters returning
|
711
743
|
# the value of the setting. If the value has not yet been set, it
|
@@ -889,14 +921,25 @@ module Sequel
|
|
889
921
|
|
890
922
|
# Returns true when current instance exists, false otherwise.
|
891
923
|
# Generally an object that isn't new will exist unless it has
|
892
|
-
# been deleted. Uses a database query to check for existence
|
924
|
+
# been deleted. Uses a database query to check for existence,
|
925
|
+
# unless the model object is new, in which case this is always
|
926
|
+
# false.
|
893
927
|
#
|
894
928
|
# Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
|
895
929
|
# # => true
|
930
|
+
# Artist.new.exists?
|
931
|
+
# # => false
|
896
932
|
def exists?
|
897
|
-
!this.get(1).nil?
|
933
|
+
new? ? false : !this.get(1).nil?
|
898
934
|
end
|
899
935
|
|
936
|
+
# Ignore the model's setter method cache when this instances extends a module, as the
|
937
|
+
# module may contain setter methods.
|
938
|
+
def extend(mod)
|
939
|
+
@singleton_setter_added = true
|
940
|
+
super
|
941
|
+
end
|
942
|
+
|
900
943
|
# Value that should be unique for objects with the same class and pk (if pk is not nil), or
|
901
944
|
# the same class and values (if pk is nil).
|
902
945
|
#
|
@@ -905,7 +948,14 @@ module Sequel
|
|
905
948
|
# Artist.new.hash == Artist.new.hash # true
|
906
949
|
# Artist.new(:name=>'Bob').hash == Artist.new.hash # false
|
907
950
|
def hash
|
908
|
-
|
951
|
+
case primary_key
|
952
|
+
when Array
|
953
|
+
[model, !pk.all? ? @values.sort_by{|k,v| k.to_s} : pk].hash
|
954
|
+
when Symbol
|
955
|
+
[model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
|
956
|
+
else
|
957
|
+
[model, @values.sort_by{|k,v| k.to_s}].hash
|
958
|
+
end
|
909
959
|
end
|
910
960
|
|
911
961
|
# Returns value for the :id attribute, even if the primary key is
|
@@ -33,10 +33,16 @@ module Sequel
|
|
33
33
|
# that can be created is 2^N (where N is the number of free columns).
|
34
34
|
attr_reader :prepared_statements_column_defaults
|
35
35
|
|
36
|
-
# Set the column defaults to use when creating on the subclass.
|
37
36
|
def inherited(subclass)
|
38
37
|
super
|
39
|
-
subclass.
|
38
|
+
subclass.instance_variable_set(:@prepared_statements_column_defaults, @prepared_statements_column_defaults) if @prepared_statements_column_defaults && !subclass.prepared_statements_column_defaults
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the column defaults to use when creating on the subclass.
|
42
|
+
def set_dataset(*)
|
43
|
+
x = super
|
44
|
+
set_prepared_statements_column_defaults
|
45
|
+
x
|
40
46
|
end
|
41
47
|
|
42
48
|
private
|
@@ -45,11 +51,13 @@ module Sequel
|
|
45
51
|
# are set to a default value unless they are a primary key column or
|
46
52
|
# they don't have a parseable default.
|
47
53
|
def set_prepared_statements_column_defaults
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
if db_schema
|
55
|
+
h = {}
|
56
|
+
db_schema.each do |k, v|
|
57
|
+
h[k] = v[:ruby_default] if (v[:ruby_default] || !v[:default]) && !v[:primary_key]
|
58
|
+
end
|
59
|
+
@prepared_statements_column_defaults = h
|
51
60
|
end
|
52
|
-
@prepared_statements_column_defaults = h
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
@@ -57,7 +65,9 @@ module Sequel
|
|
57
65
|
# Merge the current values into the default values to reduce the number
|
58
66
|
# of free columns.
|
59
67
|
def before_create
|
60
|
-
|
68
|
+
if v = model.prepared_statements_column_defaults
|
69
|
+
set_values(v.merge(values))
|
70
|
+
end
|
61
71
|
super
|
62
72
|
end
|
63
73
|
|
data/lib/sequel/sql.rb
CHANGED
@@ -38,6 +38,11 @@ module Sequel
|
|
38
38
|
class LiteralString < ::String
|
39
39
|
end
|
40
40
|
|
41
|
+
# Time subclass that gets literalized with only the time value, so it operates
|
42
|
+
# like a standard SQL time type.
|
43
|
+
class SQLTime < ::Time
|
44
|
+
end
|
45
|
+
|
41
46
|
# The SQL module holds classes whose instances represent SQL fragments.
|
42
47
|
# It also holds modules that are included in core ruby classes that
|
43
48
|
# make Sequel a friendly DSL.
|
data/lib/sequel/timezones.rb
CHANGED
@@ -109,6 +109,8 @@ module Sequel
|
|
109
109
|
else
|
110
110
|
Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s)
|
111
111
|
end
|
112
|
+
when Hash
|
113
|
+
convert_input_timestamp([:year, :month, :day, :hour, :minute, :second].map{|x| (v[x] || v[x.to_s]).to_i}, input_timezone)
|
112
114
|
when Time
|
113
115
|
if datetime_class == DateTime
|
114
116
|
v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601)
|
@@ -121,8 +123,6 @@ module Sequel
|
|
121
123
|
else
|
122
124
|
v.respond_to?(:to_time) ? v.to_time : string_to_datetime(v.to_s)
|
123
125
|
end
|
124
|
-
when Date
|
125
|
-
convert_input_timestamp(v.to_s, input_timezone)
|
126
126
|
else
|
127
127
|
raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
|
128
128
|
end
|
@@ -160,7 +160,16 @@ module Sequel
|
|
160
160
|
# +convert_output_timestamp+.
|
161
161
|
def convert_timestamp(v, input_timezone)
|
162
162
|
begin
|
163
|
-
|
163
|
+
if v.is_a?(Date) && !v.is_a?(DateTime)
|
164
|
+
# Dates handled specially as they are assumed to already be in the application_timezone
|
165
|
+
if datetime_class == DateTime
|
166
|
+
DateTime.civil(v.year, v.month, v.day, 0, 0, 0, application_timezone == :local ? (defined?(Rational) ? Rational(Time.local(v.year, v.month, v.day).utc_offset, 86400) : Time.local(v.year, v.month, v.day).utc_offset/86400.0) : 0)
|
167
|
+
else
|
168
|
+
Time.send(application_timezone == :utc ? :utc : :local, v.year, v.month, v.day)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
convert_output_timestamp(convert_input_timestamp(v, input_timezone), application_timezone)
|
172
|
+
end
|
164
173
|
rescue InvalidValue
|
165
174
|
raise
|
166
175
|
rescue => e
|