sequel 5.33.0 → 5.58.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +318 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +40 -9
- data/doc/association_basics.rdoc +77 -13
- data/doc/cheat_sheet.rdoc +13 -5
- data/doc/code_order.rdoc +0 -12
- data/doc/dataset_filtering.rdoc +2 -2
- data/doc/fork_safety.rdoc +84 -0
- data/doc/migration.rdoc +12 -6
- data/doc/model_plugins.rdoc +1 -1
- data/doc/opening_databases.rdoc +15 -3
- data/doc/postgresql.rdoc +9 -1
- data/doc/querying.rdoc +7 -5
- data/doc/release_notes/5.34.0.txt +40 -0
- data/doc/release_notes/5.35.0.txt +56 -0
- data/doc/release_notes/5.36.0.txt +60 -0
- data/doc/release_notes/5.37.0.txt +30 -0
- data/doc/release_notes/5.38.0.txt +28 -0
- data/doc/release_notes/5.39.0.txt +19 -0
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/sql.rdoc +14 -2
- data/doc/testing.rdoc +10 -1
- data/doc/transactions.rdoc +0 -8
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +29 -19
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +8 -6
- data/lib/sequel/adapters/oracle.rb +5 -4
- data/lib/sequel/adapters/postgres.rb +27 -29
- data/lib/sequel/adapters/shared/access.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +84 -7
- data/lib/sequel/adapters/shared/mysql.rb +33 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -7
- data/lib/sequel/adapters/shared/postgres.rb +158 -20
- data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
- data/lib/sequel/adapters/shared/sqlite.rb +102 -10
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +2 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +9 -8
- data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
- data/lib/sequel/connection_pool/single.rb +7 -9
- data/lib/sequel/connection_pool/threaded.rb +1 -1
- data/lib/sequel/core.rb +33 -24
- data/lib/sequel/database/connecting.rb +3 -4
- data/lib/sequel/database/misc.rb +37 -12
- data/lib/sequel/database/query.rb +3 -1
- data/lib/sequel/database/schema_generator.rb +50 -53
- data/lib/sequel/database/schema_methods.rb +45 -23
- data/lib/sequel/database/transactions.rb +9 -6
- data/lib/sequel/dataset/actions.rb +61 -8
- data/lib/sequel/dataset/features.rb +15 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +114 -11
- data/lib/sequel/dataset/sql.rb +172 -46
- data/lib/sequel/deprecated.rb +3 -1
- data/lib/sequel/exceptions.rb +2 -0
- data/lib/sequel/extensions/_pretty_table.rb +1 -2
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/columns_introspection.rb +1 -2
- data/lib/sequel/extensions/core_refinements.rb +38 -11
- data/lib/sequel/extensions/date_arithmetic.rb +36 -24
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +3 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +139 -0
- data/lib/sequel/extensions/migration.rb +13 -2
- data/lib/sequel/extensions/named_timezones.rb +5 -1
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +1 -0
- data/lib/sequel/extensions/pg_array_ops.rb +6 -2
- data/lib/sequel/extensions/pg_enum.rb +3 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +2 -2
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +55 -3
- data/lib/sequel/extensions/pg_inet.rb +2 -0
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +35 -8
- data/lib/sequel/extensions/pg_json.rb +3 -5
- data/lib/sequel/extensions/pg_json_ops.rb +119 -4
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +372 -0
- data/lib/sequel/extensions/pg_range.rb +7 -19
- data/lib/sequel/extensions/pg_range_ops.rb +39 -9
- data/lib/sequel/extensions/pg_row.rb +1 -1
- data/lib/sequel/extensions/pg_row_ops.rb +25 -1
- data/lib/sequel/extensions/query.rb +3 -0
- data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
- data/lib/sequel/extensions/s.rb +4 -1
- data/lib/sequel/extensions/schema_dumper.rb +16 -5
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
- data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
- data/lib/sequel/extensions/to_dot.rb +9 -3
- data/lib/sequel/model/associations.rb +342 -114
- data/lib/sequel/model/base.rb +45 -24
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +8 -3
- data/lib/sequel/model.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +60 -18
- data/lib/sequel/plugins/association_proxies.rb +3 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +39 -5
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/blacklist_security.rb +1 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +3 -8
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +8 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +2 -1
- data/lib/sequel/plugins/csv_serializer.rb +2 -0
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/dirty.rb +44 -0
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +113 -0
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +4 -1
- data/lib/sequel/plugins/many_through_many.rb +108 -9
- data/lib/sequel/plugins/nested_attributes.rb +8 -3
- data/lib/sequel/plugins/pg_array_associations.rb +58 -41
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
- data/lib/sequel/plugins/prepared_statements.rb +15 -12
- data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
- data/lib/sequel/plugins/rcte_tree.rb +37 -35
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/string_stripper.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/tree.rb +9 -4
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_class_methods.rb +5 -1
- data/lib/sequel/plugins/validation_helpers.rb +18 -11
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +20 -17
- data/lib/sequel/version.rb +1 -1
- metadata +93 -39
data/lib/sequel/model/base.rb
CHANGED
@@ -491,6 +491,11 @@ module Sequel
|
|
491
491
|
# the module using a the camelized plugin name under Sequel::Plugins.
|
492
492
|
def plugin(plugin, *args, &block)
|
493
493
|
m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
|
494
|
+
|
495
|
+
if !m.respond_to?(:apply) && !m.respond_to?(:configure) && (!args.empty? || block)
|
496
|
+
Deprecation.deprecate("Plugin #{plugin} accepts no arguments or block, and passing arguments/block to it", "Remove arguments and block when loading the plugin")
|
497
|
+
end
|
498
|
+
|
494
499
|
unless @plugins.include?(m)
|
495
500
|
@plugins << m
|
496
501
|
m.apply(self, *args, &block) if m.respond_to?(:apply)
|
@@ -500,8 +505,12 @@ module Sequel
|
|
500
505
|
dataset_extend(m::DatasetMethods, :create_class_methods=>false)
|
501
506
|
end
|
502
507
|
end
|
508
|
+
|
503
509
|
m.configure(self, *args, &block) if m.respond_to?(:configure)
|
504
510
|
end
|
511
|
+
# :nocov:
|
512
|
+
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
|
513
|
+
# :nocov:
|
505
514
|
|
506
515
|
# Returns primary key attribute hash. If using a composite primary key
|
507
516
|
# value such be an array with values for each primary key in the correct
|
@@ -593,7 +602,7 @@ module Sequel
|
|
593
602
|
@columns = superclass.instance_variable_get(:@columns)
|
594
603
|
@db_schema = superclass.instance_variable_get(:@db_schema)
|
595
604
|
else
|
596
|
-
@dataset = @dataset.with_extend(*@dataset_method_modules.reverse)
|
605
|
+
@dataset = @dataset.with_extend(*@dataset_method_modules.reverse)
|
597
606
|
@db_schema = get_db_schema
|
598
607
|
end
|
599
608
|
|
@@ -632,8 +641,7 @@ module Sequel
|
|
632
641
|
|
633
642
|
# Cache of setter methods to allow by default, in order to speed up mass assignment.
|
634
643
|
def setter_methods
|
635
|
-
|
636
|
-
@setter_methods = get_setter_methods
|
644
|
+
@setter_methods || (@setter_methods = get_setter_methods)
|
637
645
|
end
|
638
646
|
|
639
647
|
# Returns name of primary table for the dataset. If the table for the dataset
|
@@ -674,13 +682,11 @@ module Sequel
|
|
674
682
|
|
675
683
|
# Yield to the passed block and if do_raise is false, swallow all errors other than DatabaseConnectionErrors.
|
676
684
|
def check_non_connection_error(do_raise=require_valid_table)
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
raise if do_raise
|
683
|
-
end
|
685
|
+
db.transaction(:savepoint=>:only){yield}
|
686
|
+
rescue Sequel::DatabaseConnectionError
|
687
|
+
raise
|
688
|
+
rescue Sequel::Error
|
689
|
+
raise if do_raise
|
684
690
|
end
|
685
691
|
|
686
692
|
# Convert the given object to a Dataset that should be used as
|
@@ -721,8 +727,14 @@ module Sequel
|
|
721
727
|
im = instance_methods
|
722
728
|
overridable_methods_module.module_eval do
|
723
729
|
meth = :"#{column}="
|
724
|
-
|
725
|
-
|
730
|
+
unless im.include?(column)
|
731
|
+
define_method(column){self[column]}
|
732
|
+
alias_method(column, column)
|
733
|
+
end
|
734
|
+
unless im.include?(meth)
|
735
|
+
define_method(meth){|v| self[column] = v}
|
736
|
+
alias_method(meth, meth)
|
737
|
+
end
|
726
738
|
end
|
727
739
|
end
|
728
740
|
|
@@ -735,8 +747,14 @@ module Sequel
|
|
735
747
|
im = instance_methods
|
736
748
|
columns.each do |column|
|
737
749
|
meth = :"#{column}="
|
738
|
-
|
739
|
-
|
750
|
+
unless im.include?(column)
|
751
|
+
overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__)
|
752
|
+
overridable_methods_module.send(:alias_method, column, column)
|
753
|
+
end
|
754
|
+
unless im.include?(meth)
|
755
|
+
overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end", __FILE__, __LINE__)
|
756
|
+
overridable_methods_module.send(:alias_method, meth, meth)
|
757
|
+
end
|
740
758
|
end
|
741
759
|
end
|
742
760
|
|
@@ -751,6 +769,10 @@ module Sequel
|
|
751
769
|
else
|
752
770
|
define_singleton_method(meth){|*args, &block| dataset.public_send(meth, *args, &block)}
|
753
771
|
end
|
772
|
+
singleton_class.send(:alias_method, meth, meth)
|
773
|
+
# :nocov:
|
774
|
+
singleton_class.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
775
|
+
# :nocov:
|
754
776
|
end
|
755
777
|
|
756
778
|
# Get the schema from the database, fall back on checking the columns
|
@@ -820,7 +842,6 @@ module Sequel
|
|
820
842
|
super
|
821
843
|
ivs = subclass.instance_variables
|
822
844
|
inherited_instance_variables.each do |iv, dup|
|
823
|
-
next if ivs.include?(iv)
|
824
845
|
if (sup_class_value = instance_variable_get(iv)) && dup
|
825
846
|
sup_class_value = case dup
|
826
847
|
when :dup
|
@@ -1070,7 +1091,7 @@ module Sequel
|
|
1070
1091
|
@modified = true
|
1071
1092
|
initialize_set(values)
|
1072
1093
|
_clear_changed_columns(:initialize)
|
1073
|
-
yield self if
|
1094
|
+
yield self if defined?(yield)
|
1074
1095
|
end
|
1075
1096
|
|
1076
1097
|
# Returns value of the column's attribute.
|
@@ -1116,7 +1137,7 @@ module Sequel
|
|
1116
1137
|
when nil
|
1117
1138
|
return false
|
1118
1139
|
when Array
|
1119
|
-
return false if
|
1140
|
+
return false if pkv.any?(&:nil?)
|
1120
1141
|
end
|
1121
1142
|
|
1122
1143
|
(obj.class == model) && (obj.pk == pkv)
|
@@ -1237,12 +1258,12 @@ module Sequel
|
|
1237
1258
|
# Once an object is frozen, you cannot modify it's values, changed_columns,
|
1238
1259
|
# errors, or dataset.
|
1239
1260
|
def freeze
|
1240
|
-
values.freeze
|
1241
|
-
_changed_columns.freeze
|
1242
1261
|
unless errors.frozen?
|
1243
1262
|
validate
|
1244
1263
|
errors.freeze
|
1245
1264
|
end
|
1265
|
+
values.freeze
|
1266
|
+
_changed_columns.freeze
|
1246
1267
|
this if !new? && model.primary_key
|
1247
1268
|
super
|
1248
1269
|
end
|
@@ -1607,11 +1628,9 @@ module Sequel
|
|
1607
1628
|
# artist.set(name: 'Invalid').valid? # => false
|
1608
1629
|
# artist.errors.full_messages # => ['name cannot be Invalid']
|
1609
1630
|
def valid?(opts = OPTS)
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
false
|
1614
|
-
end
|
1631
|
+
_valid?(opts)
|
1632
|
+
rescue HookFailed
|
1633
|
+
false
|
1615
1634
|
end
|
1616
1635
|
|
1617
1636
|
private
|
@@ -2232,7 +2251,9 @@ module Sequel
|
|
2232
2251
|
plugin self
|
2233
2252
|
|
2234
2253
|
singleton_class.send(:undef_method, :dup, :clone, :initialize_copy)
|
2254
|
+
# :nocov:
|
2235
2255
|
if RUBY_VERSION >= '1.9.3'
|
2256
|
+
# :nocov:
|
2236
2257
|
singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
|
2237
2258
|
end
|
2238
2259
|
end
|
data/lib/sequel/model/errors.rb
CHANGED
@@ -38,7 +38,7 @@ module Sequel
|
|
38
38
|
def full_messages
|
39
39
|
inject([]) do |m, kv|
|
40
40
|
att, errors = *kv
|
41
|
-
errors.each {|e| m << (e.is_a?(LiteralString) ? e :
|
41
|
+
errors.each {|e| m << (e.is_a?(LiteralString) ? e : full_message(att, e))}
|
42
42
|
m
|
43
43
|
end
|
44
44
|
end
|
@@ -53,6 +53,15 @@ module Sequel
|
|
53
53
|
v
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Create full error message to use for the given attribute (or array of attributes)
|
60
|
+
# and error message. This can be overridden for easier internalization.
|
61
|
+
def full_message(att, error_msg)
|
62
|
+
att = att.join(' and ') if att.is_a?(Array)
|
63
|
+
"#{att} #{error_msg}"
|
64
|
+
end
|
56
65
|
end
|
57
66
|
end
|
58
67
|
end
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -31,6 +31,9 @@ module Sequel
|
|
31
31
|
def self.def_dataset_methods(mod, meths)
|
32
32
|
Array(meths).each do |meth|
|
33
33
|
mod.class_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
|
34
|
+
# :nocov:
|
35
|
+
mod.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
36
|
+
# :nocov:
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
@@ -119,6 +122,7 @@ module Sequel
|
|
119
122
|
|
120
123
|
model.send(:define_method, meth, &block)
|
121
124
|
model.send(:private, meth)
|
125
|
+
model.send(:alias_method, meth, meth)
|
122
126
|
call_meth
|
123
127
|
end
|
124
128
|
|
@@ -140,6 +144,8 @@ module Sequel
|
|
140
144
|
keyword = :required
|
141
145
|
when :key, :keyrest
|
142
146
|
keyword ||= true
|
147
|
+
else
|
148
|
+
raise Error, "invalid arg_type passed to _define_sequel_method_arg_numbers: #{arg_type}"
|
143
149
|
end
|
144
150
|
end
|
145
151
|
arity = callable.arity
|
@@ -149,9 +155,8 @@ module Sequel
|
|
149
155
|
required_args = arity
|
150
156
|
arity -= 1 if keyword == :required
|
151
157
|
|
152
|
-
|
153
|
-
|
154
|
-
end
|
158
|
+
# callable currently is always a non-lambda Proc
|
159
|
+
optional_args -= arity
|
155
160
|
|
156
161
|
[required_args, optional_args, rest, keyword]
|
157
162
|
end
|
data/lib/sequel/model.rb
CHANGED
@@ -69,7 +69,9 @@ module Sequel
|
|
69
69
|
require_relative "model/base"
|
70
70
|
require_relative "model/exceptions"
|
71
71
|
require_relative "model/errors"
|
72
|
+
# :nocov:
|
72
73
|
if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
|
74
|
+
# :nocov:
|
73
75
|
require_relative 'model/associations'
|
74
76
|
plugin Model::Associations
|
75
77
|
end
|
@@ -77,7 +79,7 @@ module Sequel
|
|
77
79
|
def_Model(::Sequel)
|
78
80
|
|
79
81
|
# The setter methods (methods ending with =) that are never allowed
|
80
|
-
# to be called automatically via +set+/+update+/+new+/etc
|
82
|
+
# to be called automatically via +set+/+update+/+new+/etc.
|
81
83
|
RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}.freeze
|
82
84
|
end
|
83
85
|
end
|
@@ -2,13 +2,17 @@
|
|
2
2
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
|
-
# The association_pks plugin adds association_pks and
|
6
|
-
# instance methods to the model class for each
|
7
|
-
#
|
8
|
-
#
|
5
|
+
# The association_pks plugin adds association_pks, association_pks=, and
|
6
|
+
# association_pks_dataset instance methods to the model class for each
|
7
|
+
# one_to_many and many_to_many association added. These methods allow for
|
8
|
+
# easily returning the primary keys of the associated objects, and easily
|
9
|
+
# modifying which objects are associated:
|
9
10
|
#
|
10
11
|
# Artist.one_to_many :albums
|
11
12
|
# artist = Artist[1]
|
13
|
+
# artist.album_pks_dataset
|
14
|
+
# # SELECT id FROM albums WHERE (albums.artist_id = 1)
|
15
|
+
#
|
12
16
|
# artist.album_pks # [1, 2, 3]
|
13
17
|
# artist.album_pks = [2, 4]
|
14
18
|
# artist.album_pks # [2, 4]
|
@@ -22,11 +26,18 @@ module Sequel
|
|
22
26
|
# This plugin makes modifications directly to the underlying tables,
|
23
27
|
# it does not create or return any model objects, and therefore does
|
24
28
|
# not call any callbacks. If you have any association callbacks,
|
25
|
-
# you probably should not use the setter methods.
|
29
|
+
# you probably should not use the setter methods this plugin adds.
|
26
30
|
#
|
27
31
|
# By default, changes to the association will not happen until the object
|
28
|
-
# is saved. However, using the delay_pks: false option, you can have
|
29
|
-
# changes made immediately when the association_pks setter method is called.
|
32
|
+
# is saved. However, using the delay_pks: false association option, you can have
|
33
|
+
# the changes made immediately when the association_pks setter method is called.
|
34
|
+
#
|
35
|
+
# By default, repeated calls to the association_pks getter method will not be
|
36
|
+
# cached, unless the setter method has been used and the delay_pks: false
|
37
|
+
# association option is not used. You can set caching of repeated calls to the
|
38
|
+
# association_pks getter method using the :cache_pks association option. You can
|
39
|
+
# pass the :refresh option when calling the getter method to ignore any existing
|
40
|
+
# cached values, similar to how the :refresh option works with associations.
|
30
41
|
#
|
31
42
|
# By default, if you pass a nil value to the setter, an exception will be raised.
|
32
43
|
# You can change this behavior by using the :association_pks_nil association option.
|
@@ -60,9 +71,11 @@ module Sequel
|
|
60
71
|
|
61
72
|
# Define a association_pks method using the block for the association reflection
|
62
73
|
def def_association_pks_methods(opts)
|
74
|
+
association_module_def(opts[:pks_dataset_method], &opts[:pks_dataset])
|
75
|
+
|
63
76
|
opts[:pks_getter_method] = :"#{singularize(opts[:name])}_pks_getter"
|
64
77
|
association_module_def(opts[:pks_getter_method], &opts[:pks_getter])
|
65
|
-
association_module_def(:"#{singularize(opts[:name])}_pks", opts){_association_pks_getter(opts)}
|
78
|
+
association_module_def(:"#{singularize(opts[:name])}_pks", opts){|dynamic_opts=OPTS| _association_pks_getter(opts, dynamic_opts)}
|
66
79
|
|
67
80
|
if opts[:pks_setter]
|
68
81
|
opts[:pks_setter_method] = :"#{singularize(opts[:name])}_pks_setter"
|
@@ -84,7 +97,9 @@ module Sequel
|
|
84
97
|
clpk = lpk.is_a?(Array)
|
85
98
|
crk = rk.is_a?(Array)
|
86
99
|
|
87
|
-
opts[:
|
100
|
+
dataset_method = opts[:pks_dataset_method] = :"#{singularize(opts[:name])}_pks_dataset"
|
101
|
+
|
102
|
+
opts[:pks_dataset] = if join_associated_table = opts[:association_pks_use_associated_table]
|
88
103
|
tname = opts[:join_table]
|
89
104
|
lambda do
|
90
105
|
cond = if clpk
|
@@ -95,16 +110,26 @@ module Sequel
|
|
95
110
|
rpk = opts.associated_class.primary_key
|
96
111
|
opts.associated_dataset.
|
97
112
|
naked.where(cond).
|
98
|
-
|
113
|
+
select(*Sequel.public_send(rpk.is_a?(Array) ? :deep_qualify : :qualify, opts.associated_class.table_name, rpk))
|
99
114
|
end
|
100
115
|
elsif clpk
|
101
116
|
lambda do
|
102
117
|
cond = lk.zip(lpk).map{|k, pk| [k, get_column_value(pk)]}
|
103
|
-
_join_table_dataset(opts).where(cond).
|
118
|
+
_join_table_dataset(opts).where(cond).select(*rk)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
lambda do
|
122
|
+
_join_table_dataset(opts).where(lk=>get_column_value(lpk)).select(*rk)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
opts[:pks_getter] = if join_associated_table = opts[:association_pks_use_associated_table]
|
127
|
+
lambda do
|
128
|
+
public_send(dataset_method).map(opts.associated_class.primary_key)
|
104
129
|
end
|
105
130
|
else
|
106
131
|
lambda do
|
107
|
-
|
132
|
+
public_send(dataset_method).map(rk)
|
108
133
|
end
|
109
134
|
end
|
110
135
|
|
@@ -145,8 +170,14 @@ module Sequel
|
|
145
170
|
|
146
171
|
key = opts[:key]
|
147
172
|
|
173
|
+
dataset_method = opts[:pks_dataset_method] = :"#{singularize(opts[:name])}_pks_dataset"
|
174
|
+
|
175
|
+
opts[:pks_dataset] = lambda do
|
176
|
+
public_send(opts[:dataset_method]).select(*opts.associated_class.primary_key)
|
177
|
+
end
|
178
|
+
|
148
179
|
opts[:pks_getter] = lambda do
|
149
|
-
public_send(
|
180
|
+
public_send(dataset_method).map(opts.associated_class.primary_key)
|
150
181
|
end
|
151
182
|
|
152
183
|
unless opts[:read_only]
|
@@ -207,12 +238,22 @@ module Sequel
|
|
207
238
|
# Return the primary keys of the associated objects.
|
208
239
|
# If the receiver is a new object, return any saved
|
209
240
|
# pks, or an empty array if no pks have been saved.
|
210
|
-
def _association_pks_getter(opts)
|
241
|
+
def _association_pks_getter(opts, dynamic_opts=OPTS)
|
242
|
+
do_cache = opts[:cache_pks]
|
211
243
|
delay = opts.fetch(:delay_pks, true)
|
212
|
-
|
244
|
+
cache_or_delay = do_cache || delay
|
245
|
+
|
246
|
+
if dynamic_opts[:refresh] && @_association_pks
|
247
|
+
@_association_pks.delete(opts[:name])
|
248
|
+
end
|
249
|
+
|
250
|
+
if new? && cache_or_delay
|
213
251
|
(@_association_pks ||= {})[opts[:name]] ||= []
|
214
|
-
elsif
|
252
|
+
elsif cache_or_delay && @_association_pks && (objs = @_association_pks[opts[:name]])
|
215
253
|
objs
|
254
|
+
elsif do_cache
|
255
|
+
# pks_getter_method is private
|
256
|
+
(@_association_pks ||= {})[opts[:name]] = send(opts[:pks_getter_method])
|
216
257
|
else
|
217
258
|
# pks_getter_method is private
|
218
259
|
send(opts[:pks_getter_method])
|
@@ -254,9 +295,10 @@ module Sequel
|
|
254
295
|
|
255
296
|
if primary_key.is_a?(Array)
|
256
297
|
if (cols = sch.values_at(*klass.primary_key)).all? && (convs = cols.map{|c| c[:type] == :integer}).all?
|
298
|
+
db = model.db
|
257
299
|
pks.map do |cpk|
|
258
|
-
cpk.
|
259
|
-
|
300
|
+
cpk.map do |pk|
|
301
|
+
db.typecast_value(:integer, pk)
|
260
302
|
end
|
261
303
|
end
|
262
304
|
else
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
extension 'async_thread_pool'
|
5
|
+
|
6
|
+
module Plugins
|
7
|
+
# The async_thread_pool plugin makes it slightly easier to use the async_thread_pool
|
8
|
+
# Database extension with models. It makes Model.async return an async dataset for the
|
9
|
+
# model, and support async behavior for #destroy, #with_pk, and #with_pk! for model
|
10
|
+
# datasets:
|
11
|
+
#
|
12
|
+
# # Will load the artist with primary key 1 asynchronously
|
13
|
+
# artist = Artist.async.with_pk(1)
|
14
|
+
#
|
15
|
+
# You must load the async_thread_pool Database extension into the Database object the
|
16
|
+
# model class uses in order for async behavior to work.
|
17
|
+
#
|
18
|
+
# Usage:
|
19
|
+
#
|
20
|
+
# # Make all model subclass datasets support support async class methods and additional
|
21
|
+
# # async dataset methods
|
22
|
+
# Sequel::Model.plugin :async_thread_pool
|
23
|
+
#
|
24
|
+
# # Make the Album class support async class method and additional async dataset methods
|
25
|
+
# Album.plugin :async_thread_pool
|
26
|
+
module AsyncThreadPool
|
27
|
+
module ClassMethods
|
28
|
+
Plugins.def_dataset_methods(self, :async)
|
29
|
+
end
|
30
|
+
|
31
|
+
module DatasetMethods
|
32
|
+
[:destroy, :with_pk, :with_pk!].each do |meth|
|
33
|
+
::Sequel::Database::AsyncThreadPool::DatasetMethods.define_async_method(self, meth)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The auto_restrict_eager_graph plugin will automatically disallow the use
|
6
|
+
# of eager_graph for associations that have associated blocks but no :graph_*
|
7
|
+
# association options. The reason for this is the block will have an effect
|
8
|
+
# during regular and eager loading, but not loading via eager_graph, and it
|
9
|
+
# is likely that whatever the block is doing should have an equivalent done
|
10
|
+
# when eager_graphing. Most likely, not including a :graph_* option was either
|
11
|
+
# an oversight (and one should be added), or use with eager_graph was never
|
12
|
+
# intended (and usage should be forbidden). Disallowing eager_graph in this
|
13
|
+
# case prevents likely unexpected behavior during eager_graph.
|
14
|
+
#
|
15
|
+
# As an example of this, consider the following code:
|
16
|
+
#
|
17
|
+
# Album.one_to_many :popular_tracks, class: :Track do |ds|
|
18
|
+
# ds = ds.where(popular: true)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Album.eager(:popular_tracks).all
|
22
|
+
# # SELECT * FROM albums
|
23
|
+
# # SELECT * FROM tracks WHERE ((popular IS TRUE) AND (album_id IN (...)))
|
24
|
+
#
|
25
|
+
# # Notice that no condition for tracks.popular is added.
|
26
|
+
# Album.eager_graph(:popular_tracks).all
|
27
|
+
# # SELECT ... FROM albums LEFT JOIN tracks ON (tracks.album_id = albums.id)
|
28
|
+
#
|
29
|
+
# With the auto_restrict_eager_graph plugin, the eager_graph call above will
|
30
|
+
# raise an error, alerting you to the fact that you either should not be
|
31
|
+
# using eager_graph with the association, or that you should be adding an
|
32
|
+
# appropriate :graph_* option, such as:
|
33
|
+
#
|
34
|
+
# Album.one_to_many :popular_tracks, class: :Track, graph_conditions: {popular: true} do |ds|
|
35
|
+
# ds = ds.where(popular: true)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Usage:
|
39
|
+
#
|
40
|
+
# # Automatically restrict eager_graph for associations if appropriate for all
|
41
|
+
# # model subclasses (called before loading subclasses)
|
42
|
+
# Sequel::Model.plugin :auto_restrict_eager_graph
|
43
|
+
#
|
44
|
+
# # Automatically restrict eager_graph for associations in Album class
|
45
|
+
# Album.plugin :auto_restrict_eager_graph
|
46
|
+
module AutoRestrictEagerGraph
|
47
|
+
module ClassMethods
|
48
|
+
# When defining an association, if a block is given for the association, but
|
49
|
+
# a :graph_* option is not used, disallow the use of eager_graph.
|
50
|
+
def associate(type, name, opts = OPTS, &block)
|
51
|
+
opts = super
|
52
|
+
|
53
|
+
if opts[:block] && !opts.has_key?(:allow_eager_graph) && !opts[:orig_opts].any?{|k,| /\Agraph_/ =~ k}
|
54
|
+
opts[:allow_eager_graph] = false
|
55
|
+
end
|
56
|
+
|
57
|
+
opts
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -14,7 +14,9 @@ module Sequel
|
|
14
14
|
# the plugin looks at the database schema for the model's table. To determine
|
15
15
|
# the unique validations, Sequel looks at the indexes on the table. In order
|
16
16
|
# for this plugin to be fully functional, the underlying database adapter needs
|
17
|
-
# to support both schema and index parsing.
|
17
|
+
# to support both schema and index parsing. Additionally, unique validations are
|
18
|
+
# only added for models that select from a simple table, they are not added for models
|
19
|
+
# that select from a subquery or joined dataset.
|
18
20
|
#
|
19
21
|
# This plugin uses the validation_helpers plugin underneath to implement the
|
20
22
|
# validations. It does not allow for any per-column validation message
|
@@ -51,6 +53,11 @@ module Sequel
|
|
51
53
|
# This works for unique_opts, max_length_opts, schema_types_opts,
|
52
54
|
# explicit_not_null_opts, and not_null_opts.
|
53
55
|
#
|
56
|
+
# If you only want auto_validations to add validations to columns that do not already
|
57
|
+
# have an error associated with them, you can use the skip_invalid option:
|
58
|
+
#
|
59
|
+
# Model.plugin :auto_validations, skip_invalid: true
|
60
|
+
#
|
54
61
|
# Usage:
|
55
62
|
#
|
56
63
|
# # Make all model subclass use auto validations (called before loading subclasses)
|
@@ -64,12 +71,14 @@ module Sequel
|
|
64
71
|
MAX_LENGTH_OPTIONS = {:from=>:values, :allow_nil=>true}.freeze
|
65
72
|
SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
|
66
73
|
UNIQUE_OPTIONS = NOT_NULL_OPTIONS
|
74
|
+
NO_NULL_BYTE_OPTIONS = MAX_LENGTH_OPTIONS
|
67
75
|
EMPTY_ARRAY = [].freeze
|
68
76
|
|
69
77
|
def self.apply(model, opts=OPTS)
|
70
78
|
model.instance_exec do
|
71
79
|
plugin :validation_helpers
|
72
80
|
@auto_validate_presence = false
|
81
|
+
@auto_validate_no_null_byte_columns = []
|
73
82
|
@auto_validate_not_null_columns = []
|
74
83
|
@auto_validate_explicit_not_null_columns = []
|
75
84
|
@auto_validate_max_length_columns = []
|
@@ -77,6 +86,7 @@ module Sequel
|
|
77
86
|
@auto_validate_types = true
|
78
87
|
|
79
88
|
@auto_validate_options = {
|
89
|
+
:no_null_byte=>NO_NULL_BYTE_OPTIONS,
|
80
90
|
:not_null=>NOT_NULL_OPTIONS,
|
81
91
|
:explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
|
82
92
|
:max_length=>MAX_LENGTH_OPTIONS,
|
@@ -95,16 +105,26 @@ module Sequel
|
|
95
105
|
end
|
96
106
|
|
97
107
|
h = @auto_validate_options.dup
|
98
|
-
[:not_null, :explicit_not_null, :max_length, :schema_types, :unique].each do |type|
|
108
|
+
[:not_null, :explicit_not_null, :max_length, :no_null_byte, :schema_types, :unique].each do |type|
|
99
109
|
if type_opts = opts[:"#{type}_opts"]
|
100
110
|
h[type] = h[type].merge(type_opts).freeze
|
101
111
|
end
|
102
112
|
end
|
113
|
+
|
114
|
+
if opts[:skip_invalid]
|
115
|
+
[:not_null, :explicit_not_null, :no_null_byte, :max_length, :schema_types].each do |type|
|
116
|
+
h[type] = h[type].merge(:skip_invalid=>true).freeze
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
103
120
|
@auto_validate_options = h.freeze
|
104
121
|
end
|
105
122
|
end
|
106
123
|
|
107
124
|
module ClassMethods
|
125
|
+
# The columns with automatic no_null_byte validations
|
126
|
+
attr_reader :auto_validate_no_null_byte_columns
|
127
|
+
|
108
128
|
# The columns with automatic not_null validations
|
109
129
|
attr_reader :auto_validate_not_null_columns
|
110
130
|
|
@@ -121,7 +141,15 @@ module Sequel
|
|
121
141
|
# Inherited options
|
122
142
|
attr_reader :auto_validate_options
|
123
143
|
|
124
|
-
Plugins.inherited_instance_variables(self,
|
144
|
+
Plugins.inherited_instance_variables(self,
|
145
|
+
:@auto_validate_presence=>nil,
|
146
|
+
:@auto_validate_types=>nil,
|
147
|
+
:@auto_validate_no_null_byte_columns=>:dup,
|
148
|
+
:@auto_validate_not_null_columns=>:dup,
|
149
|
+
:@auto_validate_explicit_not_null_columns=>:dup,
|
150
|
+
:@auto_validate_max_length_columns=>:dup,
|
151
|
+
:@auto_validate_unique_columns=>:dup,
|
152
|
+
:@auto_validate_options => :dup)
|
125
153
|
Plugins.after_set_dataset(self, :setup_auto_validations)
|
126
154
|
|
127
155
|
# Whether to use a presence validation for not null columns
|
@@ -136,6 +164,7 @@ module Sequel
|
|
136
164
|
|
137
165
|
# Freeze auto_validation settings when freezing model class.
|
138
166
|
def freeze
|
167
|
+
@auto_validate_no_null_byte_columns.freeze
|
139
168
|
@auto_validate_not_null_columns.freeze
|
140
169
|
@auto_validate_explicit_not_null_columns.freeze
|
141
170
|
@auto_validate_max_length_columns.freeze
|
@@ -144,12 +173,13 @@ module Sequel
|
|
144
173
|
super
|
145
174
|
end
|
146
175
|
|
147
|
-
# Skip automatic validations for the given validation type
|
176
|
+
# Skip automatic validations for the given validation type
|
177
|
+
# (:not_null, :types, :unique, :max_length, :no_null_byte).
|
148
178
|
# If :all is given as the type, skip all auto validations.
|
149
179
|
def skip_auto_validations(type)
|
150
180
|
case type
|
151
181
|
when :all
|
152
|
-
[:not_null, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
|
182
|
+
[:not_null, :no_null_byte, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
|
153
183
|
when :not_null
|
154
184
|
auto_validate_not_null_columns.clear
|
155
185
|
auto_validate_explicit_not_null_columns.clear
|
@@ -169,6 +199,7 @@ module Sequel
|
|
169
199
|
explicit_not_null_cols += Array(primary_key)
|
170
200
|
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
171
201
|
@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]]}
|
202
|
+
@auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
|
172
203
|
table = dataset.first_source_table
|
173
204
|
@auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
|
174
205
|
db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns].length == 1 ? idx[:columns].first : idx[:columns]}
|
@@ -195,6 +226,9 @@ module Sequel
|
|
195
226
|
return if skip.include?(:all)
|
196
227
|
opts = model.auto_validate_options
|
197
228
|
|
229
|
+
unless skip.include?(:no_null_byte) || (no_null_byte_columns = model.auto_validate_no_null_byte_columns).empty?
|
230
|
+
validates_no_null_byte(no_null_byte_columns, opts[:no_null_byte])
|
231
|
+
end
|
198
232
|
|
199
233
|
unless skip.include?(:not_null)
|
200
234
|
not_null_method = model.auto_validate_presence? ? :validates_presence : :validates_not_null
|