sequel 5.61.0 → 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 +32 -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/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 +8 -2
- 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/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 +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 +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 +1 -1
- metadata +10 -5
@@ -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
|
@@ -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
|
@@ -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)
|
@@ -104,7 +104,7 @@ module Sequel
|
|
104
104
|
# should reference the subquery alias (and qualified identifiers should not be needed
|
105
105
|
# unless joining to another table):
|
106
106
|
#
|
107
|
-
# a = Executive.where(:
|
107
|
+
# a = Executive.where(id: 1).first # works
|
108
108
|
# a = Executive.where{{employees[:id]=>1}}.first # works
|
109
109
|
# a = Executive.where{{executives[:id]=>1}}.first # doesn't work
|
110
110
|
#
|
@@ -115,7 +115,7 @@ module Sequel
|
|
115
115
|
#
|
116
116
|
# pks = Executive.where{num_staff < 10}.select_map(:id)
|
117
117
|
# Executive.cti_tables.reverse_each do |table|
|
118
|
-
# DB.from(table).where(:
|
118
|
+
# DB.from(table).where(id: pks).delete
|
119
119
|
# end
|
120
120
|
#
|
121
121
|
# = Usage
|
@@ -39,8 +39,8 @@ module Sequel
|
|
39
39
|
# also be implemented as:
|
40
40
|
#
|
41
41
|
# Album.composition :date,
|
42
|
-
# :
|
43
|
-
# :
|
42
|
+
# composer: proc{Date.new(year, month, day) if year || month || day},
|
43
|
+
# decomposer: (proc do
|
44
44
|
# if d = compositions[:date]
|
45
45
|
# self.year = d.year
|
46
46
|
# self.month = d.month
|
@@ -50,10 +50,10 @@ module Sequel
|
|
50
50
|
# support concurrent eager loading. Taking this example from the
|
51
51
|
# {Advanced Associations guide}[rdoc-ref:doc/advanced_associations.rdoc]
|
52
52
|
#
|
53
|
-
# Album.many_to_one :artist, :
|
53
|
+
# Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
|
54
54
|
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
55
55
|
# id_map = eo_opts[:id_map]
|
56
|
-
# Artist.where(:
|
56
|
+
# Artist.where(id: id_map.keys).all do |artist|
|
57
57
|
# if albums = id_map[artist.id]
|
58
58
|
# albums.each do |album|
|
59
59
|
# album.associations[:artist] = artist
|
@@ -74,12 +74,12 @@ module Sequel
|
|
74
74
|
# the code that loads the objects, since that will prevent concurrent loading.
|
75
75
|
# So after the changes, the custom eager loader would look like this:
|
76
76
|
#
|
77
|
-
# Album.many_to_one :artist, :
|
77
|
+
# Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
|
78
78
|
# Sequel.synchronize_with(eo[:mutex]) do
|
79
79
|
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
80
80
|
# end
|
81
81
|
# id_map = eo_opts[:id_map]
|
82
|
-
# rows = Artist.where(:
|
82
|
+
# rows = Artist.where(id: id_map.keys).all
|
83
83
|
# Sequel.synchronize_with(eo[:mutex]) do
|
84
84
|
# rows.each do |artist|
|
85
85
|
# if albums = id_map[artist.id]
|
data/lib/sequel/plugins/dirty.rb
CHANGED
@@ -97,7 +97,9 @@ module Sequel
|
|
97
97
|
# Artist.first_by_name(nil)
|
98
98
|
# # WHERE (name IS NULL)
|
99
99
|
#
|
100
|
-
# See Dataset::PlaceholderLiteralizer for additional caveats.
|
100
|
+
# See Dataset::PlaceholderLiteralizer for additional caveats. Note that if the model's
|
101
|
+
# dataset does not support placeholder literalizers, you will not be able to use this
|
102
|
+
# method.
|
101
103
|
def finder(meth=OPTS, opts=OPTS, &block)
|
102
104
|
if block
|
103
105
|
raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash)
|
@@ -33,7 +33,7 @@ module Sequel
|
|
33
33
|
# objects. You just need to make sure that the primary key field is filled in for the
|
34
34
|
# associated object:
|
35
35
|
#
|
36
|
-
# a.update(:
|
36
|
+
# a.update(albums_attributes: [{id: 1, name: 'T'}])
|
37
37
|
#
|
38
38
|
# Since the primary key field is filled in, the plugin will update the album with id 1 instead
|
39
39
|
# of creating a new album.
|
@@ -42,14 +42,14 @@ module Sequel
|
|
42
42
|
# entry to the hash, and also pass the :destroy option when calling +nested_attributes+:
|
43
43
|
#
|
44
44
|
# Artist.nested_attributes :albums, destroy: true
|
45
|
-
# a.update(:
|
45
|
+
# a.update(albums_attributes: [{id: 1, _delete: true}])
|
46
46
|
#
|
47
47
|
# This will delete the related associated object from the database. If you want to leave the
|
48
48
|
# associated object in the database, but just remove it from the association, add a _remove
|
49
49
|
# entry in the hash, and also pass the :remove option when calling +nested_attributes+:
|
50
50
|
#
|
51
51
|
# Artist.nested_attributes :albums, remove: true
|
52
|
-
# a.update(:
|
52
|
+
# a.update(albums_attributes: [{id: 1, _remove: true}])
|
53
53
|
#
|
54
54
|
# The above example was for a one_to_many association, but the plugin also works similarly
|
55
55
|
# for other association types. For one_to_one and many_to_one associations, you need to
|
@@ -84,7 +84,7 @@ module Sequel
|
|
84
84
|
# nested attributes options for that association. This is useful for per-call filtering
|
85
85
|
# of the allowed fields:
|
86
86
|
#
|
87
|
-
# a.set_nested_attributes(:albums, params['artist'], :
|
87
|
+
# a.set_nested_attributes(:albums, params['artist'], fields: %w'name')
|
88
88
|
module NestedAttributes
|
89
89
|
# Depend on the validate_associated plugin.
|
90
90
|
def self.apply(model)
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The primary_key_lookup_check_values plugin typecasts given primary key
|
6
|
+
# values before performing a lookup by primary key. If the given primary
|
7
|
+
# key value cannot be typecasted correctly, the lookup returns nil
|
8
|
+
# without issuing a query. If the schema for the primary key column
|
9
|
+
# includes minimum and maximum values, this also checks the given value
|
10
|
+
# is not outside the range. If the given value is outside the allowed
|
11
|
+
# range, the lookup returns nil without issuing a query.
|
12
|
+
#
|
13
|
+
# This affects the following Model methods:
|
14
|
+
#
|
15
|
+
# * Model.[] (when called with non-Hash)
|
16
|
+
# * Model.with_pk
|
17
|
+
# * Model.with_pk!
|
18
|
+
#
|
19
|
+
# It also affects the following Model dataset methods:
|
20
|
+
#
|
21
|
+
# * Dataset#[] (when called with Integer)
|
22
|
+
# * Dataset#with_pk
|
23
|
+
# * dataset#with_pk!
|
24
|
+
#
|
25
|
+
# Note that this can break working code. The above methods accept
|
26
|
+
# any filter condition by default, not just primary key values. The
|
27
|
+
# plugin will handle Symbol, Sequel::SQL::Expression, and
|
28
|
+
# Sequel::LiteralString objects, but code such as the following will break:
|
29
|
+
#
|
30
|
+
# # Return first Album where primary key is one of the given values
|
31
|
+
# Album.dataset.with_pk([1, 2, 3])
|
32
|
+
#
|
33
|
+
# Usage:
|
34
|
+
#
|
35
|
+
# # Make all model subclasses support checking primary key values before
|
36
|
+
# # lookup # (called before loading subclasses)
|
37
|
+
# Sequel::Model.plugin :primary_key_lookup_check_values
|
38
|
+
#
|
39
|
+
# # Make the Album class support checking primary key values before lookup
|
40
|
+
# Album.plugin :primary_key_lookup_check_values
|
41
|
+
module PrimaryKeyLookupCheckValues
|
42
|
+
def self.configure(model)
|
43
|
+
model.instance_exec do
|
44
|
+
setup_primary_key_lookup_check_values if @dataset
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
Plugins.after_set_dataset(self, :setup_primary_key_lookup_check_values)
|
50
|
+
|
51
|
+
Plugins.inherited_instance_variables(self,
|
52
|
+
:@primary_key_type=>nil,
|
53
|
+
:@primary_key_value_range=>nil)
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Check the given primary key value. Typecast it to the appropriate
|
58
|
+
# database type if the database type is known. If it cannot be
|
59
|
+
# typecasted, or the typecasted value is outside the range of column
|
60
|
+
# values, return nil.
|
61
|
+
def _check_pk_lookup_value(pk)
|
62
|
+
return if nil == pk
|
63
|
+
case pk
|
64
|
+
when SQL::Expression, LiteralString, Symbol
|
65
|
+
return pk
|
66
|
+
end
|
67
|
+
return pk unless pk_type = @primary_key_type
|
68
|
+
|
69
|
+
if pk_type.is_a?(Array)
|
70
|
+
return unless pk.is_a?(Array)
|
71
|
+
return unless pk.size == pk_type.size
|
72
|
+
return if pk.any?(&:nil?)
|
73
|
+
|
74
|
+
pk_value_range = @primary_key_value_range
|
75
|
+
i = 0
|
76
|
+
pk.map do |v|
|
77
|
+
if type = pk_type[i]
|
78
|
+
v = _typecast_pk_lookup_value(v, type)
|
79
|
+
return if nil == v
|
80
|
+
if pk_value_range
|
81
|
+
min, max = pk_value_range[i]
|
82
|
+
return if min && v < min
|
83
|
+
return if max && v > max
|
84
|
+
end
|
85
|
+
end
|
86
|
+
i += 1
|
87
|
+
v
|
88
|
+
end
|
89
|
+
elsif pk.is_a?(Array)
|
90
|
+
return
|
91
|
+
elsif nil != (pk = _typecast_pk_lookup_value(pk, pk_type))
|
92
|
+
min, max = @primary_key_value_range
|
93
|
+
return if min && pk < min
|
94
|
+
return if max && pk > max
|
95
|
+
pk
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Typecast the value to the appropriate type,
|
100
|
+
# returning nil if it cannot be typecasted.
|
101
|
+
def _typecast_pk_lookup_value(value, type)
|
102
|
+
db.typecast_value(type, value)
|
103
|
+
rescue InvalidValue
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Skip the primary key lookup if the typecasted and checked
|
108
|
+
# primary key value is nil.
|
109
|
+
def primary_key_lookup(pk)
|
110
|
+
unless nil == (pk = _check_pk_lookup_value(pk))
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Setup the primary key type and value range used for checking
|
116
|
+
# primary key values during lookup.
|
117
|
+
def setup_primary_key_lookup_check_values
|
118
|
+
if primary_key.is_a?(Array)
|
119
|
+
types = []
|
120
|
+
value_ranges = []
|
121
|
+
primary_key.each do |pk|
|
122
|
+
type, min, max = _type_min_max_values_for_column(pk)
|
123
|
+
types << type
|
124
|
+
value_ranges << ([min, max].freeze if min || max)
|
125
|
+
end
|
126
|
+
@primary_key_type = (types.freeze if types.any?)
|
127
|
+
@primary_key_value_range = (value_ranges.freeze if @primary_key_type && value_ranges.any?)
|
128
|
+
else
|
129
|
+
@primary_key_type, min, max = _type_min_max_values_for_column(primary_key)
|
130
|
+
@primary_key_value_range = ([min, max].freeze if @primary_key_type && (min || max))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return the type, min_value, and max_value schema entries
|
135
|
+
# for the column, if they exist.
|
136
|
+
def _type_min_max_values_for_column(column)
|
137
|
+
if schema = db_schema[column]
|
138
|
+
schema.values_at(:type, :min_value, :max_value)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
module DatasetMethods
|
144
|
+
# Skip the primary key lookup if the typecasted and checked
|
145
|
+
# primary key value is nil.
|
146
|
+
def with_pk(pk)
|
147
|
+
unless nil == (pk = model.send(:_check_pk_lookup_value, pk))
|
148
|
+
super
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -12,7 +12,7 @@ module Sequel
|
|
12
12
|
# # SELECT * FROM albums WHERE (id = 1) LIMIT 1
|
13
13
|
# # -- model:Album,method_type:class,method:[]
|
14
14
|
#
|
15
|
-
# album.update(:
|
15
|
+
# album.update(name: 'A')
|
16
16
|
# # UPDATE albums SET name = 'baz' WHERE (id = 1)
|
17
17
|
# # -- model:Album,method_type:instance,method:update
|
18
18
|
#
|