sequel 5.48.0 → 5.52.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +80 -0
  3. data/README.rdoc +12 -5
  4. data/doc/migration.rdoc +1 -1
  5. data/doc/opening_databases.rdoc +1 -1
  6. data/doc/postgresql.rdoc +8 -0
  7. data/doc/release_notes/5.49.0.txt +59 -0
  8. data/doc/release_notes/5.50.0.txt +78 -0
  9. data/doc/release_notes/5.51.0.txt +47 -0
  10. data/doc/release_notes/5.52.0.txt +87 -0
  11. data/doc/testing.rdoc +3 -1
  12. data/lib/sequel/adapters/ado/access.rb +1 -1
  13. data/lib/sequel/adapters/ado.rb +1 -1
  14. data/lib/sequel/adapters/amalgalite.rb +3 -5
  15. data/lib/sequel/adapters/ibmdb.rb +2 -2
  16. data/lib/sequel/adapters/jdbc/derby.rb +3 -0
  17. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  18. data/lib/sequel/adapters/jdbc.rb +9 -11
  19. data/lib/sequel/adapters/mysql.rb +80 -67
  20. data/lib/sequel/adapters/mysql2.rb +42 -44
  21. data/lib/sequel/adapters/odbc.rb +1 -1
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/postgres.rb +27 -29
  24. data/lib/sequel/adapters/shared/access.rb +2 -0
  25. data/lib/sequel/adapters/shared/db2.rb +2 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +4 -2
  27. data/lib/sequel/adapters/shared/postgres.rb +59 -6
  28. data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
  29. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  30. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  31. data/lib/sequel/adapters/sqlite.rb +16 -18
  32. data/lib/sequel/adapters/tinytds.rb +1 -1
  33. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  34. data/lib/sequel/ast_transformer.rb +6 -0
  35. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  36. data/lib/sequel/connection_pool/single.rb +6 -8
  37. data/lib/sequel/core.rb +17 -18
  38. data/lib/sequel/database/connecting.rb +2 -2
  39. data/lib/sequel/database/misc.rb +6 -0
  40. data/lib/sequel/database/query.rb +1 -1
  41. data/lib/sequel/dataset/actions.rb +2 -2
  42. data/lib/sequel/dataset/query.rb +45 -3
  43. data/lib/sequel/dataset/sql.rb +18 -9
  44. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  45. data/lib/sequel/extensions/core_refinements.rb +36 -11
  46. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  47. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  48. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  49. data/lib/sequel/extensions/inflector.rb +1 -1
  50. data/lib/sequel/extensions/migration.rb +4 -1
  51. data/lib/sequel/extensions/pagination.rb +1 -1
  52. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  53. data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
  54. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  55. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  56. data/lib/sequel/extensions/pg_interval.rb +1 -0
  57. data/lib/sequel/extensions/pg_json.rb +3 -5
  58. data/lib/sequel/extensions/pg_json_ops.rb +71 -1
  59. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  60. data/lib/sequel/extensions/pg_range.rb +4 -12
  61. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  62. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  63. data/lib/sequel/extensions/s.rb +2 -1
  64. data/lib/sequel/extensions/server_block.rb +8 -12
  65. data/lib/sequel/extensions/sql_comments.rb +108 -3
  66. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  67. data/lib/sequel/extensions/string_agg.rb +1 -1
  68. data/lib/sequel/extensions/string_date_time.rb +19 -23
  69. data/lib/sequel/model/associations.rb +3 -1
  70. data/lib/sequel/model/base.rb +9 -13
  71. data/lib/sequel/model/inflections.rb +1 -1
  72. data/lib/sequel/plugins/auto_validations.rb +25 -5
  73. data/lib/sequel/plugins/column_encryption.rb +1 -1
  74. data/lib/sequel/plugins/composition.rb +1 -0
  75. data/lib/sequel/plugins/json_serializer.rb +2 -2
  76. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  77. data/lib/sequel/plugins/serialization.rb +1 -0
  78. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  79. data/lib/sequel/plugins/sql_comments.rb +189 -0
  80. data/lib/sequel/plugins/static_cache.rb +1 -1
  81. data/lib/sequel/plugins/subclasses.rb +28 -11
  82. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
  83. data/lib/sequel/plugins/unused_associations.rb +2 -2
  84. data/lib/sequel/plugins/update_or_create.rb +1 -1
  85. data/lib/sequel/plugins/validation_helpers.rb +7 -1
  86. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  87. data/lib/sequel/sql.rb +1 -1
  88. data/lib/sequel/timezones.rb +12 -14
  89. data/lib/sequel/version.rb +1 -1
  90. metadata +17 -4
@@ -189,26 +189,24 @@ module Sequel
189
189
  # Yield an available connection. Rescue
190
190
  # any SQLite3::Exceptions and turn them into DatabaseErrors.
191
191
  def _execute(type, sql, opts, &block)
192
- begin
193
- synchronize(opts[:server]) do |conn|
194
- return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
195
- log_args = opts[:arguments]
196
- args = {}
197
- opts.fetch(:arguments, OPTS).each{|k, v| args[k] = prepared_statement_argument(v)}
198
- case type
199
- when :select
200
- log_connection_yield(sql, conn, log_args){conn.query(sql, args, &block)}
201
- when :insert
202
- log_connection_yield(sql, conn, log_args){conn.execute(sql, args)}
203
- conn.last_insert_row_id
204
- when :update
205
- log_connection_yield(sql, conn, log_args){conn.execute_batch(sql, args)}
206
- conn.changes
207
- end
192
+ synchronize(opts[:server]) do |conn|
193
+ return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
194
+ log_args = opts[:arguments]
195
+ args = {}
196
+ opts.fetch(:arguments, OPTS).each{|k, v| args[k] = prepared_statement_argument(v)}
197
+ case type
198
+ when :select
199
+ log_connection_yield(sql, conn, log_args){conn.query(sql, args, &block)}
200
+ when :insert
201
+ log_connection_yield(sql, conn, log_args){conn.execute(sql, args)}
202
+ conn.last_insert_row_id
203
+ when :update
204
+ log_connection_yield(sql, conn, log_args){conn.execute_batch(sql, args)}
205
+ conn.changes
208
206
  end
209
- rescue SQLite3::Exception => e
210
- raise_error(e)
211
207
  end
208
+ rescue SQLite3::Exception => e
209
+ raise_error(e)
212
210
  end
213
211
 
214
212
  # The SQLite adapter does not need the pool to convert exceptions.
@@ -75,7 +75,7 @@ module Sequel
75
75
  return r.public_send(m) if m
76
76
  end
77
77
  end
78
- yield(r) if block_given?
78
+ yield(r) if defined?(yield)
79
79
  rescue TinyTds::Error => e
80
80
  raise_error(e, :disconnect=>!c.active?)
81
81
  ensure
@@ -0,0 +1,22 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ class Dataset
5
+ module ColumnsLimit1
6
+ COLUMNS_CLONE_OPTIONS = {:distinct => nil, :limit => 1, :offset=>nil, :where=>nil, :having=>nil, :order=>nil, :row_proc=>nil, :graph=>nil, :eager_graph=>nil}.freeze
7
+
8
+ # Use a limit of 1 instead of a limit of 0 when
9
+ # getting the columns.
10
+ def columns!
11
+ ds = clone(COLUMNS_CLONE_OPTIONS)
12
+ ds.each{break}
13
+
14
+ if cols = ds.cache[:_columns]
15
+ self.columns = cols
16
+ else
17
+ []
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -80,6 +80,12 @@ module Sequel
80
80
  SQL::DelayedEvaluation.new(lambda{|ds| v(o.call(ds))})
81
81
  when SQL::Wrapper
82
82
  SQL::Wrapper.new(v(o.value))
83
+ when SQL::Expression
84
+ if o.respond_to?(:sequel_ast_transform)
85
+ o.sequel_ast_transform(method(:v))
86
+ else
87
+ o
88
+ end
83
89
  else
84
90
  o
85
91
  end
@@ -55,13 +55,11 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
55
55
  # Yields the connection to the supplied block for the given server.
56
56
  # This method simulates the ConnectionPool#hold API.
57
57
  def hold(server=:default)
58
- begin
59
- server = pick_server(server)
60
- yield(@conns[server] ||= make_new(server))
61
- rescue Sequel::DatabaseDisconnectError, *@error_classes => e
62
- disconnect_server(server) if disconnect_error?(e)
63
- raise
64
- end
58
+ server = pick_server(server)
59
+ yield(@conns[server] ||= make_new(server))
60
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
61
+ disconnect_server(server) if disconnect_error?(e)
62
+ raise
65
63
  end
66
64
 
67
65
  # The ShardedSingleConnectionPool always has a maximum size of 1.
@@ -24,15 +24,13 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
24
24
 
25
25
  # Yield the connection to the block.
26
26
  def hold(server=nil)
27
- begin
28
- unless c = @conn.first
29
- @conn.replace([c = make_new(:default)])
30
- end
31
- yield c
32
- rescue Sequel::DatabaseDisconnectError, *@error_classes => e
33
- disconnect if disconnect_error?(e)
34
- raise
27
+ unless c = @conn.first
28
+ @conn.replace([c = make_new(:default)])
35
29
  end
30
+ yield c
31
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
32
+ disconnect if disconnect_error?(e)
33
+ raise
36
34
  end
37
35
 
38
36
  # The SingleConnectionPool always has a maximum size of 1.
data/lib/sequel/core.rb CHANGED
@@ -278,11 +278,9 @@ module Sequel
278
278
  #
279
279
  # Sequel.string_to_date('2010-09-10') # Date.civil(2010, 09, 10)
280
280
  def string_to_date(string)
281
- begin
282
- Date.parse(string, Sequel.convert_two_digit_years)
283
- rescue => e
284
- raise convert_exception_class(e, InvalidValue)
285
- end
281
+ Date.parse(string, Sequel.convert_two_digit_years)
282
+ rescue => e
283
+ raise convert_exception_class(e, InvalidValue)
286
284
  end
287
285
 
288
286
  # Converts the given +string+ into a +Time+ or +DateTime+ object, depending on the
@@ -290,15 +288,13 @@ module Sequel
290
288
  #
291
289
  # Sequel.string_to_datetime('2010-09-10 10:20:30') # Time.local(2010, 09, 10, 10, 20, 30)
292
290
  def string_to_datetime(string)
293
- begin
294
- if datetime_class == DateTime
295
- DateTime.parse(string, convert_two_digit_years)
296
- else
297
- datetime_class.parse(string)
298
- end
299
- rescue => e
300
- raise convert_exception_class(e, InvalidValue)
291
+ if datetime_class == DateTime
292
+ DateTime.parse(string, convert_two_digit_years)
293
+ else
294
+ datetime_class.parse(string)
301
295
  end
296
+ rescue => e
297
+ raise convert_exception_class(e, InvalidValue)
302
298
  end
303
299
 
304
300
  # Converts the given +string+ into a <tt>Sequel::SQLTime</tt> object.
@@ -306,11 +302,9 @@ module Sequel
306
302
  # v = Sequel.string_to_time('10:20:30') # Sequel::SQLTime.parse('10:20:30')
307
303
  # DB.literal(v) # => '10:20:30'
308
304
  def string_to_time(string)
309
- begin
310
- SQLTime.parse(string)
311
- rescue => e
312
- raise convert_exception_class(e, InvalidValue)
313
- end
305
+ SQLTime.parse(string)
306
+ rescue => e
307
+ raise convert_exception_class(e, InvalidValue)
314
308
  end
315
309
 
316
310
  # Unless in single threaded mode, protects access to any mutable
@@ -400,6 +394,11 @@ module Sequel
400
394
 
401
395
  private
402
396
 
397
+ # Return a hash of date information parsed from the given string.
398
+ def _date_parse(string)
399
+ Date._parse(string)
400
+ end
401
+
403
402
  # Helper method that the database adapter class methods that are added to Sequel via
404
403
  # metaprogramming use to parse arguments.
405
404
  def adapter_method(adapter, *args, &block)
@@ -55,11 +55,11 @@ module Sequel
55
55
 
56
56
  begin
57
57
  db = c.new(opts)
58
- if block_given?
58
+ if defined?(yield)
59
59
  return yield(db)
60
60
  end
61
61
  ensure
62
- if block_given?
62
+ if defined?(yield)
63
63
  db.disconnect if db
64
64
  Sequel.synchronize{::Sequel::DATABASES.delete(db)}
65
65
  end
@@ -95,6 +95,8 @@ module Sequel
95
95
  # options hash.
96
96
  #
97
97
  # Accepts the following options:
98
+ # :before_preconnect :: Callable that runs after extensions from :preconnect_extensions are loaded,
99
+ # but before any connections are created.
98
100
  # :cache_schema :: Whether schema should be cached for this Database instance
99
101
  # :default_string_column_size :: The default size of string columns, 255 by default.
100
102
  # :extensions :: Extensions to load into this Database instance. Can be a symbol, array of symbols,
@@ -160,6 +162,10 @@ module Sequel
160
162
 
161
163
  initialize_load_extensions(:preconnect_extensions)
162
164
 
165
+ if before_preconnect = @opts[:before_preconnect]
166
+ before_preconnect.call(self)
167
+ end
168
+
163
169
  if typecast_value_boolean(@opts[:preconnect]) && @pool.respond_to?(:preconnect, true)
164
170
  concurrent = typecast_value_string(@opts[:preconnect]) == "concurrently"
165
171
  @pool.send(:preconnect, concurrent)
@@ -236,7 +236,7 @@ module Sequel
236
236
  when :date
237
237
  Sequel.string_to_date(default)
238
238
  when :datetime
239
- DateTime.parse(default)
239
+ Sequel.string_to_datetime(default)
240
240
  when :time
241
241
  Sequel.string_to_time(default)
242
242
  when :decimal
@@ -19,7 +19,7 @@ module Sequel
19
19
  METHS
20
20
 
21
21
  # The clone options to use when retrieving columns for a dataset.
22
- COLUMNS_CLONE_OPTIONS = {:distinct => nil, :limit => 1, :offset=>nil, :where=>nil, :having=>nil, :order=>nil, :row_proc=>nil, :graph=>nil, :eager_graph=>nil}.freeze
22
+ COLUMNS_CLONE_OPTIONS = {:distinct => nil, :limit => 0, :offset=>nil, :where=>nil, :having=>nil, :order=>nil, :row_proc=>nil, :graph=>nil, :eager_graph=>nil}.freeze
23
23
 
24
24
  # Inserts the given argument into the database. Returns self so it
25
25
  # can be used safely when chaining:
@@ -546,7 +546,7 @@ module Sequel
546
546
  unless @opts[:order]
547
547
  raise Sequel::Error, "Dataset#paged_each requires the dataset be ordered"
548
548
  end
549
- unless block_given?
549
+ unless defined?(yield)
550
550
  return enum_for(:paged_each, opts)
551
551
  end
552
552
 
@@ -508,6 +508,7 @@ module Sequel
508
508
  # argument.
509
509
  # :implicit_qualifier :: The name to use for qualifying implicit conditions. By default,
510
510
  # the last joined or primary table is used.
511
+ # :join_using :: Force the using of JOIN USING, even if +expr+ is not an array of symbols.
511
512
  # :reset_implicit_qualifier :: Can set to false to ignore this join when future joins determine qualifier
512
513
  # for implicit conditions.
513
514
  # :qualify :: Can be set to false to not do any implicit qualification. Can be set
@@ -541,7 +542,7 @@ module Sequel
541
542
  return s.join_table(type, ds, expr, options, &block)
542
543
  end
543
544
 
544
- using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
545
+ using_join = options[:join_using] || (expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)})
545
546
  if using_join && !supports_join_using?
546
547
  h = {}
547
548
  expr.each{|e| h[e] = e}
@@ -616,7 +617,7 @@ module Sequel
616
617
  UNCONDITIONED_JOIN_TYPES.each do |jtype|
617
618
  class_eval(<<-END, __FILE__, __LINE__+1)
618
619
  def #{jtype}_join(table, opts=Sequel::OPTS)
619
- raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?
620
+ raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if defined?(yield)
620
621
  raise(Sequel::Error, '#{jtype}_join 2nd argument should be an options hash, not conditions') unless opts.is_a?(Hash)
621
622
  join_table(:#{jtype}, table, nil, opts)
622
623
  end
@@ -1059,6 +1060,7 @@ module Sequel
1059
1060
 
1060
1061
  # Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
1061
1062
  # A common table expression acts as an inline view for the query.
1063
+ #
1062
1064
  # Options:
1063
1065
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
1064
1066
  # :recursive :: Specify that this is a recursive CTE
@@ -1079,10 +1081,35 @@ module Sequel
1079
1081
 
1080
1082
  # Add a recursive common table expression (CTE) with the given name, a dataset that
1081
1083
  # defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
1082
- # of the CTE. Options:
1084
+ # of the CTE.
1085
+ #
1086
+ # Options:
1083
1087
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
1084
1088
  # :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
1085
1089
  #
1090
+ # PostgreSQL 14+ Options:
1091
+ # :cycle :: Stop recursive searching when a cycle is detected. Includes two columns in the
1092
+ # result of the CTE, a cycle column indicating whether a cycle was detected for
1093
+ # the current row, and a path column for the path traversed to get to the current
1094
+ # row. If given, must be a hash with the following keys:
1095
+ # :columns :: (required) The column or array of columns to use to detect a cycle.
1096
+ # If the value of these columns match columns already traversed, then
1097
+ # a cycle is detected, and recursive searching will not traverse beyond
1098
+ # the cycle (the CTE will include the row where the cycle was detected).
1099
+ # :cycle_column :: The name of the cycle column in the output, defaults to :is_cycle.
1100
+ # :cycle_value :: The value of the cycle column in the output if the current row was
1101
+ # detected as a cycle, defaults to true.
1102
+ # :noncycle_value :: The value of the cycle column in the output if the current row
1103
+ # was not detected as a cycle, defaults to false. Only respected
1104
+ # if :cycle_value is given.
1105
+ # :path_column :: The name of the path column in the output, defaults to :path.
1106
+ # :search :: Include an order column in the result of the CTE that allows for breadth or
1107
+ # depth first searching. If given, must be a hash with the following keys:
1108
+ # :by :: (required) The column or array of columns to search by.
1109
+ # :order_column :: The name of the order column in the output, defaults to :ordercol.
1110
+ # :type :: Set to :breadth to use breadth-first searching (depth-first searching
1111
+ # is the default).
1112
+ #
1086
1113
  # DB[:t].with_recursive(:t,
1087
1114
  # DB[:i1].select(:id, :parent_id).where(parent_id: nil),
1088
1115
  # DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
@@ -1093,6 +1120,21 @@ module Sequel
1093
1120
  # # UNION ALL
1094
1121
  # # SELECT i1.id, i1.parent_id FROM i1 INNER JOIN t ON (t.id = i1.parent_id)
1095
1122
  # # ) SELECT * FROM t
1123
+ #
1124
+ # DB[:t].with_recursive(:t,
1125
+ # DB[:i1].where(parent_id: nil),
1126
+ # DB[:i1].join(:t, id: :parent_id).select_all(:i1),
1127
+ # search: {by: :id, type: :breadth},
1128
+ # cycle: {columns: :id, cycle_value: 1, noncycle_value: 2})
1129
+ #
1130
+ # # WITH RECURSIVE t AS (
1131
+ # # SELECT * FROM i1 WHERE (parent_id IS NULL)
1132
+ # # UNION ALL
1133
+ # # (SELECT i1.* FROM i1 INNER JOIN t ON (t.id = i1.parent_id))
1134
+ # # )
1135
+ # # SEARCH BREADTH FIRST BY id SET ordercol
1136
+ # # CYCLE id SET is_cycle TO 1 DEFAULT 2 USING path
1137
+ # # SELECT * FROM t
1096
1138
  def with_recursive(name, nonrecursive, recursive, opts=OPTS)
1097
1139
  raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
1098
1140
  if hoist_cte?(nonrecursive)
@@ -559,11 +559,9 @@ module Sequel
559
559
  # Append literalization of JOIN USING clause to SQL string.
560
560
  def join_using_clause_sql_append(sql, jc)
561
561
  join_clause_sql_append(sql, jc)
562
- sql << ' USING ('
563
- column_list_append(sql, jc.using)
564
- sql << ')'
562
+ join_using_clause_using_sql_append(sql, jc.using)
565
563
  end
566
-
564
+
567
565
  # Append literalization of negative boolean constant to SQL string.
568
566
  def negative_boolean_constant_sql_append(sql, constant)
569
567
  sql << 'NOT '
@@ -1218,6 +1216,13 @@ module Sequel
1218
1216
  "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
1219
1217
  end
1220
1218
 
1219
+ # Append USING clause for JOIN USING
1220
+ def join_using_clause_using_sql_append(sql, using_columns)
1221
+ sql << ' USING ('
1222
+ column_list_append(sql, using_columns)
1223
+ sql << ')'
1224
+ end
1225
+
1221
1226
  # Append a literalization of the array to SQL string.
1222
1227
  # Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
1223
1228
  def literal_array_append(sql, v)
@@ -1538,15 +1543,14 @@ module Sequel
1538
1543
 
1539
1544
  def select_with_sql(sql)
1540
1545
  return unless supports_cte?
1541
- ws = opts[:with]
1542
- return if !ws || ws.empty?
1546
+ ctes = opts[:with]
1547
+ return if !ctes || ctes.empty?
1543
1548
  sql << select_with_sql_base
1544
1549
  c = false
1545
1550
  comma = ', '
1546
- ws.each do |w|
1551
+ ctes.each do |cte|
1547
1552
  sql << comma if c
1548
- select_with_sql_prefix(sql, w)
1549
- literal_dataset_append(sql, w[:dataset])
1553
+ select_with_sql_cte(sql, cte)
1550
1554
  c ||= true
1551
1555
  end
1552
1556
  sql << ' '
@@ -1559,6 +1563,11 @@ module Sequel
1559
1563
  "WITH "
1560
1564
  end
1561
1565
 
1566
+ def select_with_sql_cte(sql, cte)
1567
+ select_with_sql_prefix(sql, cte)
1568
+ literal_dataset_append(sql, cte[:dataset])
1569
+ end
1570
+
1562
1571
  def select_with_sql_prefix(sql, w)
1563
1572
  quote_identifier_append(sql, w[:name])
1564
1573
  if args = w[:args]
@@ -33,7 +33,7 @@ module Sequel
33
33
  module AnyNotEmpty
34
34
  # If a block is not given, return whether the dataset is not empty.
35
35
  def any?
36
- if block_given?
36
+ if defined?(yield)
37
37
  super
38
38
  else
39
39
  !empty?
@@ -15,6 +15,12 @@ raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VE
15
15
  # :nocov:
16
16
 
17
17
  module Sequel::CoreRefinements
18
+ # :nocov:
19
+ include_meth = RUBY_VERSION >= '3.1' ? :import_methods : :include
20
+ # :nocov:
21
+ INCLUDE_METH = include_meth
22
+ private_constant :INCLUDE_METH
23
+
18
24
  refine Array do
19
25
  # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, not matching all of the
20
26
  # conditions.
@@ -161,8 +167,8 @@ module Sequel::CoreRefinements
161
167
  end
162
168
 
163
169
  refine String do
164
- include Sequel::SQL::AliasMethods
165
- include Sequel::SQL::CastMethods
170
+ send include_meth, Sequel::SQL::AliasMethods
171
+ send include_meth, Sequel::SQL::CastMethods
166
172
 
167
173
  # Converts a string into a <tt>Sequel::LiteralString</tt>, in order to override string
168
174
  # literalization, e.g.:
@@ -189,15 +195,34 @@ module Sequel::CoreRefinements
189
195
  end
190
196
 
191
197
  refine Symbol do
192
- include Sequel::SQL::AliasMethods
193
- include Sequel::SQL::CastMethods
194
- include Sequel::SQL::OrderMethods
195
- include Sequel::SQL::BooleanMethods
196
- include Sequel::SQL::NumericMethods
197
- include Sequel::SQL::QualifyingMethods
198
- include Sequel::SQL::StringMethods
199
- include Sequel::SQL::SubscriptMethods
200
- include Sequel::SQL::ComplexExpressionMethods
198
+ send include_meth, Sequel::SQL::AliasMethods
199
+ send include_meth, Sequel::SQL::CastMethods
200
+ send include_meth, Sequel::SQL::OrderMethods
201
+ send include_meth, Sequel::SQL::BooleanMethods
202
+ send include_meth, Sequel::SQL::NumericMethods
203
+
204
+ # :nocov:
205
+ remove_method :* if RUBY_VERSION >= '3.1'
206
+ # :nocov:
207
+
208
+ send include_meth, Sequel::SQL::QualifyingMethods
209
+ send include_meth, Sequel::SQL::StringMethods
210
+ send include_meth, Sequel::SQL::SubscriptMethods
211
+ send include_meth, Sequel::SQL::ComplexExpressionMethods
212
+
213
+ # :nocov:
214
+ if RUBY_VERSION >= '3.1'
215
+ remove_method :*
216
+ def *(ce=(arg=false;nil))
217
+ if arg == false
218
+ Sequel::SQL::ColumnAll.new(self)
219
+ else
220
+ Sequel::SQL::NumericExpression.new(:*, self, ce)
221
+ end
222
+ end
223
+
224
+ end
225
+ # :nocov:
201
226
 
202
227
  # Returns receiver wrapped in an <tt>Sequel::SQL::Identifier</tt>.
203
228
  #
@@ -0,0 +1,67 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The date_parse_input_handler extension allows for configuring how input
4
+ # to date parsing methods should be handled. By default, the
5
+ # extension does not change behavior. However, you can use the
6
+ # +Sequel.date_parse_input_handler+ method to support custom handling
7
+ # of input strings to the date parsing methods. For example, if you want
8
+ # to implement a length check to prevent denial of service vulnerabilities
9
+ # in older versions of Ruby, you can do:
10
+ #
11
+ # Sequel.extension :date_parse_input_handler
12
+ # Sequel.date_parse_input_handler do |string|
13
+ # raise Sequel::InvalidValue, "string length (200) exceeds the limit 128" if string.bytesize > 128
14
+ # string
15
+ # end
16
+ #
17
+ # You can also use +Sequel.date_parse_input_handler+ to modify the string
18
+ # that will be passed to the parsing methods. For example, you could
19
+ # truncate it:
20
+ #
21
+ # Sequel.date_parse_input_handler do |string|
22
+ # string.b[0, 128]
23
+ # end
24
+ #
25
+ # Be aware that modern versions of Ruby will raise an exception if
26
+ # date parsing input exceeds 128 bytes.
27
+
28
+ module Sequel
29
+ module DateParseInputHandler
30
+ def date_parse_input_handler(&block)
31
+ singleton_class.class_eval do
32
+ define_method(:handle_date_parse_input, &block)
33
+ private :handle_date_parse_input
34
+ alias handle_date_parse_input handle_date_parse_input
35
+ end
36
+ end
37
+
38
+ # Call date parse input handler with input string.
39
+ def string_to_date(string)
40
+ super(handle_date_parse_input(string))
41
+ end
42
+
43
+ # Call date parse input handler with input string.
44
+ def string_to_datetime(string)
45
+ super(handle_date_parse_input(string))
46
+ end
47
+
48
+ # Call date parse input handler with input string.
49
+ def string_to_time(string)
50
+ super(handle_date_parse_input(string))
51
+ end
52
+
53
+ private
54
+
55
+ # Call date parse input handler with input string.
56
+ def _date_parse(string)
57
+ super(handle_date_parse_input(string))
58
+ end
59
+
60
+ # Return string as-is by default, so by default behavior does not change.
61
+ def handle_date_parse_input(string)
62
+ string
63
+ end
64
+ end
65
+
66
+ extend DateParseInputHandler
67
+ end
@@ -19,7 +19,11 @@ module Sequel::DateTimeParseToTime
19
19
  # Use DateTime.parse.to_time to do the conversion if the input a string and is assumed to
20
20
  # be in UTC and there is no offset information in the string.
21
21
  def convert_input_timestamp(v, input_timezone)
22
- if v.is_a?(String) && datetime_class == Time && input_timezone == :utc && !Date._parse(v).has_key?(:offset)
22
+ if v.is_a?(String) && datetime_class == Time && input_timezone == :utc && !_date_parse(v).has_key?(:offset)
23
+ # :nocov:
24
+ # Whether this is fully branch covered depends on the order in which the specs are run.
25
+ v = handle_date_parse_input(v) if respond_to?(:handle_date_parse_input, true)
26
+ # :nocov:
23
27
  t = DateTime.parse(v).to_time
24
28
  case application_timezone
25
29
  when nil, :local
@@ -44,7 +44,7 @@ module Sequel
44
44
  # :nocov:
45
45
 
46
46
  # Customize handling of duplicate columns for this dataset.
47
- def on_duplicate_columns(handler = (raise Error, "Must provide either an argument or a block to on_duplicate_columns" unless block_given?; nil), &block)
47
+ def on_duplicate_columns(handler = (raise Error, "Must provide either an argument or a block to on_duplicate_columns" unless defined?(yield); nil), &block)
48
48
  raise Error, "Cannot provide both an argument and a block to on_duplicate_columns" if handler && block
49
49
  clone(:on_duplicate_columns=>handler||block)
50
50
  end
@@ -102,7 +102,7 @@ class String
102
102
  # Yield the Inflections module if a block is given, and return
103
103
  # the Inflections module.
104
104
  def self.inflections
105
- yield Inflections if block_given?
105
+ yield Inflections if defined?(yield)
106
106
  Inflections
107
107
  end
108
108
 
@@ -388,6 +388,9 @@ module Sequel
388
388
 
389
389
  # Migrates the supplied database using the migration files in the specified directory. Options:
390
390
  # :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
391
+ # It is very risky to use this option, since it can result in
392
+ # the database schema version number not matching the expected
393
+ # database schema.
391
394
  # :column :: The column in the :table argument storing the migration version (default: :version).
392
395
  # :current :: The current version of the database. If not given, it is retrieved from the database
393
396
  # using the :table and :column options.
@@ -542,7 +545,7 @@ module Sequel
542
545
 
543
546
  @direction = current < target ? :up : :down
544
547
 
545
- if @direction == :down && @current >= @files.length
548
+ if @direction == :down && @current >= @files.length && !@allow_missing_migration_files
546
549
  raise Migrator::Error, "Missing migration version(s) needed to migrate down to target version (current: #{current}, target: #{target})"
547
550
  end
548
551
 
@@ -54,7 +54,7 @@ module Sequel
54
54
  # an enumerator if no block is given.
55
55
  def each_page(page_size)
56
56
  raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
57
- return to_enum(:each_page, page_size) unless block_given?
57
+ return to_enum(:each_page, page_size) unless defined?(yield)
58
58
  record_count = count
59
59
  total_pages = (record_count / page_size.to_f).ceil
60
60
  (1..total_pages).each{|page_no| yield paginate(page_no, page_size, record_count)}
@@ -329,7 +329,7 @@ end
329
329
  if defined?(Sequel::CoreRefinements)
330
330
  module Sequel::CoreRefinements
331
331
  refine Symbol do
332
- include Sequel::Postgres::ArrayOpMethods
332
+ send INCLUDE_METH, Sequel::Postgres::ArrayOpMethods
333
333
  end
334
334
  end
335
335
  end
@@ -83,7 +83,7 @@ module Sequel
83
83
  # If convert_infinite_timestamps is true and the value is infinite, return an appropriate
84
84
  # value based on the convert_infinite_timestamps setting.
85
85
  def to_application_timestamp(value)
86
- if value.is_a?(String) && (m = value.match(/((?:[-+]\d\d:\d\d)(:\d\d)?)?( BC)?\z/)) && (m[2] || m[3])
86
+ if value.is_a?(String) && (m = /((?:[-+]\d\d:\d\d)(:\d\d)?)?( BC)?\z/.match(value)) && (m[2] || m[3])
87
87
  if m[3]
88
88
  value = value.sub(' BC', '').sub(' ', ' BC ')
89
89
  conv = defined?(JRUBY_VERSION) && JRUBY_VERSION == '9.2.0.0'