sequel 5.61.0 → 5.63.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +46 -0
- data/README.rdoc +20 -19
- data/doc/advanced_associations.rdoc +13 -13
- data/doc/association_basics.rdoc +21 -15
- data/doc/cheat_sheet.rdoc +3 -3
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +4 -4
- data/doc/postgresql.rdoc +8 -8
- data/doc/querying.rdoc +1 -1
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sql.rdoc +13 -13
- data/doc/testing.rdoc +13 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/postgres.rb +4 -0
- data/lib/sequel/adapters/shared/access.rb +9 -1
- data/lib/sequel/adapters/shared/mssql.rb +9 -5
- data/lib/sequel/adapters/shared/mysql.rb +7 -0
- data/lib/sequel/adapters/shared/oracle.rb +7 -0
- data/lib/sequel/adapters/shared/postgres.rb +275 -152
- data/lib/sequel/adapters/shared/sqlanywhere.rb +7 -0
- data/lib/sequel/adapters/shared/sqlite.rb +5 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/connection_pool/threaded.rb +8 -8
- data/lib/sequel/connection_pool/timed_queue.rb +257 -0
- data/lib/sequel/connection_pool.rb +47 -30
- data/lib/sequel/database/connecting.rb +24 -0
- data/lib/sequel/database/misc.rb +8 -2
- data/lib/sequel/database/query.rb +37 -0
- data/lib/sequel/dataset/actions.rb +56 -11
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/query.rb +9 -9
- data/lib/sequel/dataset/sql.rb +5 -1
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +11 -11
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +1 -1
- data/lib/sequel/extensions/migration.rb +1 -1
- data/lib/sequel/extensions/named_timezones.rb +21 -5
- data/lib/sequel/extensions/pg_array.rb +22 -3
- data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
- data/lib/sequel/extensions/pg_extended_date_support.rb +12 -0
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +5 -0
- data/lib/sequel/extensions/pg_inet.rb +9 -10
- data/lib/sequel/extensions/pg_interval.rb +9 -10
- data/lib/sequel/extensions/pg_json.rb +10 -10
- data/lib/sequel/extensions/pg_multirange.rb +5 -10
- data/lib/sequel/extensions/pg_range.rb +5 -10
- data/lib/sequel/extensions/pg_row.rb +18 -13
- data/lib/sequel/model/associations.rb +9 -4
- data/lib/sequel/model/base.rb +6 -5
- data/lib/sequel/plugins/auto_validations.rb +53 -15
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/composition.rb +2 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/finder.rb +3 -1
- data/lib/sequel/plugins/nested_attributes.rb +4 -4
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +1 -1
- data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +20 -0
- data/lib/sequel/version.rb +1 -1
- metadata +14 -6
data/lib/sequel/dataset/query.rb
CHANGED
@@ -65,7 +65,7 @@ module Sequel
|
|
65
65
|
Sequel.synchronize{EXTENSIONS[ext] = block}
|
66
66
|
end
|
67
67
|
|
68
|
-
# On Ruby 2.4+, use clone(:
|
68
|
+
# On Ruby 2.4+, use clone(freeze: false) to create clones, because
|
69
69
|
# we use true freezing in that case, and we need to modify the opts
|
70
70
|
# in the frozen copy.
|
71
71
|
#
|
@@ -116,7 +116,7 @@ module Sequel
|
|
116
116
|
# DB[:items].order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
|
117
117
|
# DB[:items].order(:id).distinct{func(:id)} # SQL: SELECT DISTINCT ON (func(id)) * FROM items ORDER BY id
|
118
118
|
#
|
119
|
-
# There is support for
|
119
|
+
# There is support for emulating the DISTINCT ON support in MySQL, but it
|
120
120
|
# does not support the ORDER of the dataset, and also doesn't work in many
|
121
121
|
# cases if the ONLY_FULL_GROUP_BY sql_mode is used, which is the default on
|
122
122
|
# MySQL 5.7.5+.
|
@@ -787,7 +787,7 @@ module Sequel
|
|
787
787
|
# DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
|
788
788
|
# DB[:items].order(Sequel[:a] + :b) # SELECT * FROM items ORDER BY (a + b)
|
789
789
|
# DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
|
790
|
-
# DB[:items].order(Sequel.asc(:name, :
|
790
|
+
# DB[:items].order(Sequel.asc(:name, nulls: :last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
|
791
791
|
# DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
|
792
792
|
# DB[:items].order(nil) # SELECT * FROM items
|
793
793
|
def order(*columns, &block)
|
@@ -857,13 +857,13 @@ module Sequel
|
|
857
857
|
# DB[:items].returning(nil) # RETURNING NULL
|
858
858
|
# DB[:items].returning(:id, :name) # RETURNING id, name
|
859
859
|
#
|
860
|
-
# DB[:items].returning.insert(:
|
860
|
+
# DB[:items].returning.insert(a: 1) do |hash|
|
861
861
|
# # hash for each row inserted, with values for all columns
|
862
862
|
# end
|
863
|
-
# DB[:items].returning.update(:
|
863
|
+
# DB[:items].returning.update(a: 1) do |hash|
|
864
864
|
# # hash for each row updated, with values for all columns
|
865
865
|
# end
|
866
|
-
# DB[:items].returning.delete(:
|
866
|
+
# DB[:items].returning.delete(a: 1) do |hash|
|
867
867
|
# # hash for each row deleted, with values for all columns
|
868
868
|
# end
|
869
869
|
def returning(*values)
|
@@ -1102,7 +1102,7 @@ module Sequel
|
|
1102
1102
|
# referenced in window functions. See Sequel::SQL::Window for a list of
|
1103
1103
|
# options that can be passed in. Example:
|
1104
1104
|
#
|
1105
|
-
# DB[:items].window(:w, :
|
1105
|
+
# DB[:items].window(:w, partition: :c1, order: :c2)
|
1106
1106
|
# # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
|
1107
1107
|
def window(name, opts)
|
1108
1108
|
clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
|
@@ -1163,7 +1163,7 @@ module Sequel
|
|
1163
1163
|
# DB[:t].with_recursive(:t,
|
1164
1164
|
# DB[:i1].select(:id, :parent_id).where(parent_id: nil),
|
1165
1165
|
# DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
|
1166
|
-
# :
|
1166
|
+
# args: [:id, :parent_id])
|
1167
1167
|
#
|
1168
1168
|
# # WITH RECURSIVE t(id, parent_id) AS (
|
1169
1169
|
# # SELECT id, parent_id FROM i1 WHERE (parent_id IS NULL)
|
@@ -1241,7 +1241,7 @@ module Sequel
|
|
1241
1241
|
#
|
1242
1242
|
# You can also provide a method name and arguments to call to get the SQL:
|
1243
1243
|
#
|
1244
|
-
# DB[:items].with_sql(:insert_sql, :
|
1244
|
+
# DB[:items].with_sql(:insert_sql, b: 1) # INSERT INTO items (b) VALUES (1)
|
1245
1245
|
#
|
1246
1246
|
# Note that datasets that specify custom SQL using this method will generally
|
1247
1247
|
# ignore future dataset methods that modify the SQL used, as specifying custom SQL
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1725,7 +1725,7 @@ module Sequel
|
|
1725
1725
|
# Append literalization of the subselect to SQL string.
|
1726
1726
|
def subselect_sql_append(sql, ds)
|
1727
1727
|
sds = subselect_sql_dataset(sql, ds)
|
1728
|
-
sds
|
1728
|
+
subselect_sql_append_sql(sql, sds)
|
1729
1729
|
unless sds.send(:cache_sql?)
|
1730
1730
|
# If subquery dataset does not allow caching SQL,
|
1731
1731
|
# then this dataset should not allow caching SQL.
|
@@ -1737,6 +1737,10 @@ module Sequel
|
|
1737
1737
|
ds.clone(:append_sql=>sql)
|
1738
1738
|
end
|
1739
1739
|
|
1740
|
+
def subselect_sql_append_sql(sql, ds)
|
1741
|
+
ds.sql
|
1742
|
+
end
|
1743
|
+
|
1740
1744
|
# The number of decimal digits of precision to use in timestamps.
|
1741
1745
|
def timestamp_precision
|
1742
1746
|
supports_timestamp_usecs? ? 6 : 0
|
@@ -23,18 +23,6 @@ module Sequel
|
|
23
23
|
super
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
# Handle Sequel::Model instances in bound variable arrays.
|
30
|
-
def bound_variable_array(arg)
|
31
|
-
case arg
|
32
|
-
when Sequel::Model
|
33
|
-
"\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(',').gsub(/("|\\)/, '\\\\\1')})\""
|
34
|
-
else
|
35
|
-
super
|
36
|
-
end
|
37
|
-
end
|
38
26
|
end
|
39
27
|
end
|
40
28
|
end
|
@@ -5,9 +5,9 @@
|
|
5
5
|
# code
|
6
6
|
#
|
7
7
|
# DB.extension :async_thread_pool
|
8
|
-
# foos = DB[:foos].async.where
|
8
|
+
# foos = DB[:foos].async.where(name: 'A'..'M').all
|
9
9
|
# bar_names = DB[:bar].async.select_order_map(:name)
|
10
|
-
# baz_1 = DB[:bazes].async.first(:
|
10
|
+
# baz_1 = DB[:bazes].async.first(id: 1)
|
11
11
|
#
|
12
12
|
# All 3 queries will be run in separate threads. +foos+, +bar_names+
|
13
13
|
# and +baz_1+ will be proxy objects. Calling a method on the proxy
|
@@ -15,9 +15,9 @@
|
|
15
15
|
# of calling that method on the result of the query method. For example,
|
16
16
|
# if you run:
|
17
17
|
#
|
18
|
-
# foos = DB[:foos].async.where
|
18
|
+
# foos = DB[:foos].async.where(name: 'A'..'M').all
|
19
19
|
# bar_names = DB[:bars].async.select_order_map(:name)
|
20
|
-
# baz_1 = DB[:bazes].async.first(:
|
20
|
+
# baz_1 = DB[:bazes].async.first(id: 1)
|
21
21
|
# sleep(1)
|
22
22
|
# foos.size
|
23
23
|
# bar_names.first
|
@@ -26,9 +26,9 @@
|
|
26
26
|
# These three queries will generally be run concurrently in separate
|
27
27
|
# threads. If you instead run:
|
28
28
|
#
|
29
|
-
# DB[:foos].async.where
|
29
|
+
# DB[:foos].async.where(name: 'A'..'M').all.size
|
30
30
|
# DB[:bars].async.select_order_map(:name).first
|
31
|
-
# DB[:bazes].async.first(:
|
31
|
+
# DB[:bazes].async.first(id: 1).name
|
32
32
|
#
|
33
33
|
# Then will run each query sequentially, since you need the result of
|
34
34
|
# one query before running the next query. The queries will still be
|
@@ -37,11 +37,11 @@
|
|
37
37
|
# What is run in the separate thread is the entire method call that
|
38
38
|
# returns results. So with the original example:
|
39
39
|
#
|
40
|
-
# foos = DB[:foos].async.where
|
40
|
+
# foos = DB[:foos].async.where(name: 'A'..'M').all
|
41
41
|
# bar_names = DB[:bars].async.select_order_map(:name)
|
42
|
-
# baz_1 = DB[:bazes].async.first(:
|
42
|
+
# baz_1 = DB[:bazes].async.first(id: 1)
|
43
43
|
#
|
44
|
-
# The +all+, <tt>select_order_map(:name)</tt>, and <tt>first(:
|
44
|
+
# The +all+, <tt>select_order_map(:name)</tt>, and <tt>first(id: 1)</tt>
|
45
45
|
# calls are run in separate threads. If a block is passed to a method
|
46
46
|
# such as +all+ or +each+, the block is also run in that thread. If you
|
47
47
|
# have code such as:
|
@@ -156,10 +156,10 @@
|
|
156
156
|
# so that the query will run in the current thread instead of waiting
|
157
157
|
# for an async thread to become available. With the following code:
|
158
158
|
#
|
159
|
-
# foos = DB[:foos].async.where
|
159
|
+
# foos = DB[:foos].async.where(name: 'A'..'M').all
|
160
160
|
# bar_names = DB[:bar].async.select_order_map(:name)
|
161
161
|
# if foos.length > 4
|
162
|
-
# baz_1 = DB[:bazes].async.first(:
|
162
|
+
# baz_1 = DB[:bazes].async.first(id: 1)
|
163
163
|
# end
|
164
164
|
#
|
165
165
|
# Whether you need the +baz_1+ variable depends on the value of foos.
|
@@ -22,7 +22,7 @@
|
|
22
22
|
#
|
23
23
|
# Named placeholders can also be used with a hash:
|
24
24
|
#
|
25
|
-
# ds.where("name > :a", :
|
25
|
+
# ds.where("name > :a", a: "A")
|
26
26
|
# # SELECT * FROM table WHERE (name > 'A')
|
27
27
|
#
|
28
28
|
# This extension also allows the use of a plain string passed to Dataset#update:
|
@@ -126,7 +126,7 @@
|
|
126
126
|
# be emulated by dropping the table and recreating it with the constraints.
|
127
127
|
# If you want to use this plugin on SQLite with an alter_table block,
|
128
128
|
# you should drop all constraint validation metadata using
|
129
|
-
# <tt>drop_constraint_validations_for(:
|
129
|
+
# <tt>drop_constraint_validations_for(table: 'table')</tt>, and then
|
130
130
|
# readd all constraints you want to use inside the alter table block,
|
131
131
|
# making no other changes inside the alter_table block.
|
132
132
|
#
|
@@ -25,7 +25,7 @@
|
|
25
25
|
# By default, values are casted to the generic timestamp type for the
|
26
26
|
# database. You can override the cast type using the :cast option:
|
27
27
|
#
|
28
|
-
# add = Sequel.date_add(:date_column, {years: 1, months: 2, days: 3}, :
|
28
|
+
# add = Sequel.date_add(:date_column, {years: 1, months: 2, days: 3}, cast: :timestamptz)
|
29
29
|
#
|
30
30
|
# These expressions can be used in your datasets, or anywhere else that
|
31
31
|
# Sequel expressions are allowed:
|
@@ -377,7 +377,7 @@ module Sequel
|
|
377
377
|
# Raise a NotCurrentError unless the migrator is current, takes the same
|
378
378
|
# arguments as #run.
|
379
379
|
def self.check_current(*args)
|
380
|
-
raise(NotCurrentError, '
|
380
|
+
raise(NotCurrentError, 'current migration version does not match latest available version') unless is_current?(*args)
|
381
381
|
end
|
382
382
|
|
383
383
|
# Return whether the migrator is current (i.e. it does not need to make
|
@@ -68,6 +68,10 @@ module Sequel
|
|
68
68
|
private
|
69
69
|
|
70
70
|
if RUBY_VERSION >= '2.6'
|
71
|
+
# Whether Time.at with :nsec and :in is broken. True on JRuby < 9.3.9.0.
|
72
|
+
BROKEN_TIME_AT_WITH_NSEC = defined?(JRUBY_VERSION) && (JRUBY_VERSION < '9.3' || (JRUBY_VERSION < '9.4' && JRUBY_VERSION.split('.')[2].to_i < 9))
|
73
|
+
private_constant :BROKEN_TIME_AT_WITH_NSEC
|
74
|
+
|
71
75
|
# Convert the given input Time (which must be in UTC) to the given input timezone,
|
72
76
|
# which should be a TZInfo::Timezone instance.
|
73
77
|
def convert_input_time_other(v, input_timezone)
|
@@ -76,33 +80,45 @@ module Sequel
|
|
76
80
|
raise unless disamb = tzinfo_disambiguator_for(v)
|
77
81
|
period = input_timezone.period_for_local(v, &disamb)
|
78
82
|
offset = period.utc_total_offset
|
79
|
-
|
83
|
+
# :nocov:
|
84
|
+
if BROKEN_TIME_AT_WITH_NSEC
|
85
|
+
Time.at(v.to_i - offset, :in => input_timezone) + v.nsec/1000000000.0
|
86
|
+
# :nocov:
|
87
|
+
else
|
88
|
+
Time.at(v.to_i - offset, v.nsec, :nsec, :in => input_timezone)
|
89
|
+
end
|
80
90
|
end
|
81
91
|
|
82
92
|
# Convert the given input Time to the given output timezone,
|
83
93
|
# which should be a TZInfo::Timezone instance.
|
84
94
|
def convert_output_time_other(v, output_timezone)
|
85
|
-
|
95
|
+
# :nocov:
|
96
|
+
if BROKEN_TIME_AT_WITH_NSEC
|
97
|
+
Time.at(v.to_i, :in => output_timezone) + v.nsec/1000000000.0
|
98
|
+
# :nocov:
|
99
|
+
else
|
100
|
+
Time.at(v.to_i, v.nsec, :nsec, :in => output_timezone)
|
101
|
+
end
|
86
102
|
end
|
87
103
|
# :nodoc:
|
88
104
|
# :nocov:
|
89
105
|
else
|
90
106
|
def convert_input_time_other(v, input_timezone)
|
91
107
|
local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
92
|
-
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
|
108
|
+
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
|
93
109
|
end
|
94
110
|
|
95
111
|
if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
|
96
112
|
def convert_output_time_other(v, output_timezone)
|
97
113
|
v = output_timezone.utc_to_local(v.getutc)
|
98
114
|
local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
99
|
-
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + local_offset
|
115
|
+
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0 + local_offset
|
100
116
|
end
|
101
117
|
else
|
102
118
|
def convert_output_time_other(v, output_timezone)
|
103
119
|
v = output_timezone.utc_to_local(v.getutc)
|
104
120
|
local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
|
105
|
-
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
|
121
|
+
Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
|
106
122
|
end
|
107
123
|
end
|
108
124
|
# :nodoc:
|
@@ -228,16 +228,27 @@ module Sequel
|
|
228
228
|
when Array
|
229
229
|
"{#{a.map{|i| bound_variable_array(i)}.join(',')}}"
|
230
230
|
when Sequel::SQL::Blob
|
231
|
-
|
231
|
+
bound_variable_array_string(literal(a)[BLOB_RANGE].gsub("''", "'"))
|
232
232
|
when Sequel::LiteralString
|
233
233
|
a
|
234
234
|
when String
|
235
|
-
|
235
|
+
bound_variable_array_string(a)
|
236
236
|
else
|
237
|
-
|
237
|
+
if (s = bound_variable_arg(a, nil)).is_a?(String)
|
238
|
+
bound_variable_array_string(s)
|
239
|
+
else
|
240
|
+
literal(a)
|
241
|
+
end
|
238
242
|
end
|
239
243
|
end
|
240
244
|
|
245
|
+
# Escape strings used as array members in bound variables. Most complex
|
246
|
+
# will create a regular string with bound_variable_arg, and then use this
|
247
|
+
# escaping to format it as an array member.
|
248
|
+
def bound_variable_array_string(s)
|
249
|
+
"\"#{s.gsub(/("|\\)/, '\\\\\1')}\""
|
250
|
+
end
|
251
|
+
|
241
252
|
# Look into both the current database's array schema types and the global
|
242
253
|
# array schema types to get the type symbol for the given database type
|
243
254
|
# string.
|
@@ -457,6 +468,14 @@ module Sequel
|
|
457
468
|
end
|
458
469
|
end
|
459
470
|
|
471
|
+
# Allow automatic parameterization of the receiver if all elements can be
|
472
|
+
# can be automatically parameterized.
|
473
|
+
def sequel_auto_param_type(ds)
|
474
|
+
if array_type && all?{|x| nil == x || ds.send(:auto_param_type, x)}
|
475
|
+
"::#{array_type}[]"
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
460
479
|
private
|
461
480
|
|
462
481
|
# Recursive method that handles multi-dimensional
|