sequel 3.44.0 → 3.45.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +44 -0
- data/Rakefile +12 -4
- data/doc/reflection.rdoc +3 -3
- data/doc/release_notes/3.45.0.txt +179 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/transactions.rdoc +23 -0
- data/lib/sequel/adapters/db2.rb +1 -0
- data/lib/sequel/adapters/ibmdb.rb +19 -3
- data/lib/sequel/adapters/jdbc.rb +15 -0
- data/lib/sequel/adapters/jdbc/derby.rb +1 -5
- data/lib/sequel/adapters/jdbc/h2.rb +1 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -1
- data/lib/sequel/adapters/jdbc/jtds.rb +5 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +7 -1
- data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
- data/lib/sequel/adapters/jdbc/transactions.rb +28 -1
- data/lib/sequel/adapters/mysql.rb +4 -0
- data/lib/sequel/adapters/mysql2.rb +5 -1
- data/lib/sequel/adapters/oracle.rb +18 -0
- data/lib/sequel/adapters/postgres.rb +11 -1
- data/lib/sequel/adapters/shared/access.rb +14 -2
- data/lib/sequel/adapters/shared/cubrid.rb +1 -11
- data/lib/sequel/adapters/shared/db2.rb +11 -6
- data/lib/sequel/adapters/shared/mssql.rb +10 -10
- data/lib/sequel/adapters/shared/mysql.rb +11 -1
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +17 -1
- data/lib/sequel/adapters/shared/oracle.rb +16 -15
- data/lib/sequel/adapters/shared/postgres.rb +91 -59
- data/lib/sequel/adapters/shared/sqlite.rb +1 -4
- data/lib/sequel/adapters/tinytds.rb +15 -0
- data/lib/sequel/connection_pool/threaded.rb +1 -1
- data/lib/sequel/core.rb +10 -0
- data/lib/sequel/database/connecting.rb +2 -0
- data/lib/sequel/database/misc.rb +46 -4
- data/lib/sequel/database/query.rb +33 -14
- data/lib/sequel/database/schema_methods.rb +0 -5
- data/lib/sequel/dataset/misc.rb +9 -0
- data/lib/sequel/dataset/mutation.rb +9 -7
- data/lib/sequel/dataset/sql.rb +13 -0
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/connection_validator.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +0 -8
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/named_timezones.rb +18 -2
- data/lib/sequel/extensions/pg_array.rb +5 -1
- data/lib/sequel/extensions/pg_array_ops.rb +2 -0
- data/lib/sequel/extensions/pg_hstore.rb +2 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
- data/lib/sequel/extensions/pg_json.rb +3 -1
- data/lib/sequel/extensions/pg_range.rb +2 -0
- data/lib/sequel/extensions/pg_range_ops.rb +2 -0
- data/lib/sequel/extensions/pg_row.rb +2 -0
- data/lib/sequel/extensions/pg_row_ops.rb +2 -0
- data/lib/sequel/extensions/query.rb +18 -22
- data/lib/sequel/model/associations.rb +3 -4
- data/lib/sequel/model/base.rb +2 -0
- data/lib/sequel/plugins/force_encoding.rb +2 -0
- data/lib/sequel/plugins/json_serializer.rb +155 -39
- data/lib/sequel/plugins/serialization.rb +14 -2
- data/lib/sequel/plugins/unlimited_update.rb +31 -0
- data/lib/sequel/plugins/validation_class_methods.rb +6 -4
- data/lib/sequel/plugins/xml_serializer.rb +133 -30
- data/lib/sequel/sql.rb +2 -0
- data/lib/sequel/timezones.rb +4 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +0 -11
- data/spec/adapters/postgres_spec.rb +86 -54
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/core/connection_pool_spec.rb +16 -0
- data/spec/core/database_spec.rb +77 -1
- data/spec/core/dataset_spec.rb +30 -15
- data/spec/core/expression_filters_spec.rb +55 -13
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/core/schema_spec.rb +0 -2
- data/spec/core/spec_helper.rb +5 -0
- data/spec/core_extensions_spec.rb +33 -28
- data/spec/extensions/constraint_validations_spec.rb +2 -2
- data/spec/extensions/core_refinements_spec.rb +12 -12
- data/spec/extensions/json_serializer_spec.rb +137 -31
- data/spec/extensions/named_timezones_spec.rb +10 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +5 -0
- data/spec/extensions/pg_json_spec.rb +14 -0
- data/spec/extensions/pg_row_spec.rb +11 -0
- data/spec/extensions/pretty_table_spec.rb +2 -2
- data/spec/extensions/query_spec.rb +11 -8
- data/spec/extensions/serialization_spec.rb +20 -0
- data/spec/extensions/spec_helper.rb +8 -2
- data/spec/extensions/sql_expr_spec.rb +1 -1
- data/spec/extensions/unlimited_update_spec.rb +20 -0
- data/spec/extensions/xml_serializer_spec.rb +68 -16
- data/spec/integration/dataset_test.rb +28 -0
- data/spec/integration/spec_helper.rb +6 -0
- data/spec/integration/transaction_test.rb +39 -0
- data/spec/model/model_spec.rb +1 -1
- data/spec/sequel_coverage.rb +15 -0
- metadata +8 -3
data/lib/sequel/database/misc.rb
CHANGED
@@ -201,6 +201,13 @@ module Sequel
|
|
201
201
|
Sequel.synchronize{prepared_statements[name]}
|
202
202
|
end
|
203
203
|
|
204
|
+
# Proxy the quote_identifier method to the dataset,
|
205
|
+
# useful for quoting unqualified identifiers for use
|
206
|
+
# outside of datasets.
|
207
|
+
def quote_identifier(v)
|
208
|
+
schema_utility_dataset.quote_identifier(v)
|
209
|
+
end
|
210
|
+
|
204
211
|
# Default serial primary key options, used by the table creation
|
205
212
|
# code.
|
206
213
|
def serial_primary_key_options
|
@@ -326,9 +333,9 @@ module Sequel
|
|
326
333
|
[]
|
327
334
|
end
|
328
335
|
|
329
|
-
#
|
336
|
+
# An enumerable yielding pairs of regexps and exception classes, used
|
330
337
|
# to match against underlying driver exception messages in
|
331
|
-
# order to raise a more specific Sequel::
|
338
|
+
# order to raise a more specific Sequel::DatabaseError subclass.
|
332
339
|
def database_error_regexps
|
333
340
|
DEFAULT_DATABASE_ERROR_REGEXPS
|
334
341
|
end
|
@@ -339,19 +346,52 @@ module Sequel
|
|
339
346
|
database_specific_error_class(exception, opts) || DatabaseError
|
340
347
|
end
|
341
348
|
|
349
|
+
# Return the SQLState for the given exception, if one can be
|
350
|
+
# determined
|
351
|
+
def database_exception_sqlstate(exception, opts)
|
352
|
+
nil
|
353
|
+
end
|
354
|
+
|
342
355
|
# Return a specific Sequel::DatabaseError exception class if
|
343
356
|
# one is appropriate for the underlying exception,
|
344
357
|
# or nil if there is no specific exception class.
|
345
358
|
def database_specific_error_class(exception, opts)
|
346
359
|
return DatabaseDisconnectError if disconnect_error?(exception, opts)
|
347
360
|
|
348
|
-
|
349
|
-
|
361
|
+
if sqlstate = database_exception_sqlstate(exception, opts)
|
362
|
+
if klass = database_specific_error_class_from_sqlstate(sqlstate)
|
363
|
+
return klass
|
364
|
+
end
|
365
|
+
else
|
366
|
+
database_error_regexps.each do |regexp, klass|
|
367
|
+
return klass if exception.message =~ regexp
|
368
|
+
end
|
350
369
|
end
|
351
370
|
|
352
371
|
nil
|
353
372
|
end
|
354
373
|
|
374
|
+
NOT_NULL_CONSTRAINT_SQLSTATES = %w'23502'.freeze.each{|s| s.freeze}
|
375
|
+
FOREIGN_KEY_CONSTRAINT_SQLSTATES = %w'23503 23506 23504'.freeze.each{|s| s.freeze}
|
376
|
+
UNIQUE_CONSTRAINT_SQLSTATES = %w'23505'.freeze.each{|s| s.freeze}
|
377
|
+
CHECK_CONSTRAINT_SQLSTATES = %w'23513 23514'.freeze.each{|s| s.freeze}
|
378
|
+
SERIALIZATION_CONSTRAINT_SQLSTATES = %w'40001'.freeze.each{|s| s.freeze}
|
379
|
+
# Given the SQLState, return the appropriate DatabaseError subclass.
|
380
|
+
def database_specific_error_class_from_sqlstate(sqlstate)
|
381
|
+
case sqlstate
|
382
|
+
when *NOT_NULL_CONSTRAINT_SQLSTATES
|
383
|
+
NotNullConstraintViolation
|
384
|
+
when *FOREIGN_KEY_CONSTRAINT_SQLSTATES
|
385
|
+
ForeignKeyConstraintViolation
|
386
|
+
when *UNIQUE_CONSTRAINT_SQLSTATES
|
387
|
+
UniqueConstraintViolation
|
388
|
+
when *CHECK_CONSTRAINT_SQLSTATES
|
389
|
+
CheckConstraintViolation
|
390
|
+
when *SERIALIZATION_CONSTRAINT_SQLSTATES
|
391
|
+
SerializationFailure
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
355
395
|
# Return true if exception represents a disconnect error, false otherwise.
|
356
396
|
def disconnect_error?(exception, opts)
|
357
397
|
opts[:disconnect]
|
@@ -435,12 +475,14 @@ module Sequel
|
|
435
475
|
(value.is_a?(String) && value =~ LEADING_ZERO_RE) ? Integer(value, 10) : Integer(value)
|
436
476
|
end
|
437
477
|
else
|
478
|
+
# :nocov:
|
438
479
|
# Replacement string when replacing leading zeroes.
|
439
480
|
LEADING_ZERO_REP = "\\1".freeze
|
440
481
|
# Typecast the value to an Integer
|
441
482
|
def typecast_value_integer(value)
|
442
483
|
Integer(value.is_a?(String) ? value.sub(LEADING_ZERO_RE, LEADING_ZERO_REP) : value)
|
443
484
|
end
|
485
|
+
# :nocov:
|
444
486
|
end
|
445
487
|
|
446
488
|
# Typecast the value to a String
|
@@ -251,21 +251,26 @@ module Sequel
|
|
251
251
|
#
|
252
252
|
# The following general options are respected:
|
253
253
|
#
|
254
|
-
# :disconnect ::
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
# behavior to execute multiple times. This does no checking for
|
259
|
-
# infinite loops, so if your transaction will repeatedly raise a
|
260
|
-
# disconnection error, this will cause the transaction block to loop
|
261
|
-
# indefinitely.
|
254
|
+
# :disconnect :: If set to :retry, automatically sets the :retry_on option
|
255
|
+
# with a Sequel::DatabaseDisconnectError. This option is only
|
256
|
+
# present for backwards compatibility, please use the :retry_on
|
257
|
+
# option instead.
|
262
258
|
# :isolation :: The transaction isolation level to use for this transaction,
|
263
259
|
# should be :uncommitted, :committed, :repeatable, or :serializable,
|
264
260
|
# used if given and the database/adapter supports customizable
|
265
261
|
# transaction isolation levels.
|
262
|
+
# :num_retries :: The number of times to retry if the :retry_on option is used.
|
263
|
+
# The default is 5 times. Can be set to nil to retry indefinitely,
|
264
|
+
# but that is not recommended.
|
266
265
|
# :prepare :: A string to use as the transaction identifier for a
|
267
266
|
# prepared transaction (two-phase commit), if the database/adapter
|
268
267
|
# supports prepared transactions.
|
268
|
+
# :retry_on :: An exception class or array of exception classes for which to
|
269
|
+
# automatically retry the transaction. Can only be set if not inside
|
270
|
+
# an existing transaction.
|
271
|
+
# Note that this should not be used unless the entire transaction
|
272
|
+
# block is idempotent, as otherwise it can cause non-idempotent
|
273
|
+
# behavior to execute multiple times.
|
269
274
|
# :rollback :: Can the set to :reraise to reraise any Sequel::Rollback exceptions
|
270
275
|
# raised, or :always to always rollback even if no exceptions occur
|
271
276
|
# (useful for testing).
|
@@ -284,16 +289,28 @@ module Sequel
|
|
284
289
|
# and :remote_write (9.2+).
|
285
290
|
def transaction(opts={}, &block)
|
286
291
|
if opts[:disconnect] == :retry
|
292
|
+
raise(Error, 'cannot specify both :disconnect=>:retry and :retry_on') if opts[:retry_on]
|
293
|
+
return transaction(opts.merge(:retry_on=>Sequel::DatabaseDisconnectError, :disconnect=>nil), &block)
|
294
|
+
end
|
295
|
+
|
296
|
+
if retry_on = opts[:retry_on]
|
297
|
+
num_retries = opts.fetch(:num_retries, 5)
|
287
298
|
begin
|
288
|
-
transaction(opts.merge(:
|
289
|
-
rescue
|
290
|
-
|
299
|
+
transaction(opts.merge(:retry_on=>nil, :retrying=>true), &block)
|
300
|
+
rescue *retry_on
|
301
|
+
if num_retries
|
302
|
+
num_retries -= 1
|
303
|
+
retry if num_retries >= 0
|
304
|
+
else
|
305
|
+
retry
|
306
|
+
end
|
307
|
+
raise
|
291
308
|
end
|
292
309
|
else
|
293
310
|
synchronize(opts[:server]) do |conn|
|
294
311
|
if already_in_transaction?(conn, opts)
|
295
|
-
if opts[:
|
296
|
-
raise Sequel::Error, "cannot set :disconnect=>:retry if you are already inside a transaction"
|
312
|
+
if opts[:retrying]
|
313
|
+
raise Sequel::Error, "cannot set :disconnect=>:retry or :retry_on options if you are already inside a transaction"
|
297
314
|
end
|
298
315
|
return yield(conn)
|
299
316
|
end
|
@@ -501,6 +518,7 @@ module Sequel
|
|
501
518
|
end
|
502
519
|
|
503
520
|
if (! defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION < '1.9'
|
521
|
+
# :nocov:
|
504
522
|
# Whether to commit the current transaction. On ruby 1.8 and rubinius,
|
505
523
|
# Thread.current.status is checked because Thread#kill skips rescue
|
506
524
|
# blocks (so exception would be nil), but the transaction should
|
@@ -518,6 +536,7 @@ module Sequel
|
|
518
536
|
end
|
519
537
|
end
|
520
538
|
end
|
539
|
+
# :nocov:
|
521
540
|
else
|
522
541
|
# Whether to commit the current transaction. On ruby 1.9 and JRuby,
|
523
542
|
# transactions will be committed if Thread#kill is used on an thread
|
@@ -642,7 +661,7 @@ module Sequel
|
|
642
661
|
:boolean
|
643
662
|
when /\A(real|float|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
|
644
663
|
:float
|
645
|
-
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?)
|
664
|
+
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?))\z/io
|
646
665
|
$1 && ['0', 'false'].include?($1) ? :integer : :decimal
|
647
666
|
when /bytea|blob|image|(var)?binary/io
|
648
667
|
:blob
|
@@ -705,11 +705,6 @@ module Sequel
|
|
705
705
|
schema_utility_dataset.quote_schema_table(table)
|
706
706
|
end
|
707
707
|
|
708
|
-
# Proxy the quote_identifier method to the dataset, used for quoting tables and columns.
|
709
|
-
def quote_identifier(v)
|
710
|
-
schema_utility_dataset.quote_identifier(v)
|
711
|
-
end
|
712
|
-
|
713
708
|
# SQL DDL statement for renaming a table.
|
714
709
|
def rename_table_sql(name, new_name)
|
715
710
|
"ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_schema_table(new_name)}"
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -49,6 +49,15 @@ module Sequel
|
|
49
49
|
def each_server
|
50
50
|
db.servers.each{|s| yield server(s)}
|
51
51
|
end
|
52
|
+
|
53
|
+
# Returns the string with the LIKE metacharacters (% and _) escaped.
|
54
|
+
# Useful for when the LIKE term is a user-provided string where metacharacters should not
|
55
|
+
# be recognized. Example:
|
56
|
+
#
|
57
|
+
# ds.escape_like("foo\\%_") # 'foo\\\%\_'
|
58
|
+
def escape_like(string)
|
59
|
+
string.gsub(/[\\%_]/){|m| "\\#{m}"}
|
60
|
+
end
|
52
61
|
|
53
62
|
# Alias of +first_source_alias+
|
54
63
|
def first_source
|
@@ -6,14 +6,17 @@ module Sequel
|
|
6
6
|
# ---------------------
|
7
7
|
|
8
8
|
# All methods that should have a ! method added that modifies the receiver.
|
9
|
-
MUTATION_METHODS = QUERY_METHODS - [:paginate, :naked]
|
9
|
+
MUTATION_METHODS = QUERY_METHODS - [:paginate, :naked, :from_self]
|
10
10
|
|
11
11
|
# Setup mutation (e.g. filter!) methods. These operate the same as the
|
12
12
|
# non-! methods, but replace the options of the current dataset with the
|
13
13
|
# options of the resulting dataset.
|
14
14
|
def self.def_mutation_method(*meths)
|
15
|
+
options = meths.pop if meths.last.is_a?(Hash)
|
16
|
+
mod = options[:module] if options
|
17
|
+
mod ||= self
|
15
18
|
meths.each do |meth|
|
16
|
-
class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
|
19
|
+
mod.class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
@@ -33,11 +36,10 @@ module Sequel
|
|
33
36
|
# a single hash argument and returns the object you want #each to return.
|
34
37
|
attr_accessor :row_proc
|
35
38
|
|
36
|
-
#
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
end
|
39
|
+
# Avoid self-referential dataset by cloning.
|
40
|
+
def from_self!(*args, &block)
|
41
|
+
@opts.merge!(clone.from_self(*args, &block).opts)
|
42
|
+
self
|
41
43
|
end
|
42
44
|
|
43
45
|
# Remove the row_proc from the current dataset.
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -189,6 +189,7 @@ module Sequel
|
|
189
189
|
ARRAY_EMPTY = '(NULL)'.freeze
|
190
190
|
AS = ' AS '.freeze
|
191
191
|
ASC = ' ASC'.freeze
|
192
|
+
BACKSLASH = "\\".freeze
|
192
193
|
BOOL_FALSE = "'f'".freeze
|
193
194
|
BOOL_TRUE = "'t'".freeze
|
194
195
|
BRACKET_CLOSE = ']'.freeze
|
@@ -219,6 +220,7 @@ module Sequel
|
|
219
220
|
DOUBLE_APOS = "''".freeze
|
220
221
|
DOUBLE_QUOTE = '""'.freeze
|
221
222
|
EQUAL = ' = '.freeze
|
223
|
+
ESCAPE = " ESCAPE ".freeze
|
222
224
|
EXTRACT = 'extract('.freeze
|
223
225
|
EXISTS = ['EXISTS '.freeze].freeze
|
224
226
|
FOR_UPDATE = ' FOR UPDATE'.freeze
|
@@ -239,6 +241,7 @@ module Sequel
|
|
239
241
|
INTO = " INTO ".freeze
|
240
242
|
IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
|
241
243
|
IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
|
244
|
+
LIKE_OPERATORS = ::Sequel::SQL::ComplexExpression::LIKE_OPERATORS
|
242
245
|
LIMIT = " LIMIT ".freeze
|
243
246
|
N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
|
244
247
|
NOT_SPACE = 'NOT '.freeze
|
@@ -455,6 +458,16 @@ module Sequel
|
|
455
458
|
literal_append(sql, vals)
|
456
459
|
sql << PAREN_CLOSE
|
457
460
|
end
|
461
|
+
when :LIKE, :'NOT LIKE'
|
462
|
+
sql << PAREN_OPEN
|
463
|
+
literal_append(sql, args.at(0))
|
464
|
+
sql << SPACE << op.to_s << SPACE
|
465
|
+
literal_append(sql, args.at(1))
|
466
|
+
sql << ESCAPE
|
467
|
+
literal_append(sql, BACKSLASH)
|
468
|
+
sql << PAREN_CLOSE
|
469
|
+
when :ILIKE, :'NOT ILIKE'
|
470
|
+
complex_expression_sql_append(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args.map{|v| Sequel.function(:UPPER, v)})
|
458
471
|
when *TWO_ARITY_OPERATORS
|
459
472
|
if REGEXP_OPERATORS.include?(op) && !supports_regexp?
|
460
473
|
raise InvalidOperation, "Pattern matching via regular expressions is not supported on #{db.database_type}"
|
data/lib/sequel/exceptions.rb
CHANGED
@@ -40,6 +40,9 @@ module Sequel
|
|
40
40
|
# Error raised when Sequel determines a database unique constraint has been violated.
|
41
41
|
class UniqueConstraintViolation < ConstraintViolation; end
|
42
42
|
|
43
|
+
# Error raised when Sequel determines a serialization failure/deadlock in the database.
|
44
|
+
class SerializationFailure < DatabaseError; end
|
45
|
+
|
43
46
|
# Error raised on an invalid operation, such as trying to update or delete
|
44
47
|
# a joined or grouped dataset.
|
45
48
|
class InvalidOperation < Error; end
|
@@ -21,7 +21,7 @@
|
|
21
21
|
# Note that if you set the timeout to validate connections
|
22
22
|
# on every checkout, you should probably manually control
|
23
23
|
# connection checkouts on a coarse basis, using
|
24
|
-
# Database#
|
24
|
+
# Database#synchronize. In a web application, the optimal
|
25
25
|
# place for that would be a rack middleware. Validating
|
26
26
|
# connections on every checkout without setting up coarse
|
27
27
|
# connection checkouts will hurt performance, in some cases
|
@@ -62,14 +62,6 @@ module Sequel
|
|
62
62
|
ACCESS_DURATION_UNITS = DURATION_UNITS.zip(%w'yyyy m d h n s'.map{|s| s.freeze}).freeze
|
63
63
|
DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
|
64
64
|
|
65
|
-
# Return an SQL fragment for the literalized version of the
|
66
|
-
# DateAdd expression.
|
67
|
-
def date_add_sql(da)
|
68
|
-
sql = ''
|
69
|
-
date_arith_sql_append(sql, da)
|
70
|
-
sql
|
71
|
-
end
|
72
|
-
|
73
65
|
# Append the SQL fragment for the DateAdd expression to the SQL query.
|
74
66
|
def date_add_sql_append(sql, da)
|
75
67
|
h = da.interval
|
@@ -30,10 +30,12 @@ module Sequel
|
|
30
30
|
when Time
|
31
31
|
datepart = "%Y-%m-%dT" unless obj.is_a?(Sequel::SQLTime)
|
32
32
|
if RUBY_VERSION < '1.9'
|
33
|
+
# :nocov:
|
33
34
|
# Time on 1.8 doesn't handle %N (or %z on Windows), manually set the usec value in the string
|
34
35
|
hours, mins = obj.utc_offset.divmod(3600)
|
35
36
|
mins /= 60
|
36
37
|
"#{obj.class}.parse(#{obj.strftime("#{datepart}%H:%M:%S.#{sprintf('%06i%+03i%02i', obj.usec, hours, mins)}").inspect})#{'.utc' if obj.utc?}"
|
38
|
+
# :nocov:
|
37
39
|
else
|
38
40
|
"#{obj.class}.parse(#{obj.strftime("#{datepart}%T.%N%z").inspect})#{'.utc' if obj.utc?}"
|
39
41
|
end
|
@@ -37,6 +37,13 @@ module Sequel
|
|
37
37
|
self.datetime_class = DateTime
|
38
38
|
|
39
39
|
module NamedTimezones
|
40
|
+
# Handles TZInfo::AmbiguousTime exceptions automatically by providing a
|
41
|
+
# proc called with both the datetime value being converted as well as
|
42
|
+
# the array of TZInfo::TimezonePeriod results. Example:
|
43
|
+
#
|
44
|
+
# Sequel.tzinfo_disambiguator = proc{|datetime, periods| periods.first}
|
45
|
+
attr_accessor :tzinfo_disambiguator
|
46
|
+
|
40
47
|
private
|
41
48
|
|
42
49
|
# Assume the given DateTime has a correct time but a wrong timezone. It is
|
@@ -44,7 +51,7 @@ module Sequel
|
|
44
51
|
# Keep the time the same but convert the timezone to the input_timezone.
|
45
52
|
# Expects the input_timezone to be a TZInfo::Timezone instance.
|
46
53
|
def convert_input_datetime_other(v, input_timezone)
|
47
|
-
local_offset = input_timezone.period_for_local(v).utc_total_offset_rational
|
54
|
+
local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset_rational
|
48
55
|
(v - local_offset).new_offset(local_offset)
|
49
56
|
end
|
50
57
|
|
@@ -54,7 +61,7 @@ module Sequel
|
|
54
61
|
# TZInfo converts times, but expects the given DateTime to have an offset
|
55
62
|
# of 0 and always leaves the timezone offset as 0
|
56
63
|
v = output_timezone.utc_to_local(v.new_offset(0))
|
57
|
-
local_offset = output_timezone.period_for_local(v).utc_total_offset_rational
|
64
|
+
local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset_rational
|
58
65
|
# Convert timezone offset from UTC to the offset for the output_timezone
|
59
66
|
(v - local_offset).new_offset(local_offset)
|
60
67
|
end
|
@@ -63,6 +70,15 @@ module Sequel
|
|
63
70
|
def convert_timezone_setter_arg(tz)
|
64
71
|
tz.is_a?(String) ? TZInfo::Timezone.get(tz) : super
|
65
72
|
end
|
73
|
+
|
74
|
+
# Return a disambiguation proc that provides both the datetime value
|
75
|
+
# and the periods, in order to allow the choice of period to depend
|
76
|
+
# on the datetime value.
|
77
|
+
def tzinfo_disambiguator_for(v)
|
78
|
+
if pr = @tzinfo_disambiguator
|
79
|
+
proc{|periods| pr.call(v, periods)}
|
80
|
+
end
|
81
|
+
end
|
66
82
|
end
|
67
83
|
|
68
84
|
extend NamedTimezones
|
@@ -402,10 +402,12 @@ module Sequel
|
|
402
402
|
end
|
403
403
|
|
404
404
|
if Sequel::Postgres.respond_to?(:parse_pg_array)
|
405
|
+
# :nocov:
|
405
406
|
# Use sequel_pg's C-based parser if it has already been defined.
|
406
407
|
def call(string)
|
407
408
|
PGArray.new(Sequel::Postgres.parse_pg_array(string, @converter), @type)
|
408
409
|
end
|
410
|
+
# :nocov:
|
409
411
|
else
|
410
412
|
# Parse the string using Parser with the appropriate
|
411
413
|
# converter, and return a PGArray with the appropriate database
|
@@ -431,7 +433,7 @@ module Sequel
|
|
431
433
|
# recursive map of the output is done to make sure that the entires in the
|
432
434
|
# correct type.
|
433
435
|
def call(string)
|
434
|
-
array =
|
436
|
+
array = Sequel.parse_json(string.gsub(SUBST_RE){|m| SUBST[m]})
|
435
437
|
array = Sequel.recursive_map(array, @converter) if @converter
|
436
438
|
PGArray.new(array, @type)
|
437
439
|
end
|
@@ -527,6 +529,7 @@ module Sequel
|
|
527
529
|
Database.register_extension(:pg_array, Postgres::PGArray::DatabaseMethods)
|
528
530
|
end
|
529
531
|
|
532
|
+
# :nocov:
|
530
533
|
if Sequel.core_extensions?
|
531
534
|
class Array
|
532
535
|
# Return a PGArray proxy to the receiver, using a
|
@@ -548,3 +551,4 @@ if defined?(Sequel::CoreRefinements)
|
|
548
551
|
end
|
549
552
|
end
|
550
553
|
end
|
554
|
+
# :nocov:
|
@@ -320,6 +320,7 @@ module Sequel
|
|
320
320
|
Database.register_extension(:pg_hstore, Postgres::HStore::DatabaseMethods)
|
321
321
|
end
|
322
322
|
|
323
|
+
# :nocov:
|
323
324
|
if Sequel.core_extensions?
|
324
325
|
class Hash
|
325
326
|
# Create a new HStore using the receiver as the input
|
@@ -343,3 +344,4 @@ if defined?(Sequel::CoreRefinements)
|
|
343
344
|
end
|
344
345
|
end
|
345
346
|
end
|
347
|
+
# :nocov:
|