sequel 5.75.0 → 5.76.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3047129164c9cdffee31414419904329e410db71e7e4243c0549614498a2b0d4
4
- data.tar.gz: 9cc84ed8e9dba53aac7175be04699089932566e4015aed7a465c43552808c8b4
3
+ metadata.gz: ca7f95e54ed437de173bbdfac08f4091f0e8debfb1a89bb168cd6157341afa9d
4
+ data.tar.gz: f49e07aec7ae48c509abf3965bc10ec102616d5ccc99ecb49d0116a9162760af
5
5
  SHA512:
6
- metadata.gz: 15ed5c13e4793191546cd452aa5865e65fd7b3bb0458ffdb674d526398b7c9446e2da0f43a339daa3f1dc256eb551f0b773ac9ffc192b4d19f389d0c7e296d98
7
- data.tar.gz: 1f25851b50857fbbf8378216794f26d0d8e9baa247f008feac466a218ae6b0777249252b6d05756e075096c5312b9d3b1334b7c4bc1884251e7b892cc17ae7c6
6
+ metadata.gz: 9b8453bf85f48038160481a3b795a692a670b3e73084e6f531f353739c2549f809af5d45368db4dbcc0ac0bbd318eb4a94a02d0289d8c09117641f0ea4dc493d
7
+ data.tar.gz: a7daa11f1dc3d757f1618f4e8ee832c33e7072f375cfeea028e68b340414a159c92d05d76d75fd9bbb35cb498699ed9db32c9e7cbb158bb2b8a88e598ade34e6
data/CHANGELOG CHANGED
@@ -1,3 +1,31 @@
1
+ === 5.76.0 (2024-01-01)
2
+
3
+ * Improve performance and flexibility of regexp matching in sqlite adapter (paddor) (#2108)
4
+
5
+ * Support SQL::Identifier for Database#tables :schema option values on PostgreSQL (jeremyevans)
6
+
7
+ * Support generating rcte queries using UNION or UNION ALL in the rcte plugin (jonathanfrias) (#2107)
8
+
9
+ * Make Database#table_exists? on PostgreSQL handle lock or statement timeout errors as evidence the table exists (jeremyevans) (#2106)
10
+
11
+ * Work around DateTime.jd fractional second bug on JRuby in named_timezones extension (jeremyevans)
12
+
13
+ * Support fractional times and timestamps on SQLAnywhere (jeremyevans)
14
+
15
+ * Make round_timestamps extension use Dataset#sqltime_precision for rounding Sequel::SQLTime values (jeremyevans)
16
+
17
+ * Remove special handling of %N modifier in Dataset#default_timestamp_format (jeremyevans)
18
+
19
+ * Add Dataset#default_time_format private method, for adapters to override for time (not timestamp) formatting (jeremyevans)
20
+
21
+ * Remove Dataset#format_timestamp_offset private method (jeremyevans)
22
+
23
+ * Remove special handling of %z modifier in Dataset#default_timestamp_format (jeremyevans)
24
+
25
+ * Add Dataset#literal_date_or_time, for simpler use by bound argument code (jeremyevans)
26
+
27
+ * Add auto_cast_date_and_time extension, for casting date and time values using SQL standard functions (jeremyevans)
28
+
1
29
  === 5.75.0 (2023-12-01)
2
30
 
3
31
  * Make any_not_empty? extension support passing pattern argument to any? (jeremyevans) (#2100)
@@ -388,8 +388,10 @@ The following additional options are supported:
388
388
  :readonly :: open database in read-only mode
389
389
  :timeout :: the busy timeout to use in milliseconds (default: 5000).
390
390
  :setup_regexp_function :: Whether to setup a REGEXP function in the underlying SQLite3::Database object. Doing so
391
- allows you to use regexp support in dataset expressions. Note that this creates a new
392
- Regexp object per call to the function, so it is not an efficient implementation.
391
+ allows you to use regexp support in dataset expressions. If +:cached+ or <tt>"cached"</tt>+, caches each
392
+ unique regex (more efficient but risk of memory leak). If a Proc is provided, it will be called with
393
+ a string for the regexp and a string for the value to compare, and should return whether the regexp
394
+ string matches the string value to compare. The default Proc used does <tt>Regexp.new(regexp_str).match(str)</tt>.
393
395
 
394
396
  Note that SQLite memory databases are restricted to a single connection by
395
397
  default. This is because SQLite does not allow multiple connections to
@@ -0,0 +1,86 @@
1
+ = New Features
2
+
3
+ * An auto_cast_date_and_time extension has been added, which will
4
+ automatically cast date and time values using SQL standard functions.
5
+ This makes sure the database will treat the value as a date, time,
6
+ or timestamp, instead of treating it as a string or unknown type:
7
+
8
+ DB.get(Date.today).class
9
+ # SELECT '2024-01-01' AS v LIMIT 1
10
+ String
11
+
12
+ DB.extension(:auto_cast_date_and_time)
13
+ DB.get(Date.today).class
14
+ # SELECT DATE '2024-01-01' AS v LIMIT 1
15
+ Date
16
+
17
+ This was already Sequel's default behavior on adapters that required
18
+ it. This extension is usable on PostgreSQL and MySQL. It is not
19
+ usable on SQLite (no date/time types) or Microsoft SQL Server (no
20
+ support for the SQL standard conversion syntax).
21
+
22
+ This extension can break code that currently works. If using it on
23
+ PostgreSQL, it will cast the values to TIMESTAMP, not TIMESTAMP
24
+ WITH TIME ZONE, which can break code that depended on an implicit
25
+ conversion to TIMESTAMP WITH TIME ZONE. The pg_timestamptz
26
+ extension integrates with the the auto_cast_date_and_time extension
27
+ and will implicitly cast Time/DateTime to TIMESTAMP WITH TIME ZONE.
28
+
29
+ * The sqlite adapter now supports a :cached value for the
30
+ :setup_regexp_function Database option, which will cache regexp
31
+ values instead of creating a new regexp per value to compare. This
32
+ is much faster when using a regexp comparison on a large dataset,
33
+ but can result in a memory leak if using dynamic regexps. You can
34
+ also provide a Proc value for the :setup_regexp_function option,
35
+ which will be passed both the regexp source string and the database
36
+ string to compare, and should return whether the database string
37
+ matches the regexp string.
38
+
39
+ * The rcte_tree plugin now supports a :union_all option, which can
40
+ be set to false to use UNION instead of UNION ALL in the recursive
41
+ common table expression.
42
+
43
+ = Other Improvements
44
+
45
+ * Time/DateTime/SQLite literalization speed has more than doubled
46
+ compared to the previous version. The internal code is also much
47
+ simpler, as the speedup resulted from removing multiple abstraction
48
+ layers that mostly existed for Ruby 1.8 support.
49
+
50
+ * Database#table_exists? on PostgreSQL now handles lock or statement
51
+ timeout errors as evidence the table exists.
52
+
53
+ * The round_timestamps extension now correctly rounds SQLTime values
54
+ on Microsoft SQL Server (the only database Sequel supports where
55
+ time precision is different than timestamp precision).
56
+
57
+ * Fractional times and timestamps are now supported on SQLAnywhere,
58
+ except for time values when using the jdbc adapter due to a
59
+ limitation in the JDBC sqlanywhere driver.
60
+
61
+ * Database#tables and #views on PostgreSQL now supports
62
+ SQL::Identifier values for the :schema option.
63
+
64
+ * The named_timezones extension now works around a bug in DateTime.jd
65
+ on JRuby.
66
+
67
+ = Backwards Compatibility
68
+
69
+ * Time/DateTime/SQLTime literalization internals have changed.
70
+ If you are using an external adapter and the external adapter
71
+ overrides or calls any of the following methods:
72
+
73
+ * requires_sql_standard_datetimes?
74
+ * supports_timestamp_usecs?
75
+ * supports_timestamp_timezones?
76
+ * timestamp_precision
77
+ * sqltime_precision
78
+
79
+ then the adapter may need to be updated to support Sequel 5.76.0.
80
+ Additionally, if the adapter uses %N or %z in
81
+ default_timestamp_format, it may need to be updated. Adapters
82
+ should now just override default_timestamp_format and/or
83
+ default_time_format methods as appropriate for the database.
84
+
85
+ * The Dataset#format_timestamp_offset private method has been
86
+ removed.
data/doc/testing.rdoc CHANGED
@@ -159,6 +159,7 @@ The SEQUEL_INTEGRATION_URL environment variable specifies the Database connectio
159
159
 
160
160
  === Other
161
161
 
162
+ SEQUEL_AUTO_CAST_DATE_TIME :: Use the auto_cast_date_and_time extension when running the specs
162
163
  SEQUEL_ASYNC_THREAD_POOL :: Use the async_thread_pool extension when running the specs
163
164
  SEQUEL_ASYNC_THREAD_POOL_PREEMPT :: Use the async_thread_pool extension when running the specs, with the :preempt_async_thread option
164
165
  SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  Sequel::JDBC.load_driver('org.h2.Driver', :H2)
4
+ require_relative '../../extensions/auto_cast_date_and_time'
4
5
 
5
6
  module Sequel
6
7
  module JDBC
@@ -14,6 +15,8 @@ module Sequel
14
15
 
15
16
  module H2
16
17
  module DatabaseMethods
18
+ include AutoCastDateAndTime
19
+
17
20
  def commit_prepared_transaction(transaction_id, opts=OPTS)
18
21
  run("COMMIT TRANSACTION #{transaction_id}", opts)
19
22
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  Sequel::JDBC.load_driver('org.hsqldb.jdbcDriver', :HSQLDB)
4
4
  require_relative 'transactions'
5
+ require_relative '../../extensions/auto_cast_date_and_time'
5
6
 
6
7
  module Sequel
7
8
  module JDBC
@@ -15,6 +16,7 @@ module Sequel
15
16
 
16
17
  module HSQLDB
17
18
  module DatabaseMethods
19
+ include AutoCastDateAndTime
18
20
  include ::Sequel::JDBC::Transactions
19
21
 
20
22
  def database_type
@@ -56,6 +56,17 @@ module Sequel
56
56
 
57
57
  private
58
58
 
59
+ # JDBC SQLAnywhere driver does not appear to handle fractional
60
+ # times correctly.
61
+ def default_time_format
62
+ "'%H:%M:%S'"
63
+ end
64
+
65
+ # Set to zero to work around JDBC SQLAnywhere driver bug.
66
+ def sqltime_precision
67
+ 0
68
+ end
69
+
59
70
  SMALLINT_TYPE = Java::JavaSQL::Types::SMALLINT
60
71
  BOOLEAN_METHOD = Object.new
61
72
  def BOOLEAN_METHOD.call(r, i)
@@ -182,8 +182,8 @@ module Sequel
182
182
  1
183
183
  when false
184
184
  0
185
- when DateTime, Time
186
- literal(arg)[1...-1]
185
+ when Time, Date
186
+ @default_dataset.literal_date_or_time(arg, true)
187
187
  else
188
188
  arg
189
189
  end
@@ -43,7 +43,7 @@ module Sequel
43
43
  # Use ODBC format, not Microsoft format, as the ODBC layer does
44
44
  # some translation, but allow for millisecond precision.
45
45
  def default_timestamp_format
46
- "{ts '%Y-%m-%d %H:%M:%S%N'}"
46
+ "{ts '%Y-%m-%d %H:%M:%S.%3N'}"
47
47
  end
48
48
 
49
49
  # Use ODBC format, not Microsoft format, as the ODBC layer does
@@ -188,8 +188,8 @@ module Sequel
188
188
  # :nocov:
189
189
  # Not covered by tests as tests use pg_extended_date_support
190
190
  # extension, which has basically the same code.
191
- when DateTime, Time
192
- literal(arg)
191
+ when Time, DateTime
192
+ @default_dataset.literal_date_or_time(arg)
193
193
  # :nocov:
194
194
  else
195
195
  arg
@@ -932,7 +932,7 @@ module Sequel
932
932
  # since that is the format that is multilanguage and not
933
933
  # DATEFORMAT dependent.
934
934
  def default_timestamp_format
935
- "'%Y-%m-%dT%H:%M:%S%N%z'"
935
+ "'%Y-%m-%dT%H:%M:%S.%3N'"
936
936
  end
937
937
 
938
938
  # Only include the primary table in the main delete clause
@@ -929,6 +929,16 @@ module Sequel
929
929
  super if type == :truncate || @opts[:offset]
930
930
  end
931
931
 
932
+ # The strftime format to use when literalizing time (Sequel::SQLTime) values.
933
+ def default_time_format
934
+ db.supports_timestamp_usecs? ? super : "'%H:%M:%S'"
935
+ end
936
+
937
+ # The strftime format to use when literalizing timestamp (Time/DateTime) values.
938
+ def default_timestamp_format
939
+ db.supports_timestamp_usecs? ? super : "'%Y-%m-%d %H:%M:%S'"
940
+ end
941
+
932
942
  # Consider the first table in the joined dataset is the table to delete
933
943
  # from, but include the others for the purposes of selecting rows.
934
944
  def delete_from_sql(sql)
@@ -1,6 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require_relative '../utils/emulate_offset_with_row_number'
4
+ require_relative '../../extensions/auto_cast_date_and_time'
4
5
 
5
6
  module Sequel
6
7
  module Oracle
@@ -326,6 +327,8 @@ module Sequel
326
327
  end
327
328
 
328
329
  module DatasetMethods
330
+ include AutoCastDateAndTime
331
+
329
332
  ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
330
333
  BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
331
334
 
@@ -623,7 +626,7 @@ module Sequel
623
626
 
624
627
  # The strftime format to use when literalizing the time.
625
628
  def default_timestamp_format
626
- "TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'"
629
+ "'%Y-%m-%d %H:%M:%S.%6N %:z'"
627
630
  end
628
631
 
629
632
  def empty_from_sql
@@ -660,11 +663,6 @@ module Sequel
660
663
  super
661
664
  end
662
665
 
663
- # Use a colon for the timestamp offset, since Oracle appears to require it.
664
- def format_timestamp_offset(hour, minute)
665
- sprintf("%+03i:%02i", hour, minute)
666
- end
667
-
668
666
  # Oracle doesn't support empty values when inserting.
669
667
  def insert_supports_empty_values?
670
668
  false
@@ -1092,6 +1092,14 @@ module Sequel
1092
1092
  sql << type
1093
1093
  end
1094
1094
 
1095
+ # Consider lock or statement timeout errors as evidence that the table exists
1096
+ # but is locked.
1097
+ def _table_exists?(ds)
1098
+ super
1099
+ rescue DatabaseError => e
1100
+ raise e unless /canceling statement due to (?:statement|lock) timeout/ =~ e.message
1101
+ end
1102
+
1095
1103
  def alter_table_add_column_sql(table, op)
1096
1104
  "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
1097
1105
  end
@@ -1505,7 +1513,11 @@ module Sequel
1505
1513
  # currently visible schemas.
1506
1514
  def filter_schema(ds, opts)
1507
1515
  expr = if schema = opts[:schema]
1508
- schema.to_s
1516
+ if schema.is_a?(SQL::Identifier)
1517
+ schema.value.to_s
1518
+ else
1519
+ schema.to_s
1520
+ end
1509
1521
  else
1510
1522
  Sequel.function(:any, Sequel.function(:current_schemas, false))
1511
1523
  end
@@ -2138,10 +2150,14 @@ module Sequel
2138
2150
  server_version >= 90500
2139
2151
  end
2140
2152
 
2153
+ # :nocov:
2154
+
2141
2155
  # PostgreSQL supports timezones in literal timestamps
2142
2156
  def supports_timestamp_timezones?
2157
+ # SEQUEL6: Remove
2143
2158
  true
2144
2159
  end
2160
+ # :nocov:
2145
2161
 
2146
2162
  # PostgreSQL 8.4+ supports WINDOW clause.
2147
2163
  def supports_window_clause?
@@ -2261,6 +2277,11 @@ module Sequel
2261
2277
  raise(InvalidOperation, "Joined datasets cannot be truncated") if opts[:join]
2262
2278
  end
2263
2279
 
2280
+ # The strftime format to use when literalizing the time.
2281
+ def default_timestamp_format
2282
+ "'%Y-%m-%d %H:%M:%S.%6N%z'"
2283
+ end
2284
+
2264
2285
  # Only include the primary table in the main delete clause
2265
2286
  def delete_from_sql(sql)
2266
2287
  sql << ' FROM '
@@ -281,10 +281,6 @@ module Sequel
281
281
  false
282
282
  end
283
283
 
284
- def supports_timestamp_usecs?
285
- false
286
- end
287
-
288
284
  def supports_window_clause?
289
285
  true
290
286
  end
@@ -378,6 +374,16 @@ module Sequel
378
374
 
379
375
  private
380
376
 
377
+ # SQLAnywhere only supports 3 digits after the decimal point for times.
378
+ def default_time_format
379
+ "'%H:%M:%S.%3N'"
380
+ end
381
+
382
+ # SQLAnywhere only supports 3 digits after the decimal point for timestamps.
383
+ def default_timestamp_format
384
+ "'%Y-%m-%d %H:%M:%S.%3N'"
385
+ end
386
+
381
387
  # Use 1 for true on Sybase
382
388
  def literal_true
383
389
  '1'
@@ -917,6 +917,11 @@ module Sequel
917
917
  500
918
918
  end
919
919
 
920
+ # The strftime format to use when literalizing the time.
921
+ def default_timestamp_format
922
+ db.use_timestamp_timezones? ? "'%Y-%m-%d %H:%M:%S.%6N%z'" : super
923
+ end
924
+
920
925
  # SQL fragment specifying a list of identifiers
921
926
  def identifier_list(columns)
922
927
  columns.map{|i| quote_identifier(i)}.join(', ')
@@ -111,6 +111,13 @@ module Sequel
111
111
  # static data that you do not want to modify
112
112
  # :timeout :: how long to wait for the database to be available if it
113
113
  # is locked, given in milliseconds (default is 5000)
114
+ # :setup_regexp_function :: enable use of Regexp objects with SQL
115
+ # 'REGEXP' operator. If the value is :cached or "cached",
116
+ # caches the generated regexps, which can result in a memory
117
+ # leak if dynamic regexps are used. If the value is a Proc,
118
+ # it will be called with a string for the regexp and a string
119
+ # for the value to compare, and should return whether the regexp
120
+ # matches.
114
121
  def connect(server)
115
122
  opts = server_opts(server)
116
123
  opts[:database] = ':memory:' if blank_object?(opts[:database])
@@ -126,9 +133,7 @@ module Sequel
126
133
  connection_pragmas.each{|s| log_connection_yield(s, db){db.execute_batch(s)}}
127
134
 
128
135
  if typecast_value_boolean(opts[:setup_regexp_function])
129
- db.create_function("regexp", 2) do |func, regexp_str, string|
130
- func.result = Regexp.new(regexp_str).match(string) ? 1 : 0
131
- end
136
+ setup_regexp_function(db, opts[:setup_regexp_function])
132
137
  end
133
138
 
134
139
  class << db
@@ -202,6 +207,22 @@ module Sequel
202
207
  @conversion_procs['datetime'] = @conversion_procs['timestamp'] = method(:to_application_timestamp)
203
208
  set_integer_booleans
204
209
  end
210
+
211
+ def setup_regexp_function(db, how)
212
+ case how
213
+ when Proc
214
+ # nothing
215
+ when :cached, "cached"
216
+ cache = Hash.new{|h,k| h[k] = Regexp.new(k)}
217
+ how = lambda{|regexp_str, str| cache[regexp_str].match(str)}
218
+ else
219
+ how = lambda{|regexp_str, str| Regexp.new(regexp_str).match(str)}
220
+ end
221
+
222
+ db.create_function("regexp", 2) do |func, regexp_str, str|
223
+ func.result = how.call(regexp_str, str) ? 1 : 0
224
+ end
225
+ end
205
226
 
206
227
  # Yield an available connection. Rescue
207
228
  # any SQLite3::Exceptions and turn them into DatabaseErrors.
@@ -25,11 +25,16 @@ module Sequel
25
25
  false
26
26
  end
27
27
 
28
+ # :nocov:
29
+
28
30
  # Whether the dataset requires SQL standard datetimes. False by default,
29
- # as most allow strings with ISO 8601 format.
31
+ # as most allow strings with ISO 8601 format. Only for backwards compatibility,
32
+ # no longer used internally, do not use in new code.
30
33
  def requires_sql_standard_datetimes?
34
+ # SEQUEL6: Remove
31
35
  false
32
36
  end
37
+ # :nocov:
33
38
 
34
39
  # Whether type specifiers are required for prepared statement/bound
35
40
  # variable argument placeholders (i.e. :bv__integer), false by default.
@@ -183,10 +188,14 @@ module Sequel
183
188
  true
184
189
  end
185
190
 
191
+ # :nocov:
192
+
186
193
  # Whether the dataset supports timezones in literal timestamps, false by default.
187
194
  def supports_timestamp_timezones?
195
+ # SEQUEL6: Remove
188
196
  false
189
197
  end
198
+ # :nocov:
190
199
 
191
200
  # Whether the dataset supports fractional seconds in literal timestamps, true by default.
192
201
  def supports_timestamp_usecs?
@@ -82,7 +82,7 @@ module Sequel
82
82
  when DateTime
83
83
  literal_datetime_append(sql, v)
84
84
  when Date
85
- sql << literal_date(v)
85
+ literal_date_append(sql, v)
86
86
  when Dataset
87
87
  literal_dataset_append(sql, v)
88
88
  else
@@ -115,6 +115,33 @@ module Sequel
115
115
  sql
116
116
  end
117
117
 
118
+ # Literalize a date or time value, as a SQL string value with no
119
+ # typecasting. If +raw+ is true, remove the surrounding single
120
+ # quotes. This is designed for usage by bound argument code that
121
+ # can work even if the auto_cast_date_and_time extension is
122
+ # used (either manually or implicitly in the related adapter).
123
+ def literal_date_or_time(dt, raw=false)
124
+ value = case dt
125
+ when SQLTime
126
+ literal_sqltime(dt)
127
+ when Time
128
+ literal_time(dt)
129
+ when DateTime
130
+ literal_datetime(dt)
131
+ when Date
132
+ literal_date(dt)
133
+ else
134
+ raise TypeError, "unsupported type: #{dt.inspect}"
135
+ end
136
+
137
+ if raw
138
+ value.sub!(/\A'/, '')
139
+ value.sub!(/'\z/, '')
140
+ end
141
+
142
+ value
143
+ end
144
+
118
145
  # Returns an array of insert statements for inserting multiple records.
119
146
  # This method is used by +multi_insert+ to format insert statements and
120
147
  # expects a keys array and and an array of value arrays.
@@ -1104,9 +1131,14 @@ module Sequel
1104
1131
  :"t#{number}"
1105
1132
  end
1106
1133
 
1107
- # The strftime format to use when literalizing the time.
1134
+ # The strftime format to use when literalizing time (Sequel::SQLTime) values.
1135
+ def default_time_format
1136
+ "'%H:%M:%S.%6N'"
1137
+ end
1138
+
1139
+ # The strftime format to use when literalizing timestamp (Time/DateTime) values.
1108
1140
  def default_timestamp_format
1109
- requires_sql_standard_datetimes? ? "TIMESTAMP '%Y-%m-%d %H:%M:%S%N%z'" : "'%Y-%m-%d %H:%M:%S%N%z'"
1141
+ "'%Y-%m-%d %H:%M:%S.%6N'"
1110
1142
  end
1111
1143
 
1112
1144
  def delete_delete_sql(sql)
@@ -1169,43 +1201,23 @@ module Sequel
1169
1201
  {1 => ((op == :IN) ? 0 : 1)}
1170
1202
  end
1171
1203
 
1172
- # Format the timestamp based on the default_timestamp_format, with a couple
1173
- # of modifiers. First, allow %N to be used for fractions seconds (if the
1174
- # database supports them), and override %z to always use a numeric offset
1175
- # of hours and minutes.
1204
+ # Format the timestamp based on the default_timestamp_format.
1176
1205
  def format_timestamp(v)
1177
- v2 = db.from_application_timestamp(v)
1178
- fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
1179
- if m == '%N'
1180
- # Ruby 1.9 supports %N in timestamp formats, but Sequel has supported %N
1181
- # for longer in a different way, where the . is already appended and only 6
1182
- # decimal places are used by default.
1183
- format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(1000000) : v.usec) if supports_timestamp_usecs?
1184
- else
1185
- if supports_timestamp_timezones?
1186
- # Would like to just use %z format, but it doesn't appear to work on Windows
1187
- # Instead, the offset fragment is constructed manually
1188
- minutes = (v2.is_a?(DateTime) ? v2.offset * 1440 : v2.utc_offset/60).to_i
1189
- format_timestamp_offset(*minutes.divmod(60))
1190
- end
1191
- end
1192
- end
1193
- v2.strftime(fmt)
1206
+ db.from_application_timestamp(v).strftime(default_timestamp_format)
1194
1207
  end
1195
1208
 
1196
- # Return the SQL timestamp fragment to use for the timezone offset.
1197
- def format_timestamp_offset(hour, minute)
1198
- sprintf("%+03i%02i", hour, minute)
1199
- end
1209
+ # :nocov:
1200
1210
 
1201
1211
  # Return the SQL timestamp fragment to use for the fractional time part.
1202
1212
  # Should start with the decimal point. Uses 6 decimal places by default.
1203
1213
  def format_timestamp_usec(usec, ts=timestamp_precision)
1214
+ # SEQUEL6: Remove
1204
1215
  unless ts == 6
1205
1216
  usec = usec/(10 ** (6 - ts))
1206
1217
  end
1207
1218
  sprintf(".%0#{ts}d", usec)
1208
1219
  end
1220
+ # :nocov:
1209
1221
 
1210
1222
  # Append literalization of identifier to SQL string, considering regular strings
1211
1223
  # as SQL identifiers instead of SQL strings.
@@ -1347,11 +1359,12 @@ module Sequel
1347
1359
 
1348
1360
  # SQL fragment for Date, using the ISO8601 format.
1349
1361
  def literal_date(v)
1350
- if requires_sql_standard_datetimes?
1351
- v.strftime("DATE '%Y-%m-%d'")
1352
- else
1353
- v.strftime("'%Y-%m-%d'")
1354
- end
1362
+ v.strftime("'%Y-%m-%d'")
1363
+ end
1364
+
1365
+ # Append literalization of date to SQL string.
1366
+ def literal_date_append(sql, v)
1367
+ sql << literal_date(v)
1355
1368
  end
1356
1369
 
1357
1370
  # SQL fragment for DateTime
@@ -1414,7 +1427,7 @@ module Sequel
1414
1427
 
1415
1428
  # SQL fragment for Sequel::SQLTime, containing just the time part
1416
1429
  def literal_sqltime(v)
1417
- v.strftime("'%H:%M:%S#{format_timestamp_usec(v.usec, sqltime_precision) if supports_timestamp_usecs?}'")
1430
+ v.strftime(default_time_format)
1418
1431
  end
1419
1432
 
1420
1433
  # Append literalization of Sequel::SQLTime to SQL string.
@@ -0,0 +1,94 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The auto_cast_date_and_time extension uses SQL standard type casting
4
+ # when literalizing date, time, and timestamp values:
5
+ #
6
+ # DB.literal(Time.now)
7
+ # # => "TIMESTAMP '...'"
8
+ #
9
+ # DB.literal(Date.today)
10
+ # # => "DATE '...'"
11
+ #
12
+ # DB.literal(Sequel::SQLTime.create(10, 20, 30))
13
+ # # => "TIME '10:20:30.000000'"
14
+ #
15
+ # The default behavior of Sequel on adapters that do not require the
16
+ # SQL standard behavior is to format the date or time value without:
17
+ # casting
18
+ #
19
+ # DB.literal(Sequel::SQLTime.create(10, 20, 30))
20
+ # # => "'10:20:30.000000'"
21
+ #
22
+ # However, then the database cannot determine the type of the string,
23
+ # and must perform some implicit casting. If implicit casting cannot
24
+ # be used, it will probably treat the value as a string:
25
+ #
26
+ # DB.get(Time.now).class
27
+ # # Without auto_cast_date_and_time: String
28
+ # # With auto_cast_date_and_time: Time
29
+ #
30
+ # Note that not all databases support this extension. PostgreSQL and
31
+ # MySQL support it, but SQLite and Microsoft SQL Server do not.
32
+ #
33
+ # You can load this extension into specific datasets:
34
+ #
35
+ # ds = DB[:table]
36
+ # ds = ds.extension(:auto_cast_date_and_time)
37
+ #
38
+ # Or you can load it into all of a database's datasets, which
39
+ # is probably the desired behavior if you are using this extension:
40
+ #
41
+ # DB.extension(:auto_cast_date_and_time)
42
+ #
43
+ # Related module: Sequel::AutoCastDateAndTime
44
+
45
+ #
46
+ module Sequel
47
+ module AutoCastDateAndTime
48
+ # :nocov:
49
+
50
+ # Mark the datasets as requiring sql standard date times. This is only needed
51
+ # for backwards compatibility.
52
+ def requires_sql_standard_datetimes?
53
+ # SEQUEL6: Remove
54
+ true
55
+ end
56
+ # :nocov:
57
+
58
+ private
59
+
60
+ # Explicitly cast SQLTime objects to TIME.
61
+ def literal_sqltime_append(sql, v)
62
+ sql << "TIME "
63
+ super
64
+ end
65
+
66
+ # Explicitly cast Time objects to TIMESTAMP.
67
+ def literal_time_append(sql, v)
68
+ sql << literal_datetime_timestamp_cast
69
+ super
70
+ end
71
+
72
+ # Explicitly cast DateTime objects to TIMESTAMP.
73
+ def literal_datetime_append(sql, v)
74
+ sql << literal_datetime_timestamp_cast
75
+ super
76
+ end
77
+
78
+ # Explicitly cast Date objects to DATE.
79
+ def literal_date_append(sql, v)
80
+ sql << "DATE "
81
+ super
82
+ end
83
+
84
+ # The default cast string to use for Time/DateTime objects.
85
+ # Respects existing method if already defined.
86
+ def literal_datetime_timestamp_cast
87
+ return super if defined?(super)
88
+ 'TIMESTAMP '
89
+ end
90
+ end
91
+
92
+ Dataset.register_extension(:auto_cast_date_and_time, AutoCastDateAndTime)
93
+ end
94
+
@@ -136,7 +136,7 @@ module Sequel
136
136
  v = output_timezone.utc_to_local(v.new_offset(0))
137
137
 
138
138
  # Force DateTime output instead of TZInfo::DateTimeWithOffset
139
- DateTime.jd(v.jd, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
139
+ DateTime.civil(v.year, v.month, v.day, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
140
140
  end
141
141
  # :nodoc:
142
142
  # :nocov:
@@ -241,6 +241,8 @@ module Sequel
241
241
  else
242
242
  literal(a)
243
243
  end
244
+ when Time, Date
245
+ @default_dataset.literal_date_or_time(a)
244
246
  else
245
247
  if (s = bound_variable_arg(a, nil)).is_a?(String)
246
248
  bound_variable_array_string(s)
@@ -53,8 +53,8 @@ module Sequel
53
53
  # on jdbc.
54
54
  def bound_variable_arg(arg, conn)
55
55
  case arg
56
- when Date, Time
57
- literal(arg)
56
+ when Time, Date
57
+ @default_dataset.literal_date_or_time(arg)
58
58
  else
59
59
  super
60
60
  end
@@ -203,7 +203,7 @@ module Sequel
203
203
  date <<= ((date.year) * 24 - 12)
204
204
  date = db.from_application_timestamp(date)
205
205
  minutes = (date.offset * 1440).to_i
206
- date.strftime("'%Y-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*minutes.divmod(60))} BC'")
206
+ date.strftime("'%Y-%m-%d %H:%M:%S.%6N#{sprintf("%+03i%02i", *minutes.divmod(60))} BC'")
207
207
  else
208
208
  super
209
209
  end
@@ -247,7 +247,7 @@ module Sequel
247
247
  def literal_time(time)
248
248
  if time < TIME_YEAR_1
249
249
  time = db.from_application_timestamp(time)
250
- time.strftime("'#{sprintf('%04i', time.year.abs+1)}-%m-%d %H:%M:%S.%N#{format_timestamp_offset(*(time.utc_offset/RATIONAL_60).divmod(60))} BC'")
250
+ time.strftime("'#{sprintf('%04i', time.year.abs+1)}-%m-%d %H:%M:%S.%6N#{sprintf("%+03i%02i", *(time.utc_offset/RATIONAL_60).divmod(60))} BC'")
251
251
  else
252
252
  super
253
253
  end
@@ -494,8 +494,8 @@ module Sequel
494
494
  case k
495
495
  when nil
496
496
  ''
497
- when Date, Time
498
- ds.literal(k)[1...-1]
497
+ when Time, Date
498
+ ds.literal_date_or_time(k, true)
499
499
  when Integer, Float
500
500
  k.to_s
501
501
  when BigDecimal
@@ -1,20 +1,35 @@
1
1
  # frozen-string-literal: true
2
2
  #
3
3
  # The pg_timestamptz extension changes the default timestamp
4
- # type for the database to be +timestamptz+ (+timestamp with time zone+)
5
- # instead of +timestamp+ (+timestamp without time zone+). This is
4
+ # type for the database to be +timestamptz+ (<tt>timestamp with time zone</tt>)
5
+ # instead of +timestamp+ (<tt>timestamp without time zone</tt>). This is
6
6
  # recommended if you are dealing with multiple timezones in your application.
7
+ #
8
+ # If you are using the auto_cast_date_and_time extension, the pg_timestamptz
9
+ # extension will automatically cast Time and DateTime values to
10
+ # <tt>TIMESTAMP WITH TIME ZONE</tt> instead of +TIMESTAMP+.
7
11
  #
8
12
  # To load the extension into the database:
9
13
  #
10
14
  # DB.extension :pg_timestamptz
11
15
  #
12
- # Related module: Sequel::Postgres::Timestamptz
16
+ # To load the extension into individual datasets:
17
+ #
18
+ # ds = ds.extension(:pg_timestamptz)
19
+ #
20
+ # Note that the loading into individual datasets only affects the integration
21
+ # with the auto_cast_date_and_time extension.
22
+ #
23
+ # Related modules: Sequel::Postgres::Timestamptz, Sequel::Postgres::TimestamptzDatasetMethods
13
24
 
14
25
  #
15
26
  module Sequel
16
27
  module Postgres
17
28
  module Timestamptz
29
+ def self.extended(db)
30
+ db.extend_datasets(TimestamptzDatasetMethods)
31
+ end
32
+
18
33
  private
19
34
 
20
35
  # Use timestamptz by default for generic timestamp value.
@@ -22,7 +37,16 @@ module Sequel
22
37
  :timestamptz
23
38
  end
24
39
  end
40
+
41
+ module TimestamptzDatasetMethods
42
+ private
43
+
44
+ def literal_datetime_timestamp_cast
45
+ 'TIMESTAMP WITH TIME ZONE '
46
+ end
47
+ end
25
48
  end
26
49
 
50
+ Dataset.register_extension(:pg_timestamptz, Postgres::TimestamptzDatasetMethods)
27
51
  Database.register_extension(:pg_timestamptz, Postgres::Timestamptz)
28
52
  end
@@ -35,7 +35,7 @@ module Sequel
35
35
 
36
36
  # Round Sequel::SQLTime values before literalizing
37
37
  def literal_sqltime(v)
38
- super(v.round(timestamp_precision))
38
+ super(v.round(sqltime_precision))
39
39
  end
40
40
 
41
41
  # Round Time values before literalizing
@@ -71,6 +71,8 @@ module Sequel
71
71
  # (default: :t)
72
72
  # :level_alias :: The symbol identifier to use when eagerly loading descendants
73
73
  # up to a given level (default: :x_level_x)
74
+ # :union_all :: Whether to use UNION ALL or UNION with the recursive
75
+ # common table expression (default: true)
74
76
  module RcteTree
75
77
  # Create the appropriate parent, children, ancestors, and descendants
76
78
  # associations for the model.
@@ -80,6 +82,7 @@ module Sequel
80
82
  opts = opts.dup
81
83
  opts[:class] = model
82
84
  opts[:methods_module] = Module.new
85
+ opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
83
86
  model.send(:include, opts[:methods_module])
84
87
 
85
88
  key = opts[:key] ||= :parent_id
@@ -142,7 +145,7 @@ module Sequel
142
145
  model.from(SQL::AliasedExpression.new(t, table_alias)).
143
146
  with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
144
147
  recursive_ds.select(*c_all),
145
- :args=>col_aliases)
148
+ :args=>col_aliases, union_all: opts[:union_all])
146
149
  end
147
150
  aal = Array(a[:after_load])
148
151
  aal << proc do |m, ancs|
@@ -191,7 +194,7 @@ module Sequel
191
194
  table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
192
195
  ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
193
196
  with_recursive(t, base_case, recursive_case,
194
- :args=>((key_aliases + col_aliases) if col_aliases))
197
+ :args=>((key_aliases + col_aliases) if col_aliases), union_all: opts[:union_all])
195
198
  ds = r.apply_eager_dataset_changes(ds)
196
199
  ds = ds.select_append(ka) unless ds.opts[:select] == nil
197
200
  model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil)) do |obj|
@@ -240,7 +243,7 @@ module Sequel
240
243
  model.from(SQL::AliasedExpression.new(t, table_alias)).
241
244
  with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
242
245
  recursive_ds.select(*c_all),
243
- :args=>col_aliases)
246
+ :args=>col_aliases, union_all: opts[:union_all])
244
247
  end
245
248
  dal = Array(d[:after_load])
246
249
  dal << proc do |m, descs|
@@ -299,7 +302,7 @@ module Sequel
299
302
  table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
300
303
  ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
301
304
  with_recursive(t, base_case, recursive_case,
302
- :args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases))
305
+ :args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases), union_all: opts[:union_all])
303
306
  ds = r.apply_eager_dataset_changes(ds)
304
307
  ds = ds.select_append(ka) unless ds.opts[:select] == nil
305
308
  model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil, :associations=>OPTS)) do |obj|
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 75
9
+ MINOR = 76
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.75.0
4
+ version: 5.76.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-01 00:00:00.000000000 Z
11
+ date: 2024-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -222,6 +222,7 @@ extra_rdoc_files:
222
222
  - doc/release_notes/5.73.0.txt
223
223
  - doc/release_notes/5.74.0.txt
224
224
  - doc/release_notes/5.75.0.txt
225
+ - doc/release_notes/5.76.0.txt
225
226
  - doc/release_notes/5.8.0.txt
226
227
  - doc/release_notes/5.9.0.txt
227
228
  files:
@@ -325,6 +326,7 @@ files:
325
326
  - doc/release_notes/5.73.0.txt
326
327
  - doc/release_notes/5.74.0.txt
327
328
  - doc/release_notes/5.75.0.txt
329
+ - doc/release_notes/5.76.0.txt
328
330
  - doc/release_notes/5.8.0.txt
329
331
  - doc/release_notes/5.9.0.txt
330
332
  - doc/schema_modification.rdoc
@@ -426,6 +428,7 @@ files:
426
428
  - lib/sequel/extensions/any_not_empty.rb
427
429
  - lib/sequel/extensions/arbitrary_servers.rb
428
430
  - lib/sequel/extensions/async_thread_pool.rb
431
+ - lib/sequel/extensions/auto_cast_date_and_time.rb
429
432
  - lib/sequel/extensions/auto_literal_strings.rb
430
433
  - lib/sequel/extensions/blank.rb
431
434
  - lib/sequel/extensions/caller_logging.rb
@@ -656,7 +659,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
656
659
  - !ruby/object:Gem::Version
657
660
  version: '0'
658
661
  requirements: []
659
- rubygems_version: 3.4.10
662
+ rubygems_version: 3.5.3
660
663
  signing_key:
661
664
  specification_version: 4
662
665
  summary: The Database Toolkit for Ruby