sequel 3.26.0 → 3.27.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 +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
|