sequel 3.44.0 → 3.45.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 +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:
|