sequel 5.19.0 → 5.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +102 -0
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +5 -1
- data/doc/release_notes/5.20.0.txt +89 -0
- data/doc/release_notes/5.21.0.txt +87 -0
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/sharding.rdoc +2 -0
- data/doc/testing.rdoc +1 -0
- data/doc/transactions.rdoc +38 -0
- data/lib/sequel/adapters/ado.rb +27 -19
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +2 -3
- data/lib/sequel/adapters/shared/mssql.rb +7 -7
- data/lib/sequel/adapters/shared/postgres.rb +37 -19
- data/lib/sequel/adapters/shared/sqlite.rb +27 -3
- data/lib/sequel/adapters/sqlite.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +12 -3
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/database/transactions.rb +57 -5
- data/lib/sequel/dataset.rb +4 -2
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
- data/lib/sequel/dataset/query.rb +5 -1
- data/lib/sequel/dataset/sql.rb +11 -7
- data/lib/sequel/extensions/named_timezones.rb +52 -8
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +387 -123
- data/lib/sequel/extensions/pg_range.rb +3 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/extensions/server_block.rb +15 -4
- data/lib/sequel/model/associations.rb +35 -9
- data/lib/sequel/model/plugins.rb +104 -0
- data/lib/sequel/plugins/association_dependencies.rb +3 -3
- data/lib/sequel/plugins/association_pks.rb +14 -4
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
- data/lib/sequel/plugins/composition.rb +13 -9
- data/lib/sequel/plugins/finder.rb +2 -2
- data/lib/sequel/plugins/hook_class_methods.rb +17 -5
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/inverted_subsets.rb +2 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
- data/lib/sequel/plugins/rcte_tree.rb +6 -0
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/subset_conditions.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +5 -3
- data/lib/sequel/sql.rb +15 -3
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +24 -0
- data/spec/adapters/mysql_spec.rb +0 -5
- data/spec/adapters/postgres_spec.rb +319 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +123 -2
- data/spec/core/dataset_spec.rb +33 -1
- data/spec/core/expression_filters_spec.rb +25 -1
- data/spec/core/schema_spec.rb +24 -0
- data/spec/extensions/class_table_inheritance_spec.rb +30 -8
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/hook_class_methods_spec.rb +22 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/migration_spec.rb +13 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
- data/spec/extensions/pg_json_spec.rb +218 -29
- data/spec/extensions/pg_range_spec.rb +76 -9
- data/spec/extensions/rcte_tree_spec.rb +6 -0
- data/spec/extensions/s_spec.rb +1 -1
- data/spec/extensions/schema_dumper_spec.rb +4 -2
- data/spec/extensions/server_block_spec.rb +38 -0
- data/spec/extensions/spec_helper.rb +8 -1
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/integration/dataset_test.rb +25 -9
- data/spec/integration/plugin_test.rb +42 -0
- data/spec/integration/schema_test.rb +7 -2
- data/spec/integration/transaction_test.rb +50 -0
- data/spec/model/associations_spec.rb +84 -4
- data/spec/model/plugins_spec.rb +111 -0
- metadata +16 -2
@@ -112,7 +112,7 @@ module Sequel
|
|
112
112
|
sqlite3_opts = {}
|
113
113
|
sqlite3_opts[:readonly] = typecast_value_boolean(opts[:readonly]) if opts.has_key?(:readonly)
|
114
114
|
db = ::SQLite3::Database.new(opts[:database].to_s, sqlite3_opts)
|
115
|
-
db.busy_timeout(opts.fetch(:timeout, 5000))
|
115
|
+
db.busy_timeout(typecast_value_integer(opts.fetch(:timeout, 5000)))
|
116
116
|
|
117
117
|
if USE_EXTENDED_RESULT_CODES
|
118
118
|
db.extended_result_codes = true
|
@@ -16,6 +16,18 @@ module Sequel
|
|
16
16
|
c = TinyTds::Client.new(opts)
|
17
17
|
c.query_options.merge!(:cache_rows=>false)
|
18
18
|
|
19
|
+
if opts[:ansi]
|
20
|
+
sql = %w(
|
21
|
+
ANSI_NULLS
|
22
|
+
ANSI_PADDING
|
23
|
+
ANSI_WARNINGS
|
24
|
+
ANSI_NULL_DFLT_ON
|
25
|
+
QUOTED_IDENTIFIER
|
26
|
+
CONCAT_NULL_YIELDS_NULL
|
27
|
+
).map{|v| "SET #{v} ON"}.join(";")
|
28
|
+
log_connection_yield(sql, c){c.execute(sql)}
|
29
|
+
end
|
30
|
+
|
19
31
|
if (ts = opts[:textsize])
|
20
32
|
sql = "SET TEXTSIZE #{typecast_value_integer(ts)}"
|
21
33
|
log_connection_yield(sql, c){c.execute(sql)}
|
@@ -35,7 +35,7 @@ module Sequel
|
|
35
35
|
# Yield to the block, logging any errors at error level to all loggers,
|
36
36
|
# and all other queries with the duration at warn or info level.
|
37
37
|
def log_connection_yield(sql, conn, args=nil)
|
38
|
-
return yield if
|
38
|
+
return yield if skip_logging?
|
39
39
|
sql = "#{connection_info(conn) if conn && log_connection_info}#{sql}#{"; #{args.inspect}" if args}"
|
40
40
|
timer = Sequel.start_timer
|
41
41
|
|
@@ -58,6 +58,12 @@ module Sequel
|
|
58
58
|
|
59
59
|
private
|
60
60
|
|
61
|
+
# Determine if logging should be skipped. Defaults to true if no loggers
|
62
|
+
# have been specified.
|
63
|
+
def skip_logging?
|
64
|
+
@loggers.empty?
|
65
|
+
end
|
66
|
+
|
61
67
|
# String including information about the connection, for use when logging
|
62
68
|
# connection info.
|
63
69
|
def connection_info(conn)
|
@@ -331,7 +331,7 @@ module Sequel
|
|
331
331
|
:time
|
332
332
|
when /\A(bool(ean)?)\z/io
|
333
333
|
:boolean
|
334
|
-
when /\A(real|float
|
334
|
+
when /\A(real|float( unsigned)?|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
|
335
335
|
:float
|
336
336
|
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?))\z/io
|
337
337
|
$1 && ['0', 'false'].include?($1) ? :integer : :decimal
|
@@ -110,6 +110,9 @@ module Sequel
|
|
110
110
|
# yet exist on referenced table (but will exist before the transaction commits).
|
111
111
|
# Basically it adds DEFERRABLE INITIALLY DEFERRED on key creation.
|
112
112
|
# If you use :immediate as the value, uses DEFERRABLE INITIALLY IMMEDIATE.
|
113
|
+
# :generated_always_as :: Specify a GENERATED ALWAYS AS column expression,
|
114
|
+
# if generated columns are supported (PostgreSQL 12+, MariaDB 5.2.0+,
|
115
|
+
# and MySQL 5.7.6+).
|
113
116
|
# :index :: Create an index on this column. If given a hash, use the hash as the
|
114
117
|
# options for the index.
|
115
118
|
# :key :: For foreign key columns, the column in the associated table
|
@@ -126,15 +129,21 @@ module Sequel
|
|
126
129
|
# be used if you have a single, nonautoincrementing primary key column
|
127
130
|
# (use the primary_key method in that case).
|
128
131
|
# :primary_key_constraint_name :: The name to give the primary key constraint
|
132
|
+
# :primary_key_deferrable :: Similar to :deferrable, but for the primary key constraint
|
133
|
+
# if :primary_key is used.
|
129
134
|
# :type :: Overrides the type given as the argument. Generally not used by column
|
130
135
|
# itself, but can be passed as an option to other methods that call column.
|
131
136
|
# :unique :: Mark the column as unique, generally has the same effect as
|
132
137
|
# creating a unique index on the column.
|
133
138
|
# :unique_constraint_name :: The name to give the unique key constraint
|
139
|
+
# :unique_deferrable :: Similar to :deferrable, but for the unique constraint if :unique
|
140
|
+
# is used.
|
141
|
+
#
|
142
|
+
# PostgreSQL specific options:
|
143
|
+
#
|
144
|
+
# :identity :: Create an identity column.
|
134
145
|
#
|
135
146
|
# MySQL specific options:
|
136
|
-
# :generated_always_as :: Specify a GENERATED ALWAYS AS column expression,
|
137
|
-
# if generated columns are supported.
|
138
147
|
# :generated_type :: Set the type of column when using :generated_always_as,
|
139
148
|
# should be :virtual or :stored to force a type.
|
140
149
|
def column(name, type, opts = OPTS)
|
@@ -634,7 +643,7 @@ module Sequel
|
|
634
643
|
|
635
644
|
# Drop a composite foreign key constraint
|
636
645
|
def drop_composite_foreign_key(columns, opts)
|
637
|
-
@operations <<
|
646
|
+
@operations << opts.merge(:op => :drop_constraint, :type => :foreign_key, :columns => columns)
|
638
647
|
nil
|
639
648
|
end
|
640
649
|
end
|
@@ -586,6 +586,7 @@ module Sequel
|
|
586
586
|
sql << " CONSTRAINT #{quote_identifier(name)}"
|
587
587
|
end
|
588
588
|
sql << ' PRIMARY KEY'
|
589
|
+
constraint_deferrable_sql_append(sql, column[:primary_key_deferrable])
|
589
590
|
end
|
590
591
|
end
|
591
592
|
|
@@ -606,6 +607,7 @@ module Sequel
|
|
606
607
|
sql << " CONSTRAINT #{quote_identifier(name)}"
|
607
608
|
end
|
608
609
|
sql << ' UNIQUE'
|
610
|
+
constraint_deferrable_sql_append(sql, column[:unique_deferrable])
|
609
611
|
end
|
610
612
|
end
|
611
613
|
|
@@ -25,13 +25,19 @@ module Sequel
|
|
25
25
|
# Otherwise, add the block to the list of blocks to call after the currently
|
26
26
|
# in progress transaction commits (and only if it commits).
|
27
27
|
# Options:
|
28
|
+
# :savepoint :: If currently inside a savepoint, only run this hook on transaction
|
29
|
+
# commit if all enclosing savepoints have been released.
|
28
30
|
# :server :: The server/shard to use.
|
29
31
|
def after_commit(opts=OPTS, &block)
|
30
32
|
raise Error, "must provide block to after_commit" unless block
|
31
33
|
synchronize(opts[:server]) do |conn|
|
32
34
|
if h = _trans(conn)
|
33
35
|
raise Error, "cannot call after_commit in a prepared transaction" if h[:prepare]
|
34
|
-
|
36
|
+
if opts[:savepoint] && in_savepoint?(conn)
|
37
|
+
add_savepoint_hook(conn, :after_commit, block)
|
38
|
+
else
|
39
|
+
add_transaction_hook(conn, :after_commit, block)
|
40
|
+
end
|
35
41
|
else
|
36
42
|
yield
|
37
43
|
end
|
@@ -42,13 +48,20 @@ module Sequel
|
|
42
48
|
# Otherwise, add the block to the list of the blocks to call after the currently
|
43
49
|
# in progress transaction rolls back (and only if it rolls back).
|
44
50
|
# Options:
|
51
|
+
# :savepoint :: If currently inside a savepoint, run this hook immediately when
|
52
|
+
# any enclosing savepoint is rolled back, which may be before the transaction
|
53
|
+
# commits or rollsback.
|
45
54
|
# :server :: The server/shard to use.
|
46
55
|
def after_rollback(opts=OPTS, &block)
|
47
56
|
raise Error, "must provide block to after_rollback" unless block
|
48
57
|
synchronize(opts[:server]) do |conn|
|
49
58
|
if h = _trans(conn)
|
50
59
|
raise Error, "cannot call after_rollback in a prepared transaction" if h[:prepare]
|
51
|
-
|
60
|
+
if opts[:savepoint] && in_savepoint?(conn)
|
61
|
+
add_savepoint_hook(conn, :after_rollback, block)
|
62
|
+
else
|
63
|
+
add_transaction_hook(conn, :after_rollback, block)
|
64
|
+
end
|
52
65
|
end
|
53
66
|
end
|
54
67
|
end
|
@@ -298,6 +311,13 @@ module Sequel
|
|
298
311
|
Sequel.synchronize{@transactions[conn] = hash}
|
299
312
|
end
|
300
313
|
|
314
|
+
# Set the given callable as a hook to be called. Type should be either
|
315
|
+
# :after_commit or :after_rollback.
|
316
|
+
def add_savepoint_hook(conn, type, block)
|
317
|
+
savepoint = _trans(conn)[:savepoints].last
|
318
|
+
(savepoint[type] ||= []) << block
|
319
|
+
end
|
320
|
+
|
301
321
|
# Set the given callable as a hook to be called. Type should be either
|
302
322
|
# :after_commit or :after_rollback.
|
303
323
|
def add_transaction_hook(conn, type, block)
|
@@ -401,6 +421,14 @@ module Sequel
|
|
401
421
|
supports_savepoints? && savepoint_level(conn) > 1
|
402
422
|
end
|
403
423
|
|
424
|
+
# Retrieve the savepoint hooks that should be run for the given
|
425
|
+
# connection and commit status.
|
426
|
+
def savepoint_hooks(conn, committed)
|
427
|
+
if in_savepoint?(conn)
|
428
|
+
_trans(conn)[:savepoints].last[committed ? :after_commit : :after_rollback]
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
404
432
|
# Retrieve the transaction hooks that should be run for the given
|
405
433
|
# connection and commit status.
|
406
434
|
def transaction_hooks(conn, committed)
|
@@ -411,16 +439,40 @@ module Sequel
|
|
411
439
|
|
412
440
|
# Remove the current thread from the list of active transactions
|
413
441
|
def remove_transaction(conn, committed)
|
414
|
-
|
442
|
+
if in_savepoint?(conn)
|
443
|
+
savepoint_callbacks = savepoint_hooks(conn, committed)
|
444
|
+
if committed
|
445
|
+
savepoint_rollback_callbacks = savepoint_hooks(conn, false)
|
446
|
+
end
|
447
|
+
else
|
448
|
+
callbacks = transaction_hooks(conn, committed)
|
449
|
+
end
|
415
450
|
|
416
451
|
if transaction_finished?(conn)
|
417
452
|
h = _trans(conn)
|
418
453
|
rolled_back = !committed
|
419
454
|
Sequel.synchronize{h[:rolled_back] = rolled_back}
|
420
455
|
Sequel.synchronize{@transactions.delete(conn)}
|
456
|
+
callbacks.each(&:call) if callbacks
|
457
|
+
elsif savepoint_callbacks || savepoint_rollback_callbacks
|
458
|
+
if committed
|
459
|
+
meth = in_savepoint?(conn) ? :add_savepoint_hook : :add_transaction_hook
|
460
|
+
|
461
|
+
if savepoint_callbacks
|
462
|
+
savepoint_callbacks.each do |block|
|
463
|
+
send(meth, conn, :after_commit, block)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
if savepoint_rollback_callbacks
|
468
|
+
savepoint_rollback_callbacks.each do |block|
|
469
|
+
send(meth, conn, :after_rollback, block)
|
470
|
+
end
|
471
|
+
end
|
472
|
+
else
|
473
|
+
savepoint_callbacks.each(&:call)
|
474
|
+
end
|
421
475
|
end
|
422
|
-
|
423
|
-
callbacks.each(&:call) if callbacks
|
424
476
|
end
|
425
477
|
|
426
478
|
# SQL to rollback to a savepoint
|
data/lib/sequel/dataset.rb
CHANGED
@@ -20,8 +20,10 @@ module Sequel
|
|
20
20
|
# old_posts = posts.where{stamp < Date.today - 7}
|
21
21
|
# davids_old_posts = davids_posts.where{stamp < Date.today - 7}
|
22
22
|
#
|
23
|
-
# Datasets are Enumerable objects, so they can be manipulated using
|
24
|
-
# of the Enumerable methods, such as map
|
23
|
+
# Datasets are Enumerable objects, so they can be manipulated using many
|
24
|
+
# of the Enumerable methods, such as +map+ and +inject+. Note that there are some methods
|
25
|
+
# that Dataset defines that override methods defined in Enumerable and result in different
|
26
|
+
# behavior, such as +select+ and +group_by+.
|
25
27
|
#
|
26
28
|
# For more information, see the {"Dataset Basics" guide}[rdoc-ref:doc/dataset_basics.rdoc].
|
27
29
|
class Dataset
|
@@ -333,6 +333,7 @@ module Sequel
|
|
333
333
|
# after every 50 records.
|
334
334
|
# :return :: When this is set to :primary_key, returns an array of
|
335
335
|
# autoincremented primary key values for the rows inserted.
|
336
|
+
# This does not have an effect if +values+ is a Dataset.
|
336
337
|
# :server :: Set the server/shard to use for the transaction and insert
|
337
338
|
# queries.
|
338
339
|
# :slice :: Same as :commit_every, :commit_every takes precedence.
|
@@ -1069,7 +1070,7 @@ module Sequel
|
|
1069
1070
|
|
1070
1071
|
# Set the server to use to :default unless it is already set in the passed opts
|
1071
1072
|
def default_server_opts(opts)
|
1072
|
-
if @db.sharded?
|
1073
|
+
if @db.sharded? && !opts.has_key?(:server)
|
1073
1074
|
opts = Hash[opts]
|
1074
1075
|
opts[:server] = @opts[:server] || :default
|
1075
1076
|
end
|
@@ -1080,7 +1081,7 @@ module Sequel
|
|
1080
1081
|
# :read_only server unless a specific server is set.
|
1081
1082
|
def execute(sql, opts=OPTS, &block)
|
1082
1083
|
db = @db
|
1083
|
-
if db.sharded?
|
1084
|
+
if db.sharded? && !opts.has_key?(:server)
|
1084
1085
|
opts = Hash[opts]
|
1085
1086
|
opts[:server] = @opts[:server] || (@opts[:lock] ? :default : :read_only)
|
1086
1087
|
opts
|
@@ -170,7 +170,10 @@ module Sequel
|
|
170
170
|
# receiver's dataset to the block, and the block should return the new dataset
|
171
171
|
# to use.
|
172
172
|
def with_dataset
|
173
|
-
|
173
|
+
dataset = yield @dataset
|
174
|
+
other = dup
|
175
|
+
other.instance_variable_set(:@dataset, dataset)
|
176
|
+
other.freeze
|
174
177
|
end
|
175
178
|
|
176
179
|
# Return an array of all objects by running the SQL query for the given arguments.
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -1062,6 +1062,10 @@ module Sequel
|
|
1062
1062
|
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
1063
1063
|
# :recursive :: Specify that this is a recursive CTE
|
1064
1064
|
#
|
1065
|
+
# PostgreSQL Specific Options:
|
1066
|
+
# :materialized :: Set to false to force inlining of the CTE, or true to force not inlining
|
1067
|
+
# the CTE (PostgreSQL 12+).
|
1068
|
+
#
|
1065
1069
|
# DB[:items].with(:items, DB[:syx].where(Sequel[:name].like('A%')))
|
1066
1070
|
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%' ESCAPE '\')) SELECT * FROM items
|
1067
1071
|
def with(name, dataset, opts=OPTS)
|
@@ -1091,7 +1095,7 @@ module Sequel
|
|
1091
1095
|
# # SELECT i1.id, i1.parent_id FROM i1 INNER JOIN t ON (t.id = i1.parent_id)
|
1092
1096
|
# # ) SELECT * FROM t
|
1093
1097
|
def with_recursive(name, nonrecursive, recursive, opts=OPTS)
|
1094
|
-
raise(Error, 'This
|
1098
|
+
raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
|
1095
1099
|
if hoist_cte?(nonrecursive)
|
1096
1100
|
s, ds = hoist_cte(nonrecursive)
|
1097
1101
|
s.with_recursive(name, ds, recursive, opts)
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1510,13 +1510,7 @@ module Sequel
|
|
1510
1510
|
comma = ', '
|
1511
1511
|
ws.each do |w|
|
1512
1512
|
sql << comma if c
|
1513
|
-
|
1514
|
-
if args = w[:args]
|
1515
|
-
sql << '('
|
1516
|
-
identifier_list_append(sql, args)
|
1517
|
-
sql << ')'
|
1518
|
-
end
|
1519
|
-
sql << ' AS '
|
1513
|
+
select_with_sql_prefix(sql, w)
|
1520
1514
|
literal_dataset_append(sql, w[:dataset])
|
1521
1515
|
c ||= true
|
1522
1516
|
end
|
@@ -1530,6 +1524,16 @@ module Sequel
|
|
1530
1524
|
"WITH "
|
1531
1525
|
end
|
1532
1526
|
|
1527
|
+
def select_with_sql_prefix(sql, w)
|
1528
|
+
quote_identifier_append(sql, w[:name])
|
1529
|
+
if args = w[:args]
|
1530
|
+
sql << '('
|
1531
|
+
identifier_list_append(sql, args)
|
1532
|
+
sql << ')'
|
1533
|
+
end
|
1534
|
+
sql << ' AS '
|
1535
|
+
end
|
1536
|
+
|
1533
1537
|
# Whether the symbol cache should be skipped when literalizing the dataset
|
1534
1538
|
def skip_symbol_cache?
|
1535
1539
|
@opts[:skip_symbol_cache]
|
@@ -2,18 +2,21 @@
|
|
2
2
|
#
|
3
3
|
# Allows the use of named timezones via TZInfo (requires tzinfo).
|
4
4
|
# Forces the use of DateTime as Sequel's datetime_class, since
|
5
|
-
#
|
6
|
-
# and UTC.
|
5
|
+
# historically, Ruby's Time class doesn't support timezones other
|
6
|
+
# than local and UTC. To continue using Ruby's Time class when using
|
7
|
+
# the named_timezones extension:
|
8
|
+
#
|
9
|
+
# # Load the extension
|
10
|
+
# Sequel.extension :named_timezones
|
11
|
+
#
|
12
|
+
# # Set Sequel.datetime_class back to Time
|
13
|
+
# Sequel.datetime_class = Time
|
7
14
|
#
|
8
15
|
# This allows you to either pass strings or TZInfo::Timezone
|
9
16
|
# instance to Sequel.database_timezone=, application_timezone=, and
|
10
17
|
# typecast_timezone=. If a string is passed, it is converted to a
|
11
18
|
# TZInfo::Timezone using TZInfo::Timezone.get.
|
12
19
|
#
|
13
|
-
# To load the extension:
|
14
|
-
#
|
15
|
-
# Sequel.extension :named_timezones
|
16
|
-
#
|
17
20
|
# Let's say you have the database server in New York and the
|
18
21
|
# application server in Los Angeles. For historical reasons, data
|
19
22
|
# is stored in local New York time, but the application server only
|
@@ -37,7 +40,8 @@
|
|
37
40
|
# Note that typecasting from the database timezone to the application
|
38
41
|
# timezone when fetching rows is dependent on the database adapter,
|
39
42
|
# and only works on adapters where Sequel itself does the conversion.
|
40
|
-
# It should work
|
43
|
+
# It should work with the mysql, postgres, sqlite, ibmdb, and jdbc
|
44
|
+
# adapters.
|
41
45
|
#
|
42
46
|
# Related module: Sequel::NamedTimezones
|
43
47
|
|
@@ -63,9 +67,48 @@ module Sequel
|
|
63
67
|
|
64
68
|
private
|
65
69
|
|
70
|
+
if RUBY_VERSION >= '2.6'
|
71
|
+
# Convert the given input Time (which must be in UTC) to the given input timezone,
|
72
|
+
# which should be a TZInfo::Timezone instance.
|
73
|
+
def convert_input_time_other(v, input_timezone)
|
74
|
+
Time.new(v.year, v.mon, v.day, v.hour, v.min, (v.sec + Rational(v.nsec, 1000000000)), input_timezone)
|
75
|
+
rescue TZInfo::AmbiguousTime
|
76
|
+
raise unless disamb = tzinfo_disambiguator_for(v)
|
77
|
+
period = input_timezone.period_for_local(v, &disamb)
|
78
|
+
offset = period.utc_total_offset
|
79
|
+
Time.at(v.to_i - offset, :in => input_timezone)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Convert the given input Time to the given output timezone,
|
83
|
+
# which should be a TZInfo::Timezone instance.
|
84
|
+
def convert_output_time_other(v, output_timezone)
|
85
|
+
Time.at(v.to_i, :in => output_timezone)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
# :nodoc:
|
89
|
+
# :nocov:
|
90
|
+
def convert_input_time_other(v, input_timezone)
|
91
|
+
local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
92
|
+
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
|
96
|
+
def convert_output_time_other(v, output_timezone)
|
97
|
+
v = output_timezone.utc_to_local(v.getutc)
|
98
|
+
local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
99
|
+
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + local_offset
|
100
|
+
end
|
101
|
+
else
|
102
|
+
def convert_output_time_other(v, output_timezone)
|
103
|
+
v = output_timezone.utc_to_local(v.getutc)
|
104
|
+
local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
105
|
+
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
66
110
|
# Handle both TZInfo 1 and TZInfo 2
|
67
111
|
if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
|
68
|
-
# :nodoc:
|
69
112
|
def convert_input_datetime_other(v, input_timezone)
|
70
113
|
local_offset = Rational(input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset, 86400)
|
71
114
|
(v - local_offset).new_offset(local_offset)
|
@@ -78,6 +121,7 @@ module Sequel
|
|
78
121
|
DateTime.jd(v.jd, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
|
79
122
|
end
|
80
123
|
# :nodoc:
|
124
|
+
# :nocov:
|
81
125
|
else
|
82
126
|
# Assume the given DateTime has a correct time but a wrong timezone. It is
|
83
127
|
# currently in UTC timezone, but it should be converted to the input_timezone.
|
@@ -340,14 +340,18 @@ module Sequel
|
|
340
340
|
raise Sequel::Error, "invalid array, empty string" if eos?
|
341
341
|
raise Sequel::Error, "invalid array, doesn't start with {" unless scan(/((\[\d+:\d+\])+=)?\{/)
|
342
342
|
|
343
|
+
# :nocov:
|
343
344
|
while !eos?
|
345
|
+
# :nocov:
|
344
346
|
char = scan(/[{}",]|[^{}",]+/)
|
345
347
|
if char == ','
|
346
348
|
# Comma outside quoted string indicates end of current entry
|
347
349
|
new_entry
|
348
350
|
elsif char == '"'
|
349
351
|
raise Sequel::Error, "invalid array, opening quote with existing recorded data" unless @recorded.empty?
|
352
|
+
# :nocov:
|
350
353
|
while true
|
354
|
+
# :nocov:
|
351
355
|
char = scan(/["\\]|[^"\\]+/)
|
352
356
|
if char == '\\'
|
353
357
|
@recorded << getch
|