sequel 5.22.0 → 5.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +78 -0
  3. data/README.rdoc +1 -1
  4. data/doc/dataset_filtering.rdoc +15 -0
  5. data/doc/opening_databases.rdoc +3 -0
  6. data/doc/postgresql.rdoc +2 -2
  7. data/doc/release_notes/5.23.0.txt +56 -0
  8. data/doc/release_notes/5.24.0.txt +56 -0
  9. data/doc/release_notes/5.25.0.txt +32 -0
  10. data/doc/release_notes/5.26.0.txt +35 -0
  11. data/doc/release_notes/5.27.0.txt +21 -0
  12. data/doc/testing.rdoc +1 -0
  13. data/lib/sequel/adapters/jdbc.rb +7 -1
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  15. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  16. data/lib/sequel/adapters/mysql2.rb +0 -1
  17. data/lib/sequel/adapters/shared/mssql.rb +9 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +30 -7
  19. data/lib/sequel/adapters/shared/sqlite.rb +23 -4
  20. data/lib/sequel/adapters/tinytds.rb +12 -0
  21. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  22. data/lib/sequel/database/logging.rb +7 -1
  23. data/lib/sequel/database/schema_generator.rb +11 -2
  24. data/lib/sequel/database/schema_methods.rb +2 -0
  25. data/lib/sequel/dataset/actions.rb +3 -2
  26. data/lib/sequel/dataset/features.rb +6 -0
  27. data/lib/sequel/dataset/sql.rb +17 -4
  28. data/lib/sequel/extensions/named_timezones.rb +51 -9
  29. data/lib/sequel/extensions/pg_array.rb +4 -0
  30. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  31. data/lib/sequel/extensions/pg_enum.rb +4 -1
  32. data/lib/sequel/extensions/pg_json.rb +88 -17
  33. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  34. data/lib/sequel/extensions/pg_range.rb +9 -0
  35. data/lib/sequel/extensions/pg_row.rb +3 -1
  36. data/lib/sequel/extensions/sql_comments.rb +2 -2
  37. data/lib/sequel/model/base.rb +12 -5
  38. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  39. data/lib/sequel/plugins/association_proxies.rb +3 -2
  40. data/lib/sequel/plugins/caching.rb +3 -0
  41. data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
  42. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  43. data/lib/sequel/plugins/dirty.rb +3 -9
  44. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  45. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  46. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  47. data/lib/sequel/plugins/sharding.rb +11 -5
  48. data/lib/sequel/plugins/static_cache.rb +8 -3
  49. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  51. data/lib/sequel/sql.rb +4 -1
  52. data/lib/sequel/timezones.rb +50 -11
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +135 -7
  55. data/spec/adapters/sqlite_spec.rb +1 -1
  56. data/spec/bin_spec.rb +2 -2
  57. data/spec/core/database_spec.rb +50 -0
  58. data/spec/core/dataset_spec.rb +23 -1
  59. data/spec/core/expression_filters_spec.rb +22 -3
  60. data/spec/core/schema_spec.rb +18 -0
  61. data/spec/core/spec_helper.rb +1 -1
  62. data/spec/core_extensions_spec.rb +1 -1
  63. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  64. data/spec/extensions/dirty_spec.rb +33 -0
  65. data/spec/extensions/insert_conflict_spec.rb +103 -0
  66. data/spec/extensions/named_timezones_spec.rb +109 -2
  67. data/spec/extensions/nested_attributes_spec.rb +48 -0
  68. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  69. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  70. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  71. data/spec/extensions/pg_json_spec.rb +12 -0
  72. data/spec/extensions/pg_range_spec.rb +19 -2
  73. data/spec/extensions/sharding_spec.rb +8 -0
  74. data/spec/extensions/spec_helper.rb +9 -2
  75. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  76. data/spec/guards_helper.rb +1 -1
  77. data/spec/integration/dataset_test.rb +25 -0
  78. data/spec/integration/plugin_test.rb +28 -1
  79. data/spec/integration/schema_test.rb +16 -2
  80. data/spec/integration/spec_helper.rb +7 -1
  81. data/spec/model/spec_helper.rb +1 -1
  82. metadata +32 -2
@@ -36,7 +36,6 @@ module Sequel
36
36
  # options such as :local_infile.
37
37
  def connect(server)
38
38
  opts = server_opts(server)
39
- opts[:host] ||= 'localhost'
40
39
  opts[:username] ||= opts.delete(:user)
41
40
  opts[:flags] ||= 0
42
41
  opts[:flags] |= ::Mysql2::Client::FOUND_ROWS if ::Mysql2::Client.const_defined?(:FOUND_ROWS)
@@ -279,7 +279,7 @@ module Sequel
279
279
  end
280
280
  end
281
281
  sqls << "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{column_definition_sql(op)}"
282
- sqls << alter_table_sql(table, op.merge(:op=>:set_column_default, :default=>default)) if default
282
+ sqls << alter_table_sql(table, op.merge(:op=>:set_column_default, :default=>default, :skip_drop_default=>true)) if default
283
283
  sqls
284
284
  when :set_column_null
285
285
  sch = schema(table).find{|k,v| k.to_s == op[:name].to_s}.last
@@ -290,7 +290,9 @@ module Sequel
290
290
  end
291
291
  "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} #{type_literal(:type=>type)} #{'NOT ' unless op[:null]}NULL"
292
292
  when :set_column_default
293
- "ALTER TABLE #{quote_schema_table(table)} ADD CONSTRAINT #{quote_identifier("sequel_#{table}_#{op[:name]}_def")} DEFAULT #{literal(op[:default])} FOR #{quote_identifier(op[:name])}"
293
+ sqls = []
294
+ add_drop_default_constraint_sql(sqls, table, op[:name]) unless op[:skip_drop_default]
295
+ sqls << "ALTER TABLE #{quote_schema_table(table)} ADD CONSTRAINT #{quote_identifier("sequel_#{table}_#{op[:name]}_def")} DEFAULT #{literal(op[:default])} FOR #{quote_identifier(op[:name])}"
294
296
  else
295
297
  super(table, op)
296
298
  end
@@ -427,20 +429,19 @@ module Sequel
427
429
  m = output_identifier_meth(opts[:dataset])
428
430
  m2 = input_identifier_meth(opts[:dataset])
429
431
  tn = m2.call(table_name.to_s)
430
- table_id = get(Sequel.function(:object_id, tn))
431
432
  info_sch_sch = opts[:information_schema_schema]
432
433
  inf_sch_qual = lambda{|s| info_sch_sch ? Sequel.qualify(info_sch_sch, s) : Sequel[s]}
433
- sys_qual = lambda{|s| info_sch_sch ? Sequel.qualify(info_sch_sch, Sequel.qualify(Sequel.lit(''), s)) : Sequel[s]}
434
+ table_id = metadata_dataset.from(inf_sch_qual.call(Sequel[:sys][:objects])).where(:name => tn).select_map(:object_id).first
434
435
 
435
- identity_cols = metadata_dataset.from(Sequel.lit('[sys].[columns]')).
436
+ identity_cols = metadata_dataset.from(inf_sch_qual.call(Sequel[:sys][:columns])).
436
437
  where(:object_id=>table_id, :is_identity=>true).
437
438
  select_map(:name)
438
439
 
439
- pk_index_id = metadata_dataset.from(sys_qual.call(Sequel.lit('sysindexes'))).
440
+ pk_index_id = metadata_dataset.from(inf_sch_qual.call(Sequel[:sys][:sysindexes])).
440
441
  where(:id=>table_id, :indid=>1..254){{(status & 2048)=>2048}}.
441
442
  get(:indid)
442
- pk_cols = metadata_dataset.from(sys_qual.call(Sequel.lit('sysindexkeys')).as(:sik)).
443
- join(sys_qual.call(Sequel.lit('syscolumns')).as(:sc), :id=>:id, :colid=>:colid).
443
+ pk_cols = metadata_dataset.from(inf_sch_qual.call(Sequel[:sys][:sysindexkeys]).as(:sik)).
444
+ join(inf_sch_qual.call(Sequel[:sys][:syscolumns]).as(:sc), :id=>:id, :colid=>:colid).
444
445
  where{{sik[:id]=>table_id, sik[:indid]=>pk_index_id}}.
445
446
  select_order_map{sc[:name]}
446
447
 
@@ -97,13 +97,17 @@ module Sequel
97
97
  # Add an exclusion constraint when creating the table. Elements should be
98
98
  # an array of 2 element arrays, with the first element being the column or
99
99
  # expression the exclusion constraint is applied to, and the second element
100
- # being the operator to use for the column/expression to check for exclusion.
101
- #
102
- # Example:
100
+ # being the operator to use for the column/expression to check for exclusion:
103
101
  #
104
102
  # exclude([[:col1, '&&'], [:col2, '=']])
105
103
  # # EXCLUDE USING gist (col1 WITH &&, col2 WITH =)
106
104
  #
105
+ # To use a custom operator class, you need to use Sequel.lit with the expression
106
+ # and operator class:
107
+ #
108
+ # exclude([[Sequel.lit('col1 inet_ops'), '&&'], [:col2, '=']])
109
+ # # EXCLUDE USING gist (col1 inet_ops WITH &&, col2 WITH =)
110
+ #
107
111
  # Options supported:
108
112
  #
109
113
  # :name :: Name the constraint with the given name (useful if you may
@@ -836,10 +840,14 @@ module Sequel
836
840
  # default value is given.
837
841
  def column_definition_default_sql(sql, column)
838
842
  super
839
- if !column[:serial] && !['serial', 'bigserial'].include?(column[:type].to_s) && !column[:default] && (identity = column[:identity])
840
- sql << " GENERATED "
841
- sql << (identity == :always ? "ALWAYS" : "BY DEFAULT")
842
- sql << " AS IDENTITY"
843
+ if !column[:serial] && !['serial', 'bigserial'].include?(column[:type].to_s) && !column[:default]
844
+ if (identity = column[:identity])
845
+ sql << " GENERATED "
846
+ sql << (identity == :always ? "ALWAYS" : "BY DEFAULT")
847
+ sql << " AS IDENTITY"
848
+ elsif (generated = column[:generated_always_as])
849
+ sql << " GENERATED ALWAYS AS (#{literal(generated)}) STORED"
850
+ end
843
851
  end
844
852
  end
845
853
 
@@ -1798,6 +1806,16 @@ module Sequel
1798
1806
  end
1799
1807
  end
1800
1808
 
1809
+ # Include aliases when inserting into a single table on PostgreSQL 9.5+.
1810
+ def insert_into_sql(sql)
1811
+ sql << " INTO "
1812
+ if (f = @opts[:from]) && f.length == 1
1813
+ identifier_append(sql, server_version >= 90500 ? f.first : unaliased_identifier(f.first))
1814
+ else
1815
+ source_list_append(sql, f)
1816
+ end
1817
+ end
1818
+
1801
1819
  # Return the primary key to use for RETURNING in an INSERT statement
1802
1820
  def insert_pk
1803
1821
  if (f = opts[:from]) && !f.empty?
@@ -1937,6 +1955,11 @@ module Sequel
1937
1955
  db.server_version(@opts[:server])
1938
1956
  end
1939
1957
 
1958
+ # PostgreSQL 9.4+ supports the FILTER clause for aggregate functions.
1959
+ def supports_filtered_aggregates?
1960
+ server_version >= 90400
1961
+ end
1962
+
1940
1963
  # PostgreSQL supports quoted function names.
1941
1964
  def supports_quoted_function_names?
1942
1965
  true
@@ -10,6 +10,10 @@ module Sequel
10
10
  def self.mock_adapter_setup(db)
11
11
  db.instance_exec do
12
12
  @sqlite_version = 30903
13
+
14
+ def schema_parse_table(*)
15
+ []
16
+ end
13
17
  end
14
18
  end
15
19
 
@@ -60,7 +64,7 @@ module Sequel
60
64
  def foreign_key_list(table, opts=OPTS)
61
65
  m = output_identifier_meth
62
66
  h = {}
63
- metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
67
+ _foreign_key_list_ds(table).each do |row|
64
68
  if r = h[row[:id]]
65
69
  r[:columns] << m.call(row[:from])
66
70
  r[:key] << m.call(row[:to]) if r[:key]
@@ -173,6 +177,16 @@ module Sequel
173
177
 
174
178
  private
175
179
 
180
+ # Dataset used for parsing foreign key lists
181
+ def _foreign_key_list_ds(table)
182
+ metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table))
183
+ end
184
+
185
+ # Dataset used for parsing schema
186
+ def _parse_pragma_ds(table_name, opts)
187
+ metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
188
+ end
189
+
176
190
  # Run all alter_table commands in a transaction. This is technically only
177
191
  # needed for drop column.
178
192
  def apply_alter_table(table, ops)
@@ -445,7 +459,7 @@ module Sequel
445
459
  # Parse the output of the table_info pragma
446
460
  def parse_pragma(table_name, opts)
447
461
  pks = 0
448
- sch = metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name)).map do |row|
462
+ sch = _parse_pragma_ds(table_name, opts).map do |row|
449
463
  row.delete(:cid)
450
464
  row[:allow_null] = row.delete(:notnull).to_i == 0
451
465
  row[:default] = row.delete(:dflt_value)
@@ -855,9 +869,9 @@ module Sequel
855
869
  end
856
870
  end
857
871
 
858
- # SQLite does not natively support NULLS FIRST/LAST.
872
+ # SQLite supports NULLS FIRST/LAST natively in 3.30+.
859
873
  def requires_emulating_nulls_first?
860
- true
874
+ db.sqlite_version < 33000
861
875
  end
862
876
 
863
877
  # SQLite does not support FOR UPDATE, but silently ignore it
@@ -883,6 +897,11 @@ module Sequel
883
897
  false
884
898
  end
885
899
 
900
+ # SQLite 3.30 supports the FILTER clause for aggregate functions.
901
+ def supports_filtered_aggregates?
902
+ db.sqlite_version >= 33000
903
+ end
904
+
886
905
  # SQLite supports quoted function names.
887
906
  def supports_quoted_function_names?
888
907
  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)}
@@ -55,7 +55,7 @@ module Sequel
55
55
  NotNullConstraintViolation
56
56
  when 1062
57
57
  UniqueConstraintViolation
58
- when 1451, 1452
58
+ when 1451, 1452, 1216, 1217
59
59
  ForeignKeyConstraintViolation
60
60
  when 4025
61
61
  CheckConstraintViolation
@@ -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 @loggers.empty?
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)
@@ -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)
@@ -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
 
@@ -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
@@ -230,6 +230,12 @@ module Sequel
230
230
  supports_cte_in_subqueries?
231
231
  end
232
232
 
233
+ # Whether the dataset supports the FILTER clause for aggregate functions.
234
+ # If not, support is emulated using CASE.
235
+ def supports_filtered_aggregates?
236
+ false
237
+ end
238
+
233
239
  # Whether the database supports quoting function names.
234
240
  def supports_quoted_function_names?
235
241
  false
@@ -492,11 +492,24 @@ module Sequel
492
492
  end
493
493
 
494
494
  sql << '('
495
+ if filter = opts[:filter]
496
+ filter = filter_expr(filter, &opts[:filter_block])
497
+ end
495
498
  if opts[:*]
496
- sql << '*'
499
+ if filter && !supports_filtered_aggregates?
500
+ literal_append(sql, Sequel.case({filter=>1}, nil))
501
+ filter = nil
502
+ else
503
+ sql << '*'
504
+ end
497
505
  else
498
506
  sql << "DISTINCT " if opts[:distinct]
499
- expression_list_append(sql, f.args)
507
+ if filter && !supports_filtered_aggregates?
508
+ expression_list_append(sql, f.args.map{|arg| Sequel.case({filter=>arg}, nil)})
509
+ filter = nil
510
+ else
511
+ expression_list_append(sql, f.args)
512
+ end
500
513
  if order = opts[:order]
501
514
  sql << " ORDER BY "
502
515
  expression_list_append(sql, order)
@@ -510,9 +523,9 @@ module Sequel
510
523
  sql << ')'
511
524
  end
512
525
 
513
- if filter = opts[:filter]
526
+ if filter
514
527
  sql << " FILTER (WHERE "
515
- literal_append(sql, filter_expr(filter, &opts[:filter_block]))
528
+ literal_append(sql, filter)
516
529
  sql << ')'
517
530
  end
518
531
 
@@ -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
- # ruby's Time class doesn't support timezones other than local
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 on mysql, postgres, sqlite, ibmdb, and jdbc.
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,10 +67,48 @@ module Sequel
63
67
 
64
68
  private
65
69
 
66
- # Handle both TZInfo 1 and TZInfo 2
67
- if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
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
68
88
  # :nodoc:
69
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
+
110
+ # Handle both TZInfo 1 and TZInfo 2
111
+ if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
70
112
  def convert_input_datetime_other(v, input_timezone)
71
113
  local_offset = Rational(input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset, 86400)
72
114
  (v - local_offset).new_offset(local_offset)