sequel 5.60.1 → 5.81.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 +280 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +27 -24
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +53 -17
- data/doc/cheat_sheet.rdoc +3 -3
- data/doc/code_order.rdoc +5 -3
- data/doc/dataset_basics.rdoc +1 -1
- 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 +18 -7
- data/doc/postgresql.rdoc +8 -8
- data/doc/querying.rdoc +7 -2
- 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/release_notes/5.79.0.txt +28 -0
- data/doc/release_notes/5.80.0.txt +40 -0
- data/doc/release_notes/5.81.0.txt +31 -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 +10 -5
- 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 +82 -3
- data/lib/sequel/adapters/shared/oracle.rb +17 -7
- data/lib/sequel/adapters/shared/postgres.rb +469 -160
- data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
- data/lib/sequel/adapters/shared/sqlite.rb +39 -3
- 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 +1 -1
- data/lib/sequel/database/schema_methods.rb +12 -5
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +60 -13
- data/lib/sequel/dataset/dataset_module.rb +1 -1
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +15 -1
- data/lib/sequel/dataset/graph.rb +1 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/query.rb +119 -45
- 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/caller_logging.rb +4 -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 +1 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
- data/lib/sequel/extensions/index_caching.rb +5 -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 +3 -3
- 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/provenance.rb +110 -0
- 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/temporarily_release_connection.rb +178 -0
- data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
- data/lib/sequel/model/associations.rb +38 -11
- data/lib/sequel/model/base.rb +32 -18
- 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 +28 -7
- 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/input_transformer.rb +1 -1
- data/lib/sequel/plugins/list.rb +5 -2
- 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/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +1 -1
- 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 +14 -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 +2 -2
- metadata +79 -6
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module Plugins
|
|
5
|
+
# This plugin implements optimistic locking mechanism on PostgreSQL based
|
|
6
|
+
# on the xmin of the row. The xmin system column is automatically set to
|
|
7
|
+
# the current transaction id whenever the row is inserted or updated:
|
|
8
|
+
#
|
|
9
|
+
# class Person < Sequel::Model
|
|
10
|
+
# plugin :pg_xmin_optimistic_locking
|
|
11
|
+
# end
|
|
12
|
+
# p1 = Person[1]
|
|
13
|
+
# p2 = Person[1]
|
|
14
|
+
# p1.update(name: 'Jim') # works
|
|
15
|
+
# p2.update(name: 'Bob') # raises Sequel::NoExistingObject
|
|
16
|
+
#
|
|
17
|
+
# The advantage of pg_xmin_optimistic_locking plugin compared to the
|
|
18
|
+
# regular optimistic_locking plugin as that it does not require any
|
|
19
|
+
# additional columns setup on the model. This allows it to be loaded
|
|
20
|
+
# in the base model and have all subclasses automatically use
|
|
21
|
+
# optimistic locking. The disadvantage is that testing can be
|
|
22
|
+
# more difficult if you are modifying the underlying row between
|
|
23
|
+
# when a model is retrieved and when it is saved.
|
|
24
|
+
#
|
|
25
|
+
# This plugin may not work with the class_table_inheritance plugin.
|
|
26
|
+
#
|
|
27
|
+
# This plugin relies on the instance_filters plugin.
|
|
28
|
+
module PgXminOptimisticLocking
|
|
29
|
+
WILDCARD = LiteralString.new('*').freeze
|
|
30
|
+
|
|
31
|
+
# Define the xmin column accessor
|
|
32
|
+
def self.apply(model)
|
|
33
|
+
model.instance_exec do
|
|
34
|
+
plugin(:optimistic_locking_base)
|
|
35
|
+
@lock_column = :xmin
|
|
36
|
+
def_column_accessor(:xmin)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Update the dataset to append the xmin column if it is usable
|
|
41
|
+
# and there is a dataset for the model.
|
|
42
|
+
def self.configure(model)
|
|
43
|
+
model.instance_exec do
|
|
44
|
+
set_dataset(@dataset) if @dataset
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module ClassMethods
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Ensure the dataset selects the xmin column if doing so
|
|
52
|
+
def convert_input_dataset(ds)
|
|
53
|
+
append_xmin_column_if_usable(super)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# If the xmin column is not already selected, and selecting it does not
|
|
57
|
+
# raise an error, append it to the selections.
|
|
58
|
+
def append_xmin_column_if_usable(ds)
|
|
59
|
+
select = ds.opts[:select]
|
|
60
|
+
|
|
61
|
+
unless select && select.include?(:xmin)
|
|
62
|
+
xmin_ds = ds.select_append(:xmin)
|
|
63
|
+
begin
|
|
64
|
+
columns = xmin_ds.columns!
|
|
65
|
+
rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
|
|
66
|
+
raise
|
|
67
|
+
rescue Sequel::DatabaseError
|
|
68
|
+
# ignore, could be view, subquery, table returning function, etc.
|
|
69
|
+
else
|
|
70
|
+
ds = xmin_ds if columns.include?(:xmin)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ds
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module InstanceMethods
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Only set the lock column instance filter if there is an xmin value.
|
|
82
|
+
def lock_column_instance_filter
|
|
83
|
+
super if @values[:xmin]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Include xmin value when inserting initial row
|
|
87
|
+
def _insert_dataset
|
|
88
|
+
super.returning(WILDCARD, :xmin)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Remove the xmin from the columns to update.
|
|
92
|
+
# PostgreSQL automatically updates the xmin value, and it cannot be assigned.
|
|
93
|
+
def _save_update_all_columns_hash
|
|
94
|
+
v = super
|
|
95
|
+
v.delete(:xmin)
|
|
96
|
+
v
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Add an RETURNING clause to fetch the updated xmin when updating the row.
|
|
100
|
+
def _update_without_checking(columns)
|
|
101
|
+
ds = _update_dataset
|
|
102
|
+
rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
|
|
103
|
+
values[:xmin] = rows.first[:xmin] unless rows.empty?
|
|
104
|
+
rows.length
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Sequel
|
|
4
4
|
module Plugins
|
|
5
5
|
# The prepared_statements plugin modifies the model to use prepared statements for
|
|
6
|
-
# instance level inserts and updates.
|
|
6
|
+
# instance level inserts and updates. This plugin exists for backwards compatibility
|
|
7
|
+
# and is not recommended for general use.
|
|
7
8
|
#
|
|
8
9
|
# Note that this plugin is unsafe in some circumstances, as it can allow up to
|
|
9
10
|
# 2^N prepared statements to be created for each type of insert and update query, where
|
|
@@ -5,7 +5,8 @@ module Sequel
|
|
|
5
5
|
# The prepared_statements_safe plugin modifies the model to reduce the number of
|
|
6
6
|
# prepared statements that can be created, by setting as many columns as possible
|
|
7
7
|
# before creating, and by changing +save_changes+ to save all columns instead of
|
|
8
|
-
# just the changed ones.
|
|
8
|
+
# just the changed ones. This plugin exists for backwards compatibility
|
|
9
|
+
# and is not recommended for general use.
|
|
9
10
|
#
|
|
10
11
|
# This plugin depends on the +prepared_statements+ plugin.
|
|
11
12
|
#
|
|
@@ -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|
|
|
@@ -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
|
#
|
|
@@ -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
|
|
|
@@ -152,23 +152,23 @@ module Sequel
|
|
|
152
152
|
name = opts[:name]
|
|
153
153
|
eager_reload = dynamic_opts[:eager_reload]
|
|
154
154
|
if (!associations.include?(name) || eager_reload) && opts[:allow_eager] != false && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
|
155
|
-
|
|
156
|
-
objects = if eager_reload
|
|
157
|
-
retrieved_with.reject(&:frozen?)
|
|
158
|
-
else
|
|
159
|
-
retrieved_with.reject{|x| x.frozen? || x.associations.include?(name)}
|
|
160
|
-
end
|
|
161
|
-
retrieved_by.send(:eager_load, objects, name=>dynamic_opts[:eager] || OPTS)
|
|
162
|
-
rescue Sequel::UndefinedAssociation
|
|
163
|
-
# This can happen if class table inheritance is used and the association
|
|
164
|
-
# is only defined in a subclass. This particular instance can use the
|
|
165
|
-
# association, but it can't be eagerly loaded as the parent class doesn't
|
|
166
|
-
# have access to the association, and that's the class doing the eager loading.
|
|
167
|
-
nil
|
|
168
|
-
end
|
|
155
|
+
retrieved_by.send(:eager_load, _filter_tactical_eager_load_objects(:eager_reload=>eager_reload, :name=>name), {name=>dynamic_opts[:eager] || OPTS}, model)
|
|
169
156
|
end
|
|
170
157
|
super
|
|
171
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
|
|
172
172
|
end
|
|
173
173
|
|
|
174
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
|