sequel 5.60.1 → 5.62.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +44 -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.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -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.rb +42 -28
- data/lib/sequel/database/connecting.rb +24 -0
- data/lib/sequel/database/misc.rb +62 -12
- data/lib/sequel/database/query.rb +37 -0
- data/lib/sequel/dataset/actions.rb +31 -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/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +1 -1
- data/lib/sequel/extensions/named_timezones.rb +17 -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 +27 -24
- 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 +10 -11
- data/lib/sequel/extensions/pg_interval.rb +10 -11
- data/lib/sequel/extensions/pg_json.rb +10 -10
- data/lib/sequel/extensions/pg_json_ops.rb +0 -52
- data/lib/sequel/extensions/pg_multirange.rb +5 -10
- data/lib/sequel/extensions/pg_range.rb +6 -11
- data/lib/sequel/extensions/pg_row.rb +18 -13
- data/lib/sequel/model/associations.rb +7 -2
- 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/sql_comments.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +20 -0
- data/lib/sequel/version.rb +2 -2
- metadata +12 -5
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The pg_extended_integer_support extension supports literalizing
|
4
|
+
# Ruby integers outside of PostgreSQL bigint range on PostgreSQL.
|
5
|
+
# Sequel by default will raise exceptions when
|
6
|
+
# literalizing such integers, as PostgreSQL would treat them
|
7
|
+
# as numeric type values instead of integer/bigint type values
|
8
|
+
# if unquoted, which can result in unexpected negative performance
|
9
|
+
# (e.g. forcing sequential scans when index scans would be used for
|
10
|
+
# an integer/bigint type).
|
11
|
+
#
|
12
|
+
# To load the extension into a Dataset (this returns a new Dataset):
|
13
|
+
#
|
14
|
+
# dataset = dataset.extension(:pg_extended_integer_support)
|
15
|
+
#
|
16
|
+
# To load the extension into a Database, so it affects all of the
|
17
|
+
# Database's datasets:
|
18
|
+
#
|
19
|
+
# DB.extension :pg_extended_integer_support
|
20
|
+
#
|
21
|
+
# By default, the extension will quote integers outside
|
22
|
+
# bigint range:
|
23
|
+
#
|
24
|
+
# DB.literal(2**63) # => "'9223372036854775808'"
|
25
|
+
#
|
26
|
+
# Quoting the value treats the type as unknown:
|
27
|
+
#
|
28
|
+
# DB.get{pg_typeof(2**63)} # => 'unknown'
|
29
|
+
#
|
30
|
+
# PostgreSQL will implicitly cast the unknown type to the appropriate
|
31
|
+
# database type, raising an error if it cannot be casted. Be aware this
|
32
|
+
# can result in the integer value being implicitly casted to text or
|
33
|
+
# any other PostgreSQL type:
|
34
|
+
#
|
35
|
+
# # Returns a string, not an integer:
|
36
|
+
# DB.get{2**63}
|
37
|
+
# # => "9223372036854775808"
|
38
|
+
#
|
39
|
+
# You can use the Dataset#integer_outside_bigint_range_strategy method
|
40
|
+
# with the value +:raw+ to change the strategy to not quote the variable:
|
41
|
+
#
|
42
|
+
# DB.dataset.
|
43
|
+
# integer_outside_bigint_range_strategy(:raw).
|
44
|
+
# literal(2**63)
|
45
|
+
# # => "9223372036854775808"
|
46
|
+
#
|
47
|
+
# Note that not quoting the value will result in PostgreSQL treating
|
48
|
+
# the type as numeric instead of integer:
|
49
|
+
#
|
50
|
+
# DB.dataset.
|
51
|
+
# integer_outside_bigint_range_strategy(:raw).
|
52
|
+
# get{pg_typeof(2**63)}
|
53
|
+
# # => "numeric"
|
54
|
+
#
|
55
|
+
# The +:raw+ behavior was Sequel's historical behavior, but unless
|
56
|
+
# you fully understand the reprecussions of PostgreSQL using a
|
57
|
+
# numeric type for integer values, you should not use it.
|
58
|
+
#
|
59
|
+
# To get the current default behavior of raising an exception for
|
60
|
+
# integers outside of PostgreSQL bigint range, you can use a strategy
|
61
|
+
# of +:raise+.
|
62
|
+
#
|
63
|
+
# To specify a default strategy for handling integers outside
|
64
|
+
# bigint range that applies to all of a Database's datasets, you can
|
65
|
+
# use the +:integer_outside_bigint_range_strategy+ Database option with
|
66
|
+
# a value of +:raise+ or +:raw+:
|
67
|
+
#
|
68
|
+
# DB.opts[:integer_outside_bigint_range_strategy] = :raw
|
69
|
+
#
|
70
|
+
# The Database option will be used as a fallback if you did not call
|
71
|
+
# the Dataset#integer_outside_bigint_range_strategy method to specify
|
72
|
+
# a strategy for the dataset.
|
73
|
+
#
|
74
|
+
# Related module: Sequel::Postgres::ExtendedIntegerSupport
|
75
|
+
|
76
|
+
#
|
77
|
+
module Sequel
|
78
|
+
module Postgres
|
79
|
+
module ExtendedIntegerSupport
|
80
|
+
# Set the strategy for handling integers outside PostgreSQL
|
81
|
+
# bigint range. Supported values:
|
82
|
+
#
|
83
|
+
# :quote :: Quote the integer value. PostgreSQL will treat
|
84
|
+
# the integer as a unknown type, implicitly casting
|
85
|
+
# to any other type as needed. This is the default
|
86
|
+
# value when using the pg_extended_integer_support
|
87
|
+
# extension.
|
88
|
+
# :raise :: Raise error when attempting to literalize the integer
|
89
|
+
# (the default behavior of Sequel on PostgreSQL when
|
90
|
+
# not using the pg_extended_integer_support extension).
|
91
|
+
# :raw :: Use raw integer value without quoting. PostgreSQL
|
92
|
+
# will treat the integer as a numeric. This was Sequel's
|
93
|
+
# historical behavior, but it is unlikely to be desired.
|
94
|
+
def integer_outside_bigint_range_strategy(strategy)
|
95
|
+
clone(:integer_outside_bigint_range_strategy=>strategy)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Handle integers outside the bigint range by using
|
101
|
+
# the configured strategy.
|
102
|
+
def literal_integer_outside_bigint_range(v)
|
103
|
+
case @opts[:integer_outside_bigint_range_strategy] || @db.opts[:integer_outside_bigint_range_strategy]
|
104
|
+
when :raise
|
105
|
+
super
|
106
|
+
when :raw
|
107
|
+
v.to_s
|
108
|
+
else # when :quote
|
109
|
+
"'#{v}'"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
Dataset.register_extension(:pg_extended_integer_support, Postgres::ExtendedIntegerSupport)
|
116
|
+
end
|
@@ -75,16 +75,6 @@ module Sequel
|
|
75
75
|
|
76
76
|
private
|
77
77
|
|
78
|
-
# Handle inet[]/cidr[] types in bound variables.
|
79
|
-
def bound_variable_array(a)
|
80
|
-
case a
|
81
|
-
when IPAddr
|
82
|
-
"\"#{a.to_s}/#{a.instance_variable_get(:@mask_addr).to_s(2).count('1')}\""
|
83
|
-
else
|
84
|
-
super
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
78
|
# Make the column type detection recognize the inet and cidr types.
|
89
79
|
def schema_column_type(db_type)
|
90
80
|
case db_type
|
@@ -111,7 +101,7 @@ module Sequel
|
|
111
101
|
when IPAddr
|
112
102
|
value
|
113
103
|
when String
|
114
|
-
IPAddr.new(value)
|
104
|
+
IPAddr.new(typecast_check_string_length(value, 100))
|
115
105
|
else
|
116
106
|
raise Sequel::InvalidValue, "invalid value for inet/cidr: #{value.inspect}"
|
117
107
|
end
|
@@ -121,6 +111,15 @@ module Sequel
|
|
121
111
|
module InetDatasetMethods
|
122
112
|
private
|
123
113
|
|
114
|
+
# Allow auto parameterization of IPAddr instances.
|
115
|
+
def auto_param_type_fallback(v)
|
116
|
+
if defined?(super) && (type = super)
|
117
|
+
type
|
118
|
+
elsif IPAddr === v
|
119
|
+
"::inet"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
124
123
|
# Convert IPAddr value to a string and append a literal version
|
125
124
|
# of the string to the sql.
|
126
125
|
def literal_other_append(sql, value)
|
@@ -163,16 +163,6 @@ module Sequel
|
|
163
163
|
|
164
164
|
private
|
165
165
|
|
166
|
-
# Handle arrays of interval types in bound variables.
|
167
|
-
def bound_variable_array(a)
|
168
|
-
case a
|
169
|
-
when ActiveSupport::Duration
|
170
|
-
"\"#{IntervalDatabaseMethods.literal_duration(a)}\""
|
171
|
-
else
|
172
|
-
super
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
166
|
# Set the :ruby_default value if the default value is recognized as an interval.
|
177
167
|
def schema_post_process(_)
|
178
168
|
super.each do |a|
|
@@ -197,7 +187,7 @@ module Sequel
|
|
197
187
|
when Numeric
|
198
188
|
ActiveSupport::Duration.new(value, [[:seconds, value]])
|
199
189
|
when String
|
200
|
-
PARSER.call(value)
|
190
|
+
PARSER.call(typecast_check_string_length(value, 1000))
|
201
191
|
else
|
202
192
|
raise Sequel::InvalidValue, "invalid value for interval type: #{value.inspect}"
|
203
193
|
end
|
@@ -207,6 +197,15 @@ module Sequel
|
|
207
197
|
module IntervalDatasetMethods
|
208
198
|
private
|
209
199
|
|
200
|
+
# Allow auto parameterization of ActiveSupport::Duration instances.
|
201
|
+
def auto_param_type_fallback(v)
|
202
|
+
if defined?(super) && (type = super)
|
203
|
+
type
|
204
|
+
elsif ActiveSupport::Duration === v
|
205
|
+
"::interval"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
210
209
|
# Handle literalization of ActiveSupport::Duration objects, treating them as
|
211
210
|
# PostgreSQL intervals.
|
212
211
|
def literal_other_append(sql, v)
|
@@ -142,6 +142,11 @@ module Sequel
|
|
142
142
|
ds.literal_append(sql, Sequel.object_to_json(self))
|
143
143
|
sql << '::json'
|
144
144
|
end
|
145
|
+
|
146
|
+
# Allow automatic parameterization.
|
147
|
+
def sequel_auto_param_type(ds)
|
148
|
+
"::json"
|
149
|
+
end
|
145
150
|
end
|
146
151
|
|
147
152
|
jsonb_class = Class.new(base_class) do
|
@@ -151,6 +156,11 @@ module Sequel
|
|
151
156
|
ds.literal_append(sql, Sequel.object_to_json(self))
|
152
157
|
sql << '::jsonb'
|
153
158
|
end
|
159
|
+
|
160
|
+
# Allow automatic parameterization.
|
161
|
+
def sequel_auto_param_type(ds)
|
162
|
+
"::jsonb"
|
163
|
+
end
|
154
164
|
end
|
155
165
|
|
156
166
|
const_set(:"JSON#{name}Base", base_class)
|
@@ -424,16 +434,6 @@ module Sequel
|
|
424
434
|
end
|
425
435
|
end
|
426
436
|
|
427
|
-
# Handle json[] and jsonb[] types in bound variables.
|
428
|
-
def bound_variable_array(a)
|
429
|
-
case a
|
430
|
-
when JSONObject, JSONBObject
|
431
|
-
"\"#{Sequel.object_to_json(a).gsub('"', '\\"')}\""
|
432
|
-
else
|
433
|
-
super
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
437
|
# Make the column type detection recognize the json types.
|
438
438
|
def schema_column_type(db_type)
|
439
439
|
case db_type
|
@@ -123,15 +123,6 @@
|
|
123
123
|
# c = Sequel.pg_jsonb_op(:c)
|
124
124
|
# DB[:t].update(c['key1'] => 1.to_json, c['key2'] => "a".to_json)
|
125
125
|
#
|
126
|
-
# On PostgreSQL 15+, the <tt>IS [NOT] JSON</tt> operator is supported:
|
127
|
-
#
|
128
|
-
# j.is_json # j IS JSON
|
129
|
-
# j.is_json(type: :object) # j IS JSON OBJECT
|
130
|
-
# j.is_json(type: :object, unique: true) # j IS JSON OBJECT WITH UNIQUE
|
131
|
-
# j.is_not_json # j IS NOT JSON
|
132
|
-
# j.is_json(type: :array) # j IS NOT JSON ARRAY
|
133
|
-
# j.is_json(unique: true) # j IS NOT JSON WITH UNIQUE
|
134
|
-
#
|
135
126
|
# If you are also using the pg_json extension, you should load it before
|
136
127
|
# loading this extension. Doing so will allow you to use the #op method on
|
137
128
|
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
@@ -160,18 +151,6 @@ module Sequel
|
|
160
151
|
GET_PATH = ["(".freeze, " #> ".freeze, ")".freeze].freeze
|
161
152
|
GET_PATH_TEXT = ["(".freeze, " #>> ".freeze, ")".freeze].freeze
|
162
153
|
|
163
|
-
IS_JSON = ["(".freeze, " IS JSON".freeze, "".freeze, ")".freeze].freeze
|
164
|
-
IS_NOT_JSON = ["(".freeze, " IS NOT JSON".freeze, "".freeze, ")".freeze].freeze
|
165
|
-
EMPTY_STRING = Sequel::LiteralString.new('').freeze
|
166
|
-
WITH_UNIQUE = Sequel::LiteralString.new(' WITH UNIQUE').freeze
|
167
|
-
IS_JSON_MAP = {
|
168
|
-
nil => EMPTY_STRING,
|
169
|
-
:value => Sequel::LiteralString.new(' VALUE').freeze,
|
170
|
-
:scalar => Sequel::LiteralString.new(' SCALAR').freeze,
|
171
|
-
:object => Sequel::LiteralString.new(' OBJECT').freeze,
|
172
|
-
:array => Sequel::LiteralString.new(' ARRAY').freeze
|
173
|
-
}.freeze
|
174
|
-
|
175
154
|
# Get JSON array element or object field as json. If an array is given,
|
176
155
|
# gets the object at the specified path.
|
177
156
|
#
|
@@ -254,30 +233,6 @@ module Sequel
|
|
254
233
|
end
|
255
234
|
end
|
256
235
|
|
257
|
-
# Return whether the json object can be parsed as JSON.
|
258
|
-
#
|
259
|
-
# Options:
|
260
|
-
# :type :: Check whether the json object can be parsed as a specific type
|
261
|
-
# of JSON (:value, :scalar, :object, :array).
|
262
|
-
# :unique :: Check JSON objects for unique keys.
|
263
|
-
#
|
264
|
-
# json_op.is_json # json IS JSON
|
265
|
-
# json_op.is_json(type: :object) # json IS JSON OBJECT
|
266
|
-
# json_op.is_json(unique: true) # json IS JSON WITH UNIQUE
|
267
|
-
def is_json(opts=OPTS)
|
268
|
-
_is_json(IS_JSON, opts)
|
269
|
-
end
|
270
|
-
|
271
|
-
# Return whether the json object cannot be parsed as JSON. The opposite
|
272
|
-
# of #is_json. See #is_json for options.
|
273
|
-
#
|
274
|
-
# json_op.is_not_json # json IS NOT JSON
|
275
|
-
# json_op.is_not_json(type: :object) # json IS NOT JSON OBJECT
|
276
|
-
# json_op.is_not_json(unique: true) # json IS NOT JSON WITH UNIQUE
|
277
|
-
def is_not_json(opts=OPTS)
|
278
|
-
_is_json(IS_NOT_JSON, opts)
|
279
|
-
end
|
280
|
-
|
281
236
|
# Returns a set of keys AS text in the json object.
|
282
237
|
#
|
283
238
|
# json_op.keys # json_object_keys(json)
|
@@ -331,13 +286,6 @@ module Sequel
|
|
331
286
|
|
332
287
|
private
|
333
288
|
|
334
|
-
# Internals of IS [NOT] JSON support
|
335
|
-
def _is_json(lit_array, opts)
|
336
|
-
raise Error, "invalid is_json :type option: #{opts[:type].inspect}" unless type = IS_JSON_MAP[opts[:type]]
|
337
|
-
unique = opts[:unique] ? WITH_UNIQUE : EMPTY_STRING
|
338
|
-
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(lit_array, [self, type, unique]))
|
339
|
-
end
|
340
|
-
|
341
289
|
# Return a placeholder literal with the given str and args, wrapped
|
342
290
|
# in an JSONOp or JSONBOp, used by operators that return json or jsonb.
|
343
291
|
def json_op(str, args)
|
@@ -220,16 +220,6 @@ module Sequel
|
|
220
220
|
|
221
221
|
private
|
222
222
|
|
223
|
-
# Handle arrays of multirange types in bound variables.
|
224
|
-
def bound_variable_array(a)
|
225
|
-
case a
|
226
|
-
when PGMultiRange
|
227
|
-
"\"#{bound_variable_arg(a, nil)}\""
|
228
|
-
else
|
229
|
-
super
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
223
|
# Recognize the registered database multirange types.
|
234
224
|
def schema_column_type(db_type)
|
235
225
|
@pg_multirange_schema_types[db_type] || super
|
@@ -346,6 +336,11 @@ module Sequel
|
|
346
336
|
|
347
337
|
val << "}"
|
348
338
|
end
|
339
|
+
|
340
|
+
# Allow automatic parameterization.
|
341
|
+
def sequel_auto_param_type(ds)
|
342
|
+
"::#{db_type}"
|
343
|
+
end
|
349
344
|
end
|
350
345
|
end
|
351
346
|
|
@@ -233,16 +233,6 @@ module Sequel
|
|
233
233
|
|
234
234
|
private
|
235
235
|
|
236
|
-
# Handle arrays of range types in bound variables.
|
237
|
-
def bound_variable_array(a)
|
238
|
-
case a
|
239
|
-
when PGRange, Range
|
240
|
-
"\"#{bound_variable_arg(a, nil)}\""
|
241
|
-
else
|
242
|
-
super
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
236
|
# Recognize the registered database range types.
|
247
237
|
def schema_column_type(db_type)
|
248
238
|
@pg_range_schema_types[db_type] || super
|
@@ -282,7 +272,7 @@ module Sequel
|
|
282
272
|
when Range
|
283
273
|
PGRange.from_range(value, parser.db_type)
|
284
274
|
when String
|
285
|
-
parser.call(value)
|
275
|
+
parser.call(typecast_check_string_length(value, 100))
|
286
276
|
else
|
287
277
|
raise Sequel::InvalidValue, "invalid value for range type: #{value.inspect}"
|
288
278
|
end
|
@@ -491,6 +481,11 @@ module Sequel
|
|
491
481
|
end
|
492
482
|
end
|
493
483
|
|
484
|
+
# Allow automatic parameterization for ranges with types.
|
485
|
+
def sequel_auto_param_type(ds)
|
486
|
+
"::#{db_type}" if db_type
|
487
|
+
end
|
488
|
+
|
494
489
|
private
|
495
490
|
|
496
491
|
# Escape common range types. Instead of quoting, just backslash escape all
|
@@ -136,6 +136,15 @@ module Sequel
|
|
136
136
|
ds.quote_schema_table_append(sql, db_type)
|
137
137
|
end
|
138
138
|
end
|
139
|
+
|
140
|
+
# Allow automatic parameterization if all values support it.
|
141
|
+
def sequel_auto_param_type(ds)
|
142
|
+
if db_type && all?{|v| nil == v || ds.send(:auto_param_type, v)}
|
143
|
+
s = String.new << "::"
|
144
|
+
ds.quote_schema_table_append(s, db_type)
|
145
|
+
s
|
146
|
+
end
|
147
|
+
end
|
139
148
|
end
|
140
149
|
|
141
150
|
# Class for row-valued/composite types that are treated as hashes.
|
@@ -208,6 +217,15 @@ module Sequel
|
|
208
217
|
ds.quote_schema_table_append(sql, db_type)
|
209
218
|
end
|
210
219
|
end
|
220
|
+
|
221
|
+
# Allow automatic parameterization if all values support it.
|
222
|
+
def sequel_auto_param_type(ds)
|
223
|
+
if db_type && all?{|_,v| nil == v || ds.send(:auto_param_type, v)}
|
224
|
+
s = String.new << "::"
|
225
|
+
ds.quote_schema_table_append(s, db_type)
|
226
|
+
s
|
227
|
+
end
|
228
|
+
end
|
211
229
|
end
|
212
230
|
|
213
231
|
ROW_TYPE_CLASSES = [HashRow, ArrayRow].freeze
|
@@ -519,19 +537,6 @@ module Sequel
|
|
519
537
|
|
520
538
|
private
|
521
539
|
|
522
|
-
# Format composite types used in bound variable arrays.
|
523
|
-
def bound_variable_array(arg)
|
524
|
-
case arg
|
525
|
-
when ArrayRow
|
526
|
-
"\"(#{arg.map{|v| bound_variable_array(v) if v}.join(',').gsub(/("|\\)/, '\\\\\1')})\""
|
527
|
-
when HashRow
|
528
|
-
arg.check_columns!
|
529
|
-
"\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(',').gsub(/("|\\)/, '\\\\\1')})\""
|
530
|
-
else
|
531
|
-
super
|
532
|
-
end
|
533
|
-
end
|
534
|
-
|
535
540
|
# Make the column type detection handle registered row types.
|
536
541
|
def schema_column_type(db_type)
|
537
542
|
if type = @row_schema_types[db_type]
|
@@ -276,7 +276,7 @@ module Sequel
|
|
276
276
|
|
277
277
|
if eo[:no_results]
|
278
278
|
no_results = true
|
279
|
-
elsif eo[:eager_block] || eo[:loader] == false
|
279
|
+
elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
|
280
280
|
ds = eager_loading_dataset(eo)
|
281
281
|
|
282
282
|
strategy = ds.opts[:eager_limit_strategy] || strategy
|
@@ -299,6 +299,11 @@ module Sequel
|
|
299
299
|
strategy = :ruby if strategy == :correlated_subquery
|
300
300
|
strategy = nil if strategy == :ruby && assign_singular?
|
301
301
|
objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
|
302
|
+
|
303
|
+
if strategy == :window_function
|
304
|
+
delete_rn = ds.row_number_column
|
305
|
+
objects.each{|obj| obj.values.delete(delete_rn)}
|
306
|
+
end
|
302
307
|
elsif strategy == :union
|
303
308
|
objects = []
|
304
309
|
ds = associated_dataset
|
@@ -817,7 +822,7 @@ module Sequel
|
|
817
822
|
|
818
823
|
# Whether the placeholder loader can be used to load the association.
|
819
824
|
def use_placeholder_loader?
|
820
|
-
self[:use_placeholder_loader]
|
825
|
+
self[:use_placeholder_loader] && _associated_dataset.supports_placeholder_literalizer?
|
821
826
|
end
|
822
827
|
end
|
823
828
|
|
data/lib/sequel/model/base.rb
CHANGED
@@ -606,6 +606,7 @@ module Sequel
|
|
606
606
|
@db_schema = get_db_schema
|
607
607
|
end
|
608
608
|
|
609
|
+
@fast_pk_lookup_sql = @fast_instance_delete_sql = nil unless @dataset.supports_placeholder_literalizer?
|
609
610
|
reset_instance_dataset
|
610
611
|
self
|
611
612
|
end
|
@@ -1141,7 +1142,7 @@ module Sequel
|
|
1141
1142
|
#
|
1142
1143
|
# Artist[1] === Artist[1] # => true
|
1143
1144
|
# Artist.new === Artist.new # => false
|
1144
|
-
# Artist[1].set(:
|
1145
|
+
# Artist[1].set(name: 'Bob') === Artist[1] # => true
|
1145
1146
|
def ===(obj)
|
1146
1147
|
case pkv = pk
|
1147
1148
|
when nil
|
@@ -1160,7 +1161,7 @@ module Sequel
|
|
1160
1161
|
#
|
1161
1162
|
# Artist[1].pk_equal?(Artist[1]) # => true
|
1162
1163
|
# Artist.new.pk_equal?(Artist.new) # => false
|
1163
|
-
# Artist[1].set(:
|
1164
|
+
# Artist[1].set(name: 'Bob').pk_equal?(Artist[1]) # => true
|
1164
1165
|
alias pk_equal? ===
|
1165
1166
|
|
1166
1167
|
# class is defined in Object, but it is also a keyword,
|
@@ -1232,7 +1233,7 @@ module Sequel
|
|
1232
1233
|
#
|
1233
1234
|
# Artist[1] == Artist[1] # => true
|
1234
1235
|
# Artist.new == Artist.new # => true
|
1235
|
-
# Artist[1].set(:
|
1236
|
+
# Artist[1].set(name: 'Bob') == Artist[1] # => false
|
1236
1237
|
def eql?(obj)
|
1237
1238
|
(obj.class == model) && (obj.values == @values)
|
1238
1239
|
end
|
@@ -1334,13 +1335,13 @@ module Sequel
|
|
1334
1335
|
# a = Artist[1]
|
1335
1336
|
# Artist.db.transaction do
|
1336
1337
|
# a.lock!
|
1337
|
-
# a.update(:
|
1338
|
+
# a.update(name: 'A')
|
1338
1339
|
# end
|
1339
1340
|
#
|
1340
1341
|
# a = Artist[2]
|
1341
1342
|
# Artist.db.transaction do
|
1342
1343
|
# a.lock!('FOR NO KEY UPDATE')
|
1343
|
-
# a.update(:
|
1344
|
+
# a.update(name: 'B')
|
1344
1345
|
# end
|
1345
1346
|
def lock!(style=:update)
|
1346
1347
|
_refresh(this.lock_style(style)) unless new?
|
@@ -9,14 +9,16 @@ module Sequel
|
|
9
9
|
# 2. not_null validations on NOT NULL columns (optionally, presence validations)
|
10
10
|
# 3. unique validations on columns or sets of columns with unique indexes
|
11
11
|
# 4. max length validations on string columns
|
12
|
+
# 5. no null byte validations on string columns
|
13
|
+
# 6. minimum and maximum values on columns
|
12
14
|
#
|
13
|
-
# To determine the columns to use for the type/not_null/max_length validations,
|
15
|
+
# To determine the columns to use for the type/not_null/max_length/no_null_byte/max_value/min_value validations,
|
14
16
|
# the plugin looks at the database schema for the model's table. To determine
|
15
17
|
# the unique validations, Sequel looks at the indexes on the table. In order
|
16
18
|
# for this plugin to be fully functional, the underlying database adapter needs
|
17
19
|
# to support both schema and index parsing. Additionally, unique validations are
|
18
20
|
# only added for models that select from a simple table, they are not added for models
|
19
|
-
# that select from a subquery
|
21
|
+
# that select from a subquery.
|
20
22
|
#
|
21
23
|
# This plugin uses the validation_helpers plugin underneath to implement the
|
22
24
|
# validations. It does not allow for any per-column validation message
|
@@ -50,7 +52,7 @@ module Sequel
|
|
50
52
|
#
|
51
53
|
# Model.plugin :auto_validations, unique_opts: {only_if_modified: true}
|
52
54
|
#
|
53
|
-
# This works for unique_opts, max_length_opts, schema_types_opts,
|
55
|
+
# This works for unique_opts, max_length_opts, schema_types_opts, max_value_opts, min_value_opts, no_null_byte_opts,
|
54
56
|
# explicit_not_null_opts, and not_null_opts.
|
55
57
|
#
|
56
58
|
# If you only want auto_validations to add validations to columns that do not already
|
@@ -72,6 +74,19 @@ module Sequel
|
|
72
74
|
SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
|
73
75
|
UNIQUE_OPTIONS = NOT_NULL_OPTIONS
|
74
76
|
NO_NULL_BYTE_OPTIONS = MAX_LENGTH_OPTIONS
|
77
|
+
MAX_VALUE_OPTIONS = {:from=>:values, :allow_nil=>true, :skip_invalid=>true}.freeze
|
78
|
+
MIN_VALUE_OPTIONS = MAX_VALUE_OPTIONS
|
79
|
+
AUTO_VALIDATE_OPTIONS = {
|
80
|
+
:no_null_byte=>NO_NULL_BYTE_OPTIONS,
|
81
|
+
:not_null=>NOT_NULL_OPTIONS,
|
82
|
+
:explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
|
83
|
+
:max_length=>MAX_LENGTH_OPTIONS,
|
84
|
+
:max_value=>MAX_VALUE_OPTIONS,
|
85
|
+
:min_value=>MIN_VALUE_OPTIONS,
|
86
|
+
:schema_types=>SCHEMA_TYPES_OPTIONS,
|
87
|
+
:unique=>UNIQUE_OPTIONS
|
88
|
+
}.freeze
|
89
|
+
|
75
90
|
EMPTY_ARRAY = [].freeze
|
76
91
|
|
77
92
|
def self.apply(model, opts=OPTS)
|
@@ -82,17 +97,11 @@ module Sequel
|
|
82
97
|
@auto_validate_not_null_columns = []
|
83
98
|
@auto_validate_explicit_not_null_columns = []
|
84
99
|
@auto_validate_max_length_columns = []
|
100
|
+
@auto_validate_max_value_columns = []
|
101
|
+
@auto_validate_min_value_columns = []
|
85
102
|
@auto_validate_unique_columns = []
|
86
103
|
@auto_validate_types = true
|
87
|
-
|
88
|
-
@auto_validate_options = {
|
89
|
-
:no_null_byte=>NO_NULL_BYTE_OPTIONS,
|
90
|
-
:not_null=>NOT_NULL_OPTIONS,
|
91
|
-
:explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
|
92
|
-
:max_length=>MAX_LENGTH_OPTIONS,
|
93
|
-
:schema_types=>SCHEMA_TYPES_OPTIONS,
|
94
|
-
:unique=>UNIQUE_OPTIONS
|
95
|
-
}.freeze
|
104
|
+
@auto_validate_options = AUTO_VALIDATE_OPTIONS
|
96
105
|
end
|
97
106
|
end
|
98
107
|
|
@@ -105,7 +114,7 @@ module Sequel
|
|
105
114
|
end
|
106
115
|
|
107
116
|
h = @auto_validate_options.dup
|
108
|
-
[:not_null, :explicit_not_null, :max_length, :no_null_byte, :schema_types, :unique].each do |type|
|
117
|
+
[:not_null, :explicit_not_null, :max_length, :max_value, :min_value, :no_null_byte, :schema_types, :unique].each do |type|
|
109
118
|
if type_opts = opts[:"#{type}_opts"]
|
110
119
|
h[type] = h[type].merge(type_opts).freeze
|
111
120
|
end
|
@@ -135,6 +144,14 @@ module Sequel
|
|
135
144
|
# pairs, with the first entry being the column name and second entry being the maximum length.
|
136
145
|
attr_reader :auto_validate_max_length_columns
|
137
146
|
|
147
|
+
# The columns with automatch max value validations, as an array of
|
148
|
+
# pairs, with the first entry being the column name and second entry being the maximum value.
|
149
|
+
attr_reader :auto_validate_max_value_columns
|
150
|
+
|
151
|
+
# The columns with automatch min value validations, as an array of
|
152
|
+
# pairs, with the first entry being the column name and second entry being the minimum value.
|
153
|
+
attr_reader :auto_validate_min_value_columns
|
154
|
+
|
138
155
|
# The columns or sets of columns with automatic unique validations
|
139
156
|
attr_reader :auto_validate_unique_columns
|
140
157
|
|
@@ -148,6 +165,8 @@ module Sequel
|
|
148
165
|
:@auto_validate_not_null_columns=>:dup,
|
149
166
|
:@auto_validate_explicit_not_null_columns=>:dup,
|
150
167
|
:@auto_validate_max_length_columns=>:dup,
|
168
|
+
:@auto_validate_max_value_columns=>:dup,
|
169
|
+
:@auto_validate_min_value_columns=>:dup,
|
151
170
|
:@auto_validate_unique_columns=>:dup,
|
152
171
|
:@auto_validate_options => :dup)
|
153
172
|
Plugins.after_set_dataset(self, :setup_auto_validations)
|
@@ -168,18 +187,23 @@ module Sequel
|
|
168
187
|
@auto_validate_not_null_columns.freeze
|
169
188
|
@auto_validate_explicit_not_null_columns.freeze
|
170
189
|
@auto_validate_max_length_columns.freeze
|
190
|
+
@auto_validate_max_value_columns.freeze
|
191
|
+
@auto_validate_min_value_columns.freeze
|
171
192
|
@auto_validate_unique_columns.freeze
|
172
193
|
|
173
194
|
super
|
174
195
|
end
|
175
196
|
|
176
197
|
# Skip automatic validations for the given validation type
|
177
|
-
# (:not_null, :types, :unique, :max_length, :
|
198
|
+
# (:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value).
|
178
199
|
# If :all is given as the type, skip all auto validations.
|
200
|
+
#
|
201
|
+
# Skipping types validation automatically skips max_value and min_value validations,
|
202
|
+
# since those validations require valid types.
|
179
203
|
def skip_auto_validations(type)
|
180
204
|
case type
|
181
205
|
when :all
|
182
|
-
[:not_null, :no_null_byte, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
|
206
|
+
[:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value].each{|v| skip_auto_validations(v)}
|
183
207
|
when :not_null
|
184
208
|
auto_validate_not_null_columns.clear
|
185
209
|
auto_validate_explicit_not_null_columns.clear
|
@@ -199,6 +223,8 @@ module Sequel
|
|
199
223
|
explicit_not_null_cols += Array(primary_key)
|
200
224
|
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
201
225
|
@auto_validate_max_length_columns = db_schema.select{|col, sch| sch[:type] == :string && sch[:max_length].is_a?(Integer)}.map{|col, sch| [col, sch[:max_length]]}
|
226
|
+
@auto_validate_max_value_columns = db_schema.select{|col, sch| sch[:max_value]}.map{|col, sch| [col, sch[:max_value]]}
|
227
|
+
@auto_validate_min_value_columns = db_schema.select{|col, sch| sch[:min_value]}.map{|col, sch| [col, sch[:min_value]]}
|
202
228
|
@auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
|
203
229
|
table = dataset.first_source_table
|
204
230
|
@auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
|
@@ -248,6 +274,18 @@ module Sequel
|
|
248
274
|
|
249
275
|
unless skip.include?(:types) || !model.auto_validate_types?
|
250
276
|
validates_schema_types(keys, opts[:schema_types])
|
277
|
+
|
278
|
+
unless skip.include?(:max_value) || ((max_value_columns = model.auto_validate_max_value_columns).empty?)
|
279
|
+
max_value_columns.each do |col, max|
|
280
|
+
validates_max_value(max, col, opts[:max_value])
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
unless skip.include?(:min_value) || ((min_value_columns = model.auto_validate_min_value_columns).empty?)
|
285
|
+
min_value_columns.each do |col, min|
|
286
|
+
validates_min_value(min, col, opts[:min_value])
|
287
|
+
end
|
288
|
+
end
|
251
289
|
end
|
252
290
|
|
253
291
|
unless skip.include?(:unique)
|