sequel 5.75.0 → 5.76.0

Sign up to get free protection for your applications and to get access to all the features.
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