sequel 5.60.1 → 5.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|