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
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
|
@@ -680,10 +681,11 @@ module Sequel
|
|
680
681
|
|
681
682
|
private
|
682
683
|
|
683
|
-
# Yield to the passed block and if do_raise is false, swallow
|
684
|
+
# Yield to the passed block and if do_raise is false, swallow Sequel::Errors other than DatabaseConnectionError
|
685
|
+
# and DatabaseDisconnectError.
|
684
686
|
def check_non_connection_error(do_raise=require_valid_table)
|
685
687
|
db.transaction(:savepoint=>:only){yield}
|
686
|
-
rescue Sequel::DatabaseConnectionError
|
688
|
+
rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
|
687
689
|
raise
|
688
690
|
rescue Sequel::Error
|
689
691
|
raise if do_raise
|
@@ -693,9 +695,12 @@ module Sequel
|
|
693
695
|
# this model's dataset.
|
694
696
|
def convert_input_dataset(ds)
|
695
697
|
case ds
|
696
|
-
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
698
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
697
699
|
self.simple_table = db.literal(ds).freeze
|
698
700
|
ds = db.from(ds)
|
701
|
+
when SQL::AliasedExpression, LiteralString
|
702
|
+
self.simple_table = nil
|
703
|
+
ds = db.from(ds)
|
699
704
|
when Dataset
|
700
705
|
ds = ds.from_self(:alias=>ds.first_source) if ds.joined_dataset?
|
701
706
|
|
@@ -784,7 +789,7 @@ module Sequel
|
|
784
789
|
schema_hash = {}
|
785
790
|
ds_opts = dataset.opts
|
786
791
|
get_columns = proc{check_non_connection_error{columns} || []}
|
787
|
-
schema_array =
|
792
|
+
schema_array = get_db_schema_array(reload) if db.supports_schema_parsing?
|
788
793
|
if schema_array
|
789
794
|
schema_array.each{|k,v| schema_hash[k] = v}
|
790
795
|
|
@@ -821,6 +826,12 @@ module Sequel
|
|
821
826
|
schema_hash
|
822
827
|
end
|
823
828
|
|
829
|
+
# Get the array of schema information for the dataset. Returns nil if
|
830
|
+
# the schema information cannot be determined.
|
831
|
+
def get_db_schema_array(reload)
|
832
|
+
check_non_connection_error(false){db.schema(dataset, :reload=>reload)}
|
833
|
+
end
|
834
|
+
|
824
835
|
# Uncached version of setter_methods, to be overridden by plugins
|
825
836
|
# that want to modify the methods used.
|
826
837
|
def get_setter_methods
|
@@ -1131,7 +1142,7 @@ module Sequel
|
|
1131
1142
|
#
|
1132
1143
|
# Artist[1] === Artist[1] # => true
|
1133
1144
|
# Artist.new === Artist.new # => false
|
1134
|
-
# Artist[1].set(:
|
1145
|
+
# Artist[1].set(name: 'Bob') === Artist[1] # => true
|
1135
1146
|
def ===(obj)
|
1136
1147
|
case pkv = pk
|
1137
1148
|
when nil
|
@@ -1150,7 +1161,7 @@ module Sequel
|
|
1150
1161
|
#
|
1151
1162
|
# Artist[1].pk_equal?(Artist[1]) # => true
|
1152
1163
|
# Artist.new.pk_equal?(Artist.new) # => false
|
1153
|
-
# Artist[1].set(:
|
1164
|
+
# Artist[1].set(name: 'Bob').pk_equal?(Artist[1]) # => true
|
1154
1165
|
alias pk_equal? ===
|
1155
1166
|
|
1156
1167
|
# class is defined in Object, but it is also a keyword,
|
@@ -1222,7 +1233,7 @@ module Sequel
|
|
1222
1233
|
#
|
1223
1234
|
# Artist[1] == Artist[1] # => true
|
1224
1235
|
# Artist.new == Artist.new # => true
|
1225
|
-
# Artist[1].set(:
|
1236
|
+
# Artist[1].set(name: 'Bob') == Artist[1] # => false
|
1226
1237
|
def eql?(obj)
|
1227
1238
|
(obj.class == model) && (obj.values == @values)
|
1228
1239
|
end
|
@@ -1233,18 +1244,21 @@ module Sequel
|
|
1233
1244
|
@errors ||= errors_class.new
|
1234
1245
|
end
|
1235
1246
|
|
1247
|
+
EXISTS_SELECT_ = SQL::AliasedExpression.new(1, :one)
|
1248
|
+
private_constant :EXISTS_SELECT_
|
1249
|
+
|
1236
1250
|
# Returns true when current instance exists, false otherwise.
|
1237
1251
|
# Generally an object that isn't new will exist unless it has
|
1238
1252
|
# been deleted. Uses a database query to check for existence,
|
1239
1253
|
# unless the model object is new, in which case this is always
|
1240
1254
|
# false.
|
1241
1255
|
#
|
1242
|
-
# Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
|
1256
|
+
# Artist[1].exists? # SELECT 1 AS one FROM artists WHERE (id = 1)
|
1243
1257
|
# # => true
|
1244
1258
|
# Artist.new.exists?
|
1245
1259
|
# # => false
|
1246
1260
|
def exists?
|
1247
|
-
new? ? false : !this.get(
|
1261
|
+
new? ? false : !this.get(EXISTS_SELECT_).nil?
|
1248
1262
|
end
|
1249
1263
|
|
1250
1264
|
# Ignore the model's setter method cache when this instances extends a module, as the
|
@@ -1324,13 +1338,13 @@ module Sequel
|
|
1324
1338
|
# a = Artist[1]
|
1325
1339
|
# Artist.db.transaction do
|
1326
1340
|
# a.lock!
|
1327
|
-
# a.update(:
|
1341
|
+
# a.update(name: 'A')
|
1328
1342
|
# end
|
1329
1343
|
#
|
1330
1344
|
# a = Artist[2]
|
1331
1345
|
# Artist.db.transaction do
|
1332
1346
|
# a.lock!('FOR NO KEY UPDATE')
|
1333
|
-
# a.update(:
|
1347
|
+
# a.update(name: 'B')
|
1334
1348
|
# end
|
1335
1349
|
def lock!(style=:update)
|
1336
1350
|
_refresh(this.lock_style(style)) unless new?
|
@@ -1934,8 +1948,10 @@ module Sequel
|
|
1934
1948
|
end
|
1935
1949
|
|
1936
1950
|
# If transactions should be used, wrap the yield in a transaction block.
|
1937
|
-
def checked_transaction(opts=OPTS)
|
1938
|
-
|
1951
|
+
def checked_transaction(opts=OPTS, &block)
|
1952
|
+
h = {:server=>this_server}.merge!(opts)
|
1953
|
+
h[:skip_transaction] = true unless use_transaction?(opts)
|
1954
|
+
db.transaction(h, &block)
|
1939
1955
|
end
|
1940
1956
|
|
1941
1957
|
# Change the value of the column to given value, recording the change.
|
@@ -2020,19 +2036,20 @@ module Sequel
|
|
2020
2036
|
meths = setter_methods(type)
|
2021
2037
|
strict = strict_param_setting
|
2022
2038
|
hash.each do |k,v|
|
2039
|
+
k = k.to_s
|
2023
2040
|
m = "#{k}="
|
2024
2041
|
if meths.include?(m)
|
2025
2042
|
set_column_value(m, v)
|
2026
2043
|
elsif strict
|
2027
2044
|
# Avoid using respond_to? or creating symbols from user input
|
2028
2045
|
if public_methods.map(&:to_s).include?(m)
|
2029
|
-
if Array(model.primary_key).map(&:to_s).member?(k
|
2030
|
-
raise MassAssignmentRestriction
|
2046
|
+
if Array(model.primary_key).map(&:to_s).member?(k) && model.restrict_primary_key?
|
2047
|
+
raise MassAssignmentRestriction.create("#{k} is a restricted primary key", self, k)
|
2031
2048
|
else
|
2032
|
-
raise MassAssignmentRestriction
|
2049
|
+
raise MassAssignmentRestriction.create("#{k} is a restricted column", self, k)
|
2033
2050
|
end
|
2034
2051
|
else
|
2035
|
-
raise MassAssignmentRestriction
|
2052
|
+
raise MassAssignmentRestriction.create("method #{m} doesn't exist", self, k)
|
2036
2053
|
end
|
2037
2054
|
end
|
2038
2055
|
end
|
@@ -2136,8 +2153,9 @@ module Sequel
|
|
2136
2153
|
# # DELETE FROM artists WHERE (id = 2)
|
2137
2154
|
# # ...
|
2138
2155
|
def destroy
|
2139
|
-
|
2140
|
-
|
2156
|
+
@db.transaction(:server=>opts[:server], :skip_transaction=>model.use_transactions == false) do
|
2157
|
+
all(&:destroy).length
|
2158
|
+
end
|
2141
2159
|
end
|
2142
2160
|
|
2143
2161
|
# If there is no order already defined on this dataset, order it by
|
@@ -2217,11 +2235,17 @@ module Sequel
|
|
2217
2235
|
|
2218
2236
|
private
|
2219
2237
|
|
2238
|
+
# Return the dataset ordered by the model's primary key. This should not
|
2239
|
+
# be used if the model does not have a primary key.
|
2240
|
+
def _force_primary_key_order
|
2241
|
+
cached_dataset(:_pk_order_ds){order(*model.primary_key)}
|
2242
|
+
end
|
2243
|
+
|
2220
2244
|
# If the dataset is not already ordered, and the model has a primary key,
|
2221
2245
|
# return a clone ordered by the primary key.
|
2222
2246
|
def _primary_key_order
|
2223
|
-
if @opts[:order].nil? && model &&
|
2224
|
-
|
2247
|
+
if @opts[:order].nil? && model && model.primary_key
|
2248
|
+
_force_primary_key_order
|
2225
2249
|
end
|
2226
2250
|
end
|
2227
2251
|
|
@@ -8,6 +8,9 @@ module Sequel
|
|
8
8
|
# automatically creates class methods for public dataset
|
9
9
|
# methods.
|
10
10
|
class DatasetModule < Dataset::DatasetModule
|
11
|
+
# The model class related to this dataset module.
|
12
|
+
attr_reader :model
|
13
|
+
|
11
14
|
# Store the model related to this dataset module.
|
12
15
|
def initialize(model)
|
13
16
|
@model = model
|
@@ -24,11 +24,23 @@ module Sequel
|
|
24
24
|
UndefinedAssociation = Class.new(Error)
|
25
25
|
).name
|
26
26
|
|
27
|
-
(
|
28
27
|
# Raised when a mass assignment method is called in strict mode with either a restricted column
|
29
28
|
# or a column without a setter method.
|
30
|
-
MassAssignmentRestriction
|
31
|
-
|
29
|
+
class MassAssignmentRestriction < Error
|
30
|
+
# The Sequel::Model object related to this exception.
|
31
|
+
attr_reader :model
|
32
|
+
|
33
|
+
# The column related to this exception, as a string.
|
34
|
+
attr_reader :column
|
35
|
+
|
36
|
+
# Create an instance of this class with the model and column set.
|
37
|
+
def self.create(msg, model, column)
|
38
|
+
e = new("#{msg} for class #{model.class.inspect}")
|
39
|
+
e.instance_variable_set(:@model, model)
|
40
|
+
e.instance_variable_set(:@column, column)
|
41
|
+
e
|
42
|
+
end
|
43
|
+
end
|
32
44
|
|
33
45
|
# Exception class raised when +raise_on_save_failure+ is set and validation fails
|
34
46
|
class ValidationFailed < Error
|
@@ -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
|
@@ -31,7 +31,6 @@ rescue RuntimeError, OpenSSL::Cipher::CipherError
|
|
31
31
|
# :nocov:
|
32
32
|
end
|
33
33
|
|
34
|
-
require 'base64'
|
35
34
|
require 'securerandom'
|
36
35
|
|
37
36
|
module Sequel
|
@@ -326,7 +325,7 @@ module Sequel
|
|
326
325
|
# DB.alter_table(:ce_test) do
|
327
326
|
# c = Sequel[:encrypted_column_name]
|
328
327
|
# add_constraint(:enc_base64) do
|
329
|
-
# octet_length(decode(regexp_replace(regexp_replace(c, '_', '/', 'g'), '-', '+', 'g'), 'base64')) >= 65
|
328
|
+
# octet_length(decode(regexp_replace(regexp_replace(c, '_', '/', 'g'), '-', '+', 'g'), 'base64')) >= 65
|
330
329
|
# end
|
331
330
|
# end
|
332
331
|
#
|
@@ -375,7 +374,7 @@ module Sequel
|
|
375
374
|
# Decrypt using any supported format and any available key.
|
376
375
|
def decrypt(data)
|
377
376
|
begin
|
378
|
-
data =
|
377
|
+
data = urlsafe_decode64(data)
|
379
378
|
rescue ArgumentError
|
380
379
|
raise Error, "Unable to decode encrypted column: invalid base64"
|
381
380
|
end
|
@@ -448,7 +447,7 @@ module Sequel
|
|
448
447
|
# The prefix string of columns for the given search type and the first configured encryption key.
|
449
448
|
# Used to find values that do not use this prefix in order to perform reencryption.
|
450
449
|
def current_key_prefix(search_type)
|
451
|
-
|
450
|
+
urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
|
452
451
|
end
|
453
452
|
|
454
453
|
# The prefix values to search for the given data (an array of strings), assuming the column uses
|
@@ -472,11 +471,33 @@ module Sequel
|
|
472
471
|
|
473
472
|
private
|
474
473
|
|
474
|
+
if RUBY_VERSION >= '2.4'
|
475
|
+
def decode64(str)
|
476
|
+
str.unpack1("m0")
|
477
|
+
end
|
478
|
+
# :nocov:
|
479
|
+
else
|
480
|
+
def decode64(str)
|
481
|
+
str.unpack("m0")[0]
|
482
|
+
end
|
483
|
+
# :nocov:
|
484
|
+
end
|
485
|
+
|
486
|
+
def urlsafe_encode64(bin)
|
487
|
+
str = [bin].pack("m0")
|
488
|
+
str.tr!("+/", "-_")
|
489
|
+
str
|
490
|
+
end
|
491
|
+
|
492
|
+
def urlsafe_decode64(str)
|
493
|
+
decode64(str.tr("-_", "+/"))
|
494
|
+
end
|
495
|
+
|
475
496
|
# An array of strings, one for each configured encryption key, to find encypted values matching
|
476
497
|
# the given data and search format.
|
477
498
|
def _search_prefixes(data, search_type)
|
478
499
|
@key_map.map do |key_id, (key, _)|
|
479
|
-
|
500
|
+
urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
|
480
501
|
end
|
481
502
|
end
|
482
503
|
|
@@ -509,7 +530,7 @@ module Sequel
|
|
509
530
|
cipher_text << cipher.update(data) if data_size > 0
|
510
531
|
cipher_text << cipher.final
|
511
532
|
|
512
|
-
|
533
|
+
urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
|
513
534
|
end
|
514
535
|
end
|
515
536
|
|
@@ -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]
|
@@ -125,14 +125,15 @@ module Sequel
|
|
125
125
|
ds = @dataset.with_quote_identifiers(false)
|
126
126
|
table_name = ds.literal(ds.first_source_table)
|
127
127
|
reflections = {}
|
128
|
-
|
128
|
+
allow_missing_columns = db_schema.select{|col, sch| sch[:allow_null] == false && nil != sch[:default]}.map(&:first)
|
129
|
+
@constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections, allow_missing_columns)}
|
129
130
|
@constraint_validation_reflections = reflections
|
130
131
|
end
|
131
132
|
end
|
132
133
|
|
133
134
|
# Given a specific database constraint validation metadata row hash, transform
|
134
135
|
# it in an validation method call array suitable for splatting to send.
|
135
|
-
def constraint_validation_array(r, reflections)
|
136
|
+
def constraint_validation_array(r, reflections, allow_missing_columns=EMPTY_ARRAY)
|
136
137
|
opts = {}
|
137
138
|
opts[:message] = r[:message] if r[:message]
|
138
139
|
opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
|
@@ -191,11 +192,13 @@ module Sequel
|
|
191
192
|
reflection_opts[:argument] = arg
|
192
193
|
end
|
193
194
|
|
194
|
-
|
195
|
-
|
196
|
-
|
195
|
+
opts[:from] = :values
|
196
|
+
if column.is_a?(Symbol) && allow_missing_columns.include?(column)
|
197
|
+
opts[:allow_missing] = true
|
197
198
|
end
|
198
199
|
|
200
|
+
a << column << opts
|
201
|
+
|
199
202
|
if column.is_a?(Array) && column.length == 1
|
200
203
|
column = column.first
|
201
204
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
+
require 'delegate'
|
4
|
+
|
3
5
|
module Sequel
|
4
6
|
module Plugins
|
5
7
|
# The defaults_setter plugin makes the column getter methods return the default
|
@@ -106,6 +108,20 @@ module Sequel
|
|
106
108
|
lambda{Date.today}
|
107
109
|
when Sequel::CURRENT_TIMESTAMP
|
108
110
|
lambda{dataset.current_datetime}
|
111
|
+
when Hash, Array
|
112
|
+
v = Marshal.dump(v).freeze
|
113
|
+
lambda{Marshal.load(v)}
|
114
|
+
when Delegator
|
115
|
+
# DelegateClass returns an anonymous case, which cannot be marshalled, so marshal the
|
116
|
+
# underlying object and create a new instance of the class with the unmarshalled object.
|
117
|
+
klass = v.class
|
118
|
+
case o = v.__getobj__
|
119
|
+
when Hash, Array
|
120
|
+
v = Marshal.dump(o).freeze
|
121
|
+
lambda{klass.new(Marshal.load(v))}
|
122
|
+
else
|
123
|
+
v
|
124
|
+
end
|
109
125
|
else
|
110
126
|
v
|
111
127
|
end
|
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)
|
@@ -149,7 +151,7 @@ module Sequel
|
|
149
151
|
ds
|
150
152
|
end
|
151
153
|
|
152
|
-
|
154
|
+
model.dataset.placeholder_literalizer_class.loader(model, &block)
|
153
155
|
end
|
154
156
|
end
|
155
157
|
|
data/lib/sequel/plugins/list.rb
CHANGED
@@ -33,7 +33,9 @@ module Sequel
|
|
33
33
|
# You can provide a <tt>:scope</tt> option to scope the list. This option
|
34
34
|
# can be a symbol or array of symbols specifying column name(s), or a proc
|
35
35
|
# that accepts a model instance and returns a dataset representing the list
|
36
|
-
# the object is in.
|
36
|
+
# the object is in. You will need to provide a <tt>:scope</tt> option if
|
37
|
+
# the model's dataset uses a subquery (such as when using the class_table_inheritance
|
38
|
+
# plugin).
|
37
39
|
#
|
38
40
|
# For example, if each item has a +user_id+ field, and you want every user
|
39
41
|
# to have their own list:
|
@@ -183,10 +185,13 @@ module Sequel
|
|
183
185
|
end
|
184
186
|
|
185
187
|
# Set the value of the position_field to the maximum value plus 1 unless the
|
186
|
-
# position field already has a value.
|
188
|
+
# position field already has a value. If the list is empty, the position will
|
189
|
+
# be set to the model's +top_of_list+ value.
|
187
190
|
def before_validation
|
188
191
|
unless get_column_value(position_field)
|
189
|
-
|
192
|
+
current_max = list_dataset.max(position_field)
|
193
|
+
value = current_max.nil? ? model.top_of_list : current_max.to_i + 1
|
194
|
+
set_column_value("#{position_field}=", value)
|
190
195
|
end
|
191
196
|
super
|
192
197
|
end
|
@@ -385,7 +385,7 @@ module Sequel
|
|
385
385
|
iq = nil
|
386
386
|
end
|
387
387
|
fe = opts.final_edge
|
388
|
-
ds.graph(opts
|
388
|
+
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
|
389
389
|
end
|
390
390
|
end
|
391
391
|
end
|