sequel 5.58.0 → 5.78.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 +288 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +24 -23
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +53 -17
- data/doc/cheat_sheet.rdoc +3 -3
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +15 -0
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +20 -12
- data/doc/postgresql.rdoc +8 -8
- data/doc/querying.rdoc +1 -1
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/release_notes/5.67.0.txt +32 -0
- data/doc/release_notes/5.68.0.txt +61 -0
- data/doc/release_notes/5.69.0.txt +26 -0
- data/doc/release_notes/5.70.0.txt +35 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/release_notes/5.73.0.txt +66 -0
- data/doc/release_notes/5.74.0.txt +45 -0
- data/doc/release_notes/5.75.0.txt +35 -0
- data/doc/release_notes/5.76.0.txt +86 -0
- data/doc/release_notes/5.77.0.txt +63 -0
- data/doc/release_notes/5.78.0.txt +67 -0
- data/doc/schema_modification.rdoc +3 -3
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +14 -14
- data/doc/testing.rdoc +16 -12
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/ibmdb.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +3 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +10 -6
- data/lib/sequel/adapters/mysql.rb +19 -7
- data/lib/sequel/adapters/mysql2.rb +2 -2
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +1 -0
- data/lib/sequel/adapters/postgres.rb +62 -16
- data/lib/sequel/adapters/shared/access.rb +9 -1
- data/lib/sequel/adapters/shared/db2.rb +12 -0
- data/lib/sequel/adapters/shared/mssql.rb +71 -9
- data/lib/sequel/adapters/shared/mysql.rb +80 -1
- data/lib/sequel/adapters/shared/oracle.rb +17 -7
- data/lib/sequel/adapters/shared/postgres.rb +494 -164
- data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
- data/lib/sequel/adapters/shared/sqlite.rb +40 -4
- data/lib/sequel/adapters/sqlite.rb +42 -3
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/threaded.rb +14 -8
- data/lib/sequel/connection_pool/timed_queue.rb +270 -0
- data/lib/sequel/connection_pool.rb +57 -31
- data/lib/sequel/database/connecting.rb +25 -1
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +65 -14
- data/lib/sequel/database/query.rb +72 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/database/schema_methods.rb +13 -3
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +60 -13
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +15 -1
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/query.rb +62 -37
- data/lib/sequel/dataset/sql.rb +58 -36
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/exceptions.rb +5 -0
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +2 -2
- data/lib/sequel/extensions/async_thread_pool.rb +21 -13
- data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +16 -11
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +36 -8
- data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/is_distinct_from.rb +3 -1
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +65 -15
- data/lib/sequel/extensions/named_timezones.rb +22 -6
- data/lib/sequel/extensions/pg_array.rb +33 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/pg_enum.rb +1 -2
- data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
- 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 +52 -0
- data/lib/sequel/extensions/pg_multirange.rb +6 -11
- data/lib/sequel/extensions/pg_range.rb +9 -14
- data/lib/sequel/extensions/pg_row.rb +20 -19
- data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
- data/lib/sequel/extensions/round_timestamps.rb +1 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +32 -9
- data/lib/sequel/extensions/server_block.rb +2 -1
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
- data/lib/sequel/model/associations.rb +50 -11
- data/lib/sequel/model/base.rb +45 -21
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/exceptions.rb +15 -3
- data/lib/sequel/plugins/auto_validations.rb +53 -15
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +27 -6
- data/lib/sequel/plugins/composition.rb +2 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
- data/lib/sequel/plugins/constraint_validations.rb +8 -5
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/list.rb +8 -3
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/nested_attributes.rb +4 -4
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/paged_operations.rb +181 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/prepared_statements.rb +2 -1
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +7 -4
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +5 -5
- data/lib/sequel/plugins/static_cache.rb +38 -0
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/tactical_eager_loading.rb +21 -14
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +29 -2
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/version.rb +1 -1
- metadata +76 -6
@@ -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
|
@@ -71,6 +71,8 @@ module Sequel
|
|
71
71
|
# (default: :t)
|
72
72
|
# :level_alias :: The symbol identifier to use when eagerly loading descendants
|
73
73
|
# up to a given level (default: :x_level_x)
|
74
|
+
# :union_all :: Whether to use UNION ALL or UNION with the recursive
|
75
|
+
# common table expression (default: true)
|
74
76
|
module RcteTree
|
75
77
|
# Create the appropriate parent, children, ancestors, and descendants
|
76
78
|
# associations for the model.
|
@@ -80,6 +82,7 @@ module Sequel
|
|
80
82
|
opts = opts.dup
|
81
83
|
opts[:class] = model
|
82
84
|
opts[:methods_module] = Module.new
|
85
|
+
opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
|
83
86
|
model.send(:include, opts[:methods_module])
|
84
87
|
|
85
88
|
key = opts[:key] ||= :parent_id
|
@@ -142,7 +145,7 @@ module Sequel
|
|
142
145
|
model.from(SQL::AliasedExpression.new(t, table_alias)).
|
143
146
|
with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
|
144
147
|
recursive_ds.select(*c_all),
|
145
|
-
:args=>col_aliases)
|
148
|
+
:args=>col_aliases, union_all: opts[:union_all])
|
146
149
|
end
|
147
150
|
aal = Array(a[:after_load])
|
148
151
|
aal << proc do |m, ancs|
|
@@ -191,7 +194,7 @@ module Sequel
|
|
191
194
|
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
192
195
|
ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
|
193
196
|
with_recursive(t, base_case, recursive_case,
|
194
|
-
:args=>((key_aliases + col_aliases) if col_aliases))
|
197
|
+
:args=>((key_aliases + col_aliases) if col_aliases), union_all: opts[:union_all])
|
195
198
|
ds = r.apply_eager_dataset_changes(ds)
|
196
199
|
ds = ds.select_append(ka) unless ds.opts[:select] == nil
|
197
200
|
model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil)) do |obj|
|
@@ -240,7 +243,7 @@ module Sequel
|
|
240
243
|
model.from(SQL::AliasedExpression.new(t, table_alias)).
|
241
244
|
with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
|
242
245
|
recursive_ds.select(*c_all),
|
243
|
-
:args=>col_aliases)
|
246
|
+
:args=>col_aliases, union_all: opts[:union_all])
|
244
247
|
end
|
245
248
|
dal = Array(d[:after_load])
|
246
249
|
dal << proc do |m, descs|
|
@@ -299,7 +302,7 @@ module Sequel
|
|
299
302
|
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
300
303
|
ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
|
301
304
|
with_recursive(t, base_case, recursive_case,
|
302
|
-
:args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases))
|
305
|
+
:args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases), union_all: opts[:union_all])
|
303
306
|
ds = r.apply_eager_dataset_changes(ds)
|
304
307
|
ds = ds.select_append(ka) unless ds.opts[:select] == nil
|
305
308
|
model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil, :associations=>OPTS)) do |obj|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The require_valid_schema plugin makes Sequel raise or warn if attempting
|
6
|
+
# to set the dataset of a model class to a simple table, where the database
|
7
|
+
# supports schema parsing, but schema parsing does not work for the model's
|
8
|
+
# table.
|
9
|
+
#
|
10
|
+
# The plugin's default behavior requires that all models that select from a
|
11
|
+
# single identifier have a valid table schema, if the database supports
|
12
|
+
# schema parsing. If the schema cannot be determined for such
|
13
|
+
# a model, an error is raised:
|
14
|
+
#
|
15
|
+
# Sequel::Model.plugin :require_valid_schema
|
16
|
+
#
|
17
|
+
# If you load the plugin with an argument of :warn, Sequel will warn instead
|
18
|
+
# of raising for such tables:
|
19
|
+
#
|
20
|
+
# Sequel::Model.plugin :require_valid_schema, :warn
|
21
|
+
#
|
22
|
+
# This can catch bugs where you expect models to have valid schema, but
|
23
|
+
# they do not. This setting only affects future attempts to set datasets
|
24
|
+
# in the current class and subclasses created in the future.
|
25
|
+
#
|
26
|
+
# If you load the plugin with an argument of false, it will not require valid schema.
|
27
|
+
# This can be used in subclasses where you do not want to require valid schema,
|
28
|
+
# but the plugin must be loaded before a dataset with invalid schema is set:
|
29
|
+
#
|
30
|
+
# Sequel::Model.plugin :require_valid_schema
|
31
|
+
# InvalidSchemaAllowed = Class.new(Sequel::Model)
|
32
|
+
# InvalidSchemaAllowed.plugin :require_valid_schema, false
|
33
|
+
# class MyModel < InvalidSchemaAllowed
|
34
|
+
# end
|
35
|
+
module RequireValidSchema
|
36
|
+
# Modify the current model's dataset selection, if the model
|
37
|
+
# has a dataset.
|
38
|
+
def self.configure(model, setting=true)
|
39
|
+
model.instance_variable_set(:@require_valid_schema, setting)
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
Plugins.inherited_instance_variables(self, :@require_valid_schema=>nil)
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# If the schema cannot be determined, the model uses a simple table,
|
48
|
+
# require_valid_schema is set, and the database supports schema parsing, raise or
|
49
|
+
# warn based on the require_valid_schema setting.
|
50
|
+
def get_db_schema_array(reload)
|
51
|
+
schema_array = super
|
52
|
+
|
53
|
+
if !schema_array && simple_table && @require_valid_schema
|
54
|
+
message = "Not able to parse schema for model: #{inspect}, table: #{simple_table}"
|
55
|
+
if @require_valid_schema == :warn
|
56
|
+
warn message
|
57
|
+
else
|
58
|
+
raise Error, message
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
schema_array
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -253,6 +253,14 @@ module Sequel
|
|
253
253
|
|
254
254
|
private
|
255
255
|
|
256
|
+
# Limit tactical eager loading objects to objects that support the same association.
|
257
|
+
def _filter_tactical_eager_load_objects(opts)
|
258
|
+
objects = defined?(super) ? super : retrieved_with.dup
|
259
|
+
name = opts[:name]
|
260
|
+
objects.select!{|x| x.model.association_reflections.include?(name)}
|
261
|
+
objects
|
262
|
+
end
|
263
|
+
|
256
264
|
# Don't allow use of prepared statements.
|
257
265
|
def use_prepared_statements_for?(type)
|
258
266
|
false
|
@@ -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
|
#
|
@@ -63,7 +63,7 @@ module Sequel
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
# :nocov:
|
66
|
-
ruby2_keywords
|
66
|
+
mod.send(:ruby2_keywords, meth) if mod.respond_to?(:ruby2_keywords, true)
|
67
67
|
# :nocov:
|
68
68
|
end
|
69
69
|
|
@@ -97,7 +97,7 @@ module Sequel
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
# :nocov:
|
100
|
-
ruby2_keywords(meth) if respond_to?(:ruby2_keywords,
|
100
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
101
101
|
# :nocov:
|
102
102
|
end
|
103
103
|
|
@@ -129,7 +129,7 @@ module Sequel
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
# :nocov:
|
132
|
-
ruby2_keywords(meth) if respond_to?(:ruby2_keywords,
|
132
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
133
133
|
# :nocov:
|
134
134
|
end
|
135
135
|
|
@@ -159,7 +159,7 @@ module Sequel
|
|
159
159
|
end
|
160
160
|
end
|
161
161
|
# :nocov:
|
162
|
-
ruby2_keywords(meth) if respond_to?(:ruby2_keywords,
|
162
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
163
163
|
# :nocov:
|
164
164
|
end
|
165
165
|
|
@@ -64,6 +64,9 @@ module Sequel
|
|
64
64
|
def self.configure(model, opts=OPTS)
|
65
65
|
model.instance_exec do
|
66
66
|
@static_cache_frozen = opts.fetch(:frozen, true)
|
67
|
+
if @static_cache_frozen && defined?(::Sequel::Plugins::ForbidLazyLoad::ClassMethods) && is_a?(::Sequel::Plugins::ForbidLazyLoad::ClassMethods)
|
68
|
+
extend ForbidLazyLoadClassMethods
|
69
|
+
end
|
67
70
|
load_cache
|
68
71
|
end
|
69
72
|
end
|
@@ -246,6 +249,41 @@ module Sequel
|
|
246
249
|
end
|
247
250
|
end
|
248
251
|
|
252
|
+
module ForbidLazyLoadClassMethods
|
253
|
+
# Do not forbid lazy loading for single object retrieval.
|
254
|
+
def cache_get_pk(pk)
|
255
|
+
primary_key_lookup(pk)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Use static cache to return first arguments.
|
259
|
+
def first(*args)
|
260
|
+
if !defined?(yield) && args.empty?
|
261
|
+
if o = @all.first
|
262
|
+
_static_cache_frozen_copy(o)
|
263
|
+
end
|
264
|
+
else
|
265
|
+
super
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
# Return a frozen copy of the object that does not have lazy loading
|
272
|
+
# forbidden.
|
273
|
+
def _static_cache_frozen_copy(o)
|
274
|
+
o = call(Hash[o.values])
|
275
|
+
o.errors.freeze
|
276
|
+
o.freeze
|
277
|
+
end
|
278
|
+
|
279
|
+
# Do not forbid lazy loading for single object retrieval.
|
280
|
+
def primary_key_lookup(pk)
|
281
|
+
if o = cache[pk]
|
282
|
+
_static_cache_frozen_copy(o)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
249
287
|
module InstanceMethods
|
250
288
|
# Disallowing destroying the object unless the frozen: false option was used.
|
251
289
|
def before_destroy
|
@@ -26,7 +26,11 @@ module Sequel
|
|
26
26
|
module ClassMethods
|
27
27
|
# Dump the in-memory cached rows to the cache file.
|
28
28
|
def dump_static_cache_cache
|
29
|
-
|
29
|
+
static_cache_cache = {}
|
30
|
+
@static_cache_cache.sort.each do |k, v|
|
31
|
+
static_cache_cache[k] = v
|
32
|
+
end
|
33
|
+
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(static_cache_cache))}
|
30
34
|
nil
|
31
35
|
end
|
32
36
|
|
@@ -109,6 +109,13 @@ module Sequel
|
|
109
109
|
# to eagerly set the associated objects, and having separate threads
|
110
110
|
# modify the same model instance is not thread-safe.
|
111
111
|
#
|
112
|
+
# Because this plugin will automatically use eager loading for
|
113
|
+
# performance, it can break code that defines associations that
|
114
|
+
# do not support eager loading, without marking that they do not
|
115
|
+
# support eager loading via the <tt>allow_eager: false</tt> option.
|
116
|
+
# Make sure to set <tt>allow_eager: false</tt> on any association
|
117
|
+
# used with this plugin if the association doesn't support eager loading.
|
118
|
+
#
|
112
119
|
# Usage:
|
113
120
|
#
|
114
121
|
# # Make all model subclass instances use tactical eager loading (called before loading subclasses)
|
@@ -145,23 +152,23 @@ module Sequel
|
|
145
152
|
name = opts[:name]
|
146
153
|
eager_reload = dynamic_opts[:eager_reload]
|
147
154
|
if (!associations.include?(name) || eager_reload) && opts[:allow_eager] != false && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
148
|
-
|
149
|
-
objects = if eager_reload
|
150
|
-
retrieved_with.reject(&:frozen?)
|
151
|
-
else
|
152
|
-
retrieved_with.reject{|x| x.frozen? || x.associations.include?(name)}
|
153
|
-
end
|
154
|
-
retrieved_by.send(:eager_load, objects, name=>dynamic_opts[:eager] || OPTS)
|
155
|
-
rescue Sequel::UndefinedAssociation
|
156
|
-
# This can happen if class table inheritance is used and the association
|
157
|
-
# is only defined in a subclass. This particular instance can use the
|
158
|
-
# association, but it can't be eagerly loaded as the parent class doesn't
|
159
|
-
# have access to the association, and that's the class doing the eager loading.
|
160
|
-
nil
|
161
|
-
end
|
155
|
+
retrieved_by.send(:eager_load, _filter_tactical_eager_load_objects(:eager_reload=>eager_reload, :name=>name), {name=>dynamic_opts[:eager] || OPTS}, model)
|
162
156
|
end
|
163
157
|
super
|
164
158
|
end
|
159
|
+
|
160
|
+
# Filter the objects used when tactical eager loading.
|
161
|
+
# By default, this removes frozen objects and objects that alreayd have the association loaded
|
162
|
+
def _filter_tactical_eager_load_objects(opts)
|
163
|
+
objects = defined?(super) ? super : retrieved_with.dup
|
164
|
+
if opts[:eager_reload]
|
165
|
+
objects.reject!(&:frozen?)
|
166
|
+
else
|
167
|
+
name = opts[:name]
|
168
|
+
objects.reject!{|x| x.frozen? || x.associations.include?(name)}
|
169
|
+
end
|
170
|
+
objects
|
171
|
+
end
|
165
172
|
end
|
166
173
|
|
167
174
|
module DatasetMethods
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
|
-
# The
|
5
|
+
# The validate_associated plugin allows you to validate associated
|
6
6
|
# objects. It also offers the ability to delay the validation of
|
7
7
|
# associated objects until the current object is validated.
|
8
8
|
# If the associated object is invalid, validation error messages
|
@@ -54,22 +54,32 @@ module Sequel
|
|
54
54
|
return if reflection[:validate] == false
|
55
55
|
association = reflection[:name]
|
56
56
|
if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !(pk_val = obj.values[key])
|
57
|
-
# There could be a presence validation on the foreign key in the associated model,
|
58
|
-
# which will fail if we validate before saving the current object. If there is
|
59
|
-
# no value for the foreign key, set it to the current primary key value, or a dummy
|
60
|
-
# value of 0 if we haven't saved the current object.
|
61
57
|
p_key = pk unless pk.is_a?(Array)
|
62
|
-
|
63
|
-
|
58
|
+
if p_key
|
59
|
+
obj.values[key] = p_key
|
60
|
+
else
|
61
|
+
ignore_key_errors = true
|
62
|
+
end
|
64
63
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
64
|
+
|
65
|
+
unless obj.valid?
|
66
|
+
if ignore_key_errors
|
67
|
+
# Ignore errors on the key column in the associated object. This column
|
68
|
+
# will be set when saving to a presumably valid value using a column
|
69
|
+
# in the current object (which may not be available until after the current
|
70
|
+
# object is saved).
|
71
|
+
obj.errors.delete(key)
|
72
|
+
obj.errors.delete_if{|k,| Array === k && k.include?(key)}
|
73
|
+
end
|
74
|
+
|
75
|
+
obj.errors.full_messages.each do |m|
|
76
|
+
errors.add(association, m)
|
77
|
+
end
|
69
78
|
end
|
79
|
+
|
80
|
+
nil
|
70
81
|
end
|
71
82
|
end
|
72
83
|
end
|
73
84
|
end
|
74
85
|
end
|
75
|
-
|
@@ -75,6 +75,10 @@ module Sequel
|
|
75
75
|
# "#{Array(attribute).join(I18n.t('errors.joiner'))} #{error_msg}"
|
76
76
|
# end
|
77
77
|
# end
|
78
|
+
#
|
79
|
+
# It is recommended that users of this plugin that use validates_schema_types also use
|
80
|
+
# the validation_helpers_generic_type_messages plugin for more useful type validation
|
81
|
+
# failure messages.
|
78
82
|
module ValidationHelpers
|
79
83
|
DEFAULT_OPTIONS = {
|
80
84
|
:exact_length=>{:message=>lambda{|exact| "is not #{exact} characters"}},
|
@@ -83,7 +87,9 @@ module Sequel
|
|
83
87
|
:integer=>{:message=>lambda{"is not a number"}},
|
84
88
|
:length_range=>{:message=>lambda{|range| "is too short or too long"}},
|
85
89
|
:max_length=>{:message=>lambda{|max| "is longer than #{max} characters"}, :nil_message=>lambda{"is not present"}},
|
90
|
+
:max_value=>{:message=>lambda{|max| "is greater than maximum allowed value"}},
|
86
91
|
:min_length=>{:message=>lambda{|min| "is shorter than #{min} characters"}},
|
92
|
+
:min_value=>{:message=>lambda{|min| "is less than minimum allowed value"}},
|
87
93
|
:not_null=>{:message=>lambda{"is not present"}},
|
88
94
|
:no_null_byte=>{:message=>lambda{"contains a null byte"}},
|
89
95
|
:numeric=>{:message=>lambda{"is not a number"}},
|
@@ -141,11 +147,29 @@ module Sequel
|
|
141
147
|
end
|
142
148
|
end
|
143
149
|
|
150
|
+
# Check that the attribute values are not greater that the given maximum value.
|
151
|
+
# Does not perform validation if attribute value is nil.
|
152
|
+
# You should only call this if you have checked the attribute value has the expected type.
|
153
|
+
def validates_max_value(max, atts, opts=OPTS)
|
154
|
+
validatable_attributes_for_type(:max_value, atts, opts) do |a,v,m|
|
155
|
+
validation_error_message(m, max) if !v.nil? && v > max
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
144
159
|
# Check that the attribute values are not shorter than the given min length.
|
145
160
|
def validates_min_length(min, atts, opts=OPTS)
|
146
161
|
validatable_attributes_for_type(:min_length, atts, opts){|a,v,m| validation_error_message(m, min) if v.nil? || v.length < min}
|
147
162
|
end
|
148
163
|
|
164
|
+
# Check that the attribute values are not less that the given minimum value.
|
165
|
+
# Does not perform validation if attribute value is nil.
|
166
|
+
# You should only call this if you have checked the attribute value has the expected type.
|
167
|
+
def validates_min_value(min, atts, opts=OPTS)
|
168
|
+
validatable_attributes_for_type(:min_value, atts, opts) do |a,v,m|
|
169
|
+
validation_error_message(m, min) if !v.nil? && v < min
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
149
173
|
# Check attribute value(s) are not NULL/nil.
|
150
174
|
def validates_not_null(atts, opts=OPTS)
|
151
175
|
validatable_attributes_for_type(:not_null, atts, opts){|a,v,m| validation_error_message(m) if v.nil?}
|
@@ -191,7 +215,7 @@ module Sequel
|
|
191
215
|
klass = klass.to_s.constantize if klass.is_a?(String) || klass.is_a?(Symbol)
|
192
216
|
validatable_attributes_for_type(:type, atts, opts) do |a,v,m|
|
193
217
|
if klass.is_a?(Array) ? !klass.any?{|kls| v.is_a?(kls)} : !v.is_a?(klass)
|
194
|
-
|
218
|
+
validates_type_error_message(m, klass)
|
195
219
|
end
|
196
220
|
end
|
197
221
|
end
|
@@ -271,7 +295,7 @@ module Sequel
|
|
271
295
|
h = ds.joined_dataset? ? qualified_pk_hash : pk_hash
|
272
296
|
ds = ds.exclude(h)
|
273
297
|
end
|
274
|
-
errors.add(a, message) unless ds.
|
298
|
+
errors.add(a, message) unless ds.empty?
|
275
299
|
end
|
276
300
|
end
|
277
301
|
|
@@ -318,6 +342,9 @@ module Sequel
|
|
318
342
|
def validation_error_message(message, *args)
|
319
343
|
message.is_a?(Proc) ? message.call(*args) : message
|
320
344
|
end
|
345
|
+
|
346
|
+
# The validation error message for type validations, for the given class.
|
347
|
+
alias validates_type_error_message validation_error_message
|
321
348
|
end
|
322
349
|
end
|
323
350
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require_relative 'validation_helpers'
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
module Plugins
|
7
|
+
# The validation_helpers_generic_type_messages plugin overrides the default
|
8
|
+
# type validation failure messages in the validation_helpers plugin to be
|
9
|
+
# more generic and understandable by the average user, instead of always
|
10
|
+
# be based on the names of the allowed classes for the type. For example:
|
11
|
+
#
|
12
|
+
# # :blob type
|
13
|
+
# # validation_helpers default: "value is not a valid sequel::sql::blob"
|
14
|
+
# # with this plugin: "value is not a blob"
|
15
|
+
#
|
16
|
+
# # :boolean type
|
17
|
+
# # validation_helpers default: "value is not a valid trueclass or falseclass"
|
18
|
+
# # with this plugin: "value is not true or false"
|
19
|
+
#
|
20
|
+
# # :datetime type
|
21
|
+
# # validation_helpers default: "value is not a valid time or datetime"
|
22
|
+
# # with this plugin: "value is not a valid timestamp"
|
23
|
+
#
|
24
|
+
# # custom/database-specific types
|
25
|
+
# # validation_helpers default: "value is not a valid sequel::class_name"
|
26
|
+
# # with this plugin: "value is not the expected type"
|
27
|
+
#
|
28
|
+
# It is expected that this plugin will become the default behavior of
|
29
|
+
# validation_helpers in Sequel 6.
|
30
|
+
#
|
31
|
+
# To enable this the use of generic type messages for all models, load this
|
32
|
+
# plugin into Sequel::Model.
|
33
|
+
#
|
34
|
+
# Sequel::Model.plugin :validation_helpers_generic_type_messages
|
35
|
+
module ValidationHelpersGenericTypeMessages
|
36
|
+
OVERRIDE_PROC = ValidationHelpers::DEFAULT_OPTIONS[:type][:message]
|
37
|
+
private_constant :OVERRIDE_PROC
|
38
|
+
|
39
|
+
TYPE_ERROR_STRINGS = {
|
40
|
+
String => 'is not a string'.freeze,
|
41
|
+
Integer => 'is not an integer'.freeze,
|
42
|
+
Date => 'is not a valid date'.freeze,
|
43
|
+
[Time, DateTime].freeze => 'is not a valid timestamp'.freeze,
|
44
|
+
Sequel::SQLTime => 'is not a valid time'.freeze,
|
45
|
+
[TrueClass, FalseClass].freeze => 'is not true or false'.freeze,
|
46
|
+
Float => 'is not a number'.freeze,
|
47
|
+
BigDecimal => 'is not a number'.freeze,
|
48
|
+
Sequel::SQL::Blob => 'is not a blob'.freeze,
|
49
|
+
}
|
50
|
+
TYPE_ERROR_STRINGS.default = "is not the expected type".freeze
|
51
|
+
TYPE_ERROR_STRINGS.freeze
|
52
|
+
private_constant :TYPE_ERROR_STRINGS
|
53
|
+
|
54
|
+
def self.apply(mod)
|
55
|
+
mod.plugin :validation_helpers
|
56
|
+
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
private
|
60
|
+
|
61
|
+
# Use a generic error message for type validations.
|
62
|
+
def validates_type_error_message(m, klass)
|
63
|
+
# SEQUEL6: Make this the default behavior in validation_helpers
|
64
|
+
if OVERRIDE_PROC.equal?(m)
|
65
|
+
TYPE_ERROR_STRINGS[klass]
|
66
|
+
else
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/sequel/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
|
7
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
8
8
|
# release, generally around once a month.
|
9
|
-
MINOR =
|
9
|
+
MINOR = 78
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|