sequel 5.48.0 → 5.52.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +80 -0
- data/README.rdoc +12 -5
- data/doc/migration.rdoc +1 -1
- data/doc/opening_databases.rdoc +1 -1
- data/doc/postgresql.rdoc +8 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/testing.rdoc +3 -1
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +3 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +9 -11
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +42 -44
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +3 -3
- data/lib/sequel/adapters/postgres.rb +27 -29
- data/lib/sequel/adapters/shared/access.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +2 -0
- data/lib/sequel/adapters/shared/mysql.rb +4 -2
- data/lib/sequel/adapters/shared/postgres.rb +59 -6
- data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
- data/lib/sequel/adapters/shared/sqlite.rb +1 -1
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +16 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/core.rb +17 -18
- data/lib/sequel/database/connecting.rb +2 -2
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/dataset/actions.rb +2 -2
- data/lib/sequel/dataset/query.rb +45 -3
- data/lib/sequel/dataset/sql.rb +18 -9
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/inflector.rb +1 -1
- data/lib/sequel/extensions/migration.rb +4 -1
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array_ops.rb +1 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +1 -0
- data/lib/sequel/extensions/pg_json.rb +3 -5
- data/lib/sequel/extensions/pg_json_ops.rb +71 -1
- data/lib/sequel/extensions/pg_multirange.rb +372 -0
- data/lib/sequel/extensions/pg_range.rb +4 -12
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +108 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/model/associations.rb +3 -1
- data/lib/sequel/model/base.rb +9 -13
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/plugins/auto_validations.rb +25 -5
- data/lib/sequel/plugins/column_encryption.rb +1 -1
- data/lib/sequel/plugins/composition.rb +1 -0
- data/lib/sequel/plugins/json_serializer.rb +2 -2
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/serialization.rb +1 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
- data/lib/sequel/plugins/unused_associations.rb +2 -2
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +7 -1
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- 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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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.
|
@@ -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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
28
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
310
|
-
|
311
|
-
|
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
|
58
|
+
if defined?(yield)
|
59
59
|
return yield(db)
|
60
60
|
end
|
61
61
|
ensure
|
62
|
-
if
|
62
|
+
if defined?(yield)
|
63
63
|
db.disconnect if db
|
64
64
|
Sequel.synchronize{::Sequel::DATABASES.delete(db)}
|
65
65
|
end
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -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)
|
@@ -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 =>
|
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
|
549
|
+
unless defined?(yield)
|
550
550
|
return enum_for(:paged_each, opts)
|
551
551
|
end
|
552
552
|
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -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
|
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.
|
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)
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -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
|
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
|
-
|
1542
|
-
return if !
|
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
|
-
|
1551
|
+
ctes.each do |cte|
|
1547
1552
|
sql << comma if c
|
1548
|
-
|
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]
|
@@ -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
|
-
|
165
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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 && !
|
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
|
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
|
@@ -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
|
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)}
|
@@ -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 =
|
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'
|