sequel 5.103.0 → 5.105.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/lib/sequel/extensions/connection_expiration.rb +43 -4
- data/lib/sequel/model/associations.rb +180 -5
- data/lib/sequel/model/base.rb +95 -8
- data/lib/sequel/model/plugins.rb +12 -1
- data/lib/sequel/model.rb +1 -0
- data/lib/sequel/plugins/accessed_columns.rb +4 -0
- data/lib/sequel/plugins/active_model.rb +5 -6
- data/lib/sequel/plugins/association_pks.rb +2 -0
- data/lib/sequel/plugins/auto_validations.rb +2 -0
- data/lib/sequel/plugins/columns_updated.rb +4 -0
- data/lib/sequel/plugins/composition.rb +2 -0
- data/lib/sequel/plugins/dataset_associations.rb +20 -1
- data/lib/sequel/plugins/dirty.rb +4 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +1 -0
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_filters.rb +4 -0
- data/lib/sequel/plugins/instance_hooks.rb +4 -0
- data/lib/sequel/plugins/json_serializer.rb +2 -0
- data/lib/sequel/plugins/many_through_many.rb +21 -0
- data/lib/sequel/plugins/modification_detection.rb +2 -0
- data/lib/sequel/plugins/serialization.rb +2 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +4 -0
- data/lib/sequel/plugins/split_values.rb +2 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +4 -0
- data/lib/sequel/plugins/update_primary_key.rb +4 -0
- data/lib/sequel/plugins/validation_contexts.rb +4 -0
- data/lib/sequel/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa9e8681d693c24d9f5dc7f6369be50dfcbf73c648402ec486e9ba69d5792f76
|
|
4
|
+
data.tar.gz: 7de020c93e5388ea9d58bd6e4d601bd06b240684e4c9001985549fd3b1c2d665
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34030c2c1a045fa5fc36a6a36e1414960315469d415be97c3734fea182e63224c86c0882fa1958d5549f77c420879f6402af61a1756a4b7575a315ceb9c67f11
|
|
7
|
+
data.tar.gz: ea08def92c00668ca1898eac5a3dd37b50aeb1f81379e7c3ab19544e01cc5ef0c0c54786fc2a76f885e14b71534a3895451f33891cb374b3a3f10c08b0244d8a
|
|
@@ -46,8 +46,10 @@ module Sequel
|
|
|
46
46
|
# Initialize the data structures used by this extension.
|
|
47
47
|
def self.extended(pool)
|
|
48
48
|
case pool.pool_type
|
|
49
|
-
when :
|
|
50
|
-
|
|
49
|
+
when :threaded, :sharded_threaded, :timed_queue, :sharded_timed_queue
|
|
50
|
+
nil
|
|
51
|
+
else
|
|
52
|
+
raise Error, "cannot load connection_expiration extension if using single or sharded_single connection pool (or other unsupported connection pool)"
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
pool.instance_exec do
|
|
@@ -55,6 +57,39 @@ module Sequel
|
|
|
55
57
|
@connection_expiration_timestamps ||= {}
|
|
56
58
|
@connection_expiration_timeout ||= 14400
|
|
57
59
|
@connection_expiration_random_delay ||= 0
|
|
60
|
+
|
|
61
|
+
# Record an expiration timestamp for any connections that already
|
|
62
|
+
# exist in the pool, so that a connection opened before the extension
|
|
63
|
+
# was loaded (e.g. via Sequel.connect) will eventually be expired.
|
|
64
|
+
register = method(:register_connection_expiration_time)
|
|
65
|
+
|
|
66
|
+
case pool_type
|
|
67
|
+
when :timed_queue, :sharded_timed_queue
|
|
68
|
+
register_queued_connections = lambda do |queue|
|
|
69
|
+
conns = []
|
|
70
|
+
while conn = queue.pop(timeout: 0)
|
|
71
|
+
conns << conn
|
|
72
|
+
end
|
|
73
|
+
conns.each do |conn|
|
|
74
|
+
queue.push(register_connection_expiration_time(conn))
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
case pool_type
|
|
80
|
+
when :threaded
|
|
81
|
+
@available_connections.each(®ister)
|
|
82
|
+
@allocated.each_value(®ister)
|
|
83
|
+
when :sharded_threaded
|
|
84
|
+
@available_connections.each_value{|conns| conns.each(®ister)}
|
|
85
|
+
@allocated.each_value{|threads| threads.each_value(®ister)}
|
|
86
|
+
when :timed_queue
|
|
87
|
+
register_queued_connections.call(@queue)
|
|
88
|
+
@allocated.each_value(®ister)
|
|
89
|
+
else # when :sharded_timed_queue
|
|
90
|
+
@queues.each_value(®ister_queued_connections)
|
|
91
|
+
@allocated.each_value{|threads| threads.each_value(®ister)}
|
|
92
|
+
end
|
|
58
93
|
end
|
|
59
94
|
end
|
|
60
95
|
end
|
|
@@ -69,8 +104,12 @@ module Sequel
|
|
|
69
104
|
|
|
70
105
|
# Record the time the connection was created.
|
|
71
106
|
def make_new(*)
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
register_connection_expiration_time(super)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Record an expiration entry for a connection, returns the connection.
|
|
111
|
+
def register_connection_expiration_time(conn)
|
|
112
|
+
@connection_expiration_timestamps[conn] ||= [Sequel.start_timer, @connection_expiration_timeout + (rand * @connection_expiration_random_delay)].freeze
|
|
74
113
|
conn
|
|
75
114
|
end
|
|
76
115
|
|
|
@@ -112,6 +112,8 @@ module Sequel
|
|
|
112
112
|
# the dataset unmodified if no SQL limit strategy is needed.
|
|
113
113
|
def apply_eager_graph_limit_strategy(strategy, ds)
|
|
114
114
|
case strategy
|
|
115
|
+
when :lateral_subquery
|
|
116
|
+
apply_lateral_subquery_eager_graph_limit_strategy(ds)
|
|
115
117
|
when :distinct_on
|
|
116
118
|
apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order]))
|
|
117
119
|
when :window_function
|
|
@@ -298,7 +300,12 @@ module Sequel
|
|
|
298
300
|
# Correlated subqueries are not supported for regular eager loading
|
|
299
301
|
strategy = :ruby if strategy == :correlated_subquery
|
|
300
302
|
strategy = nil if strategy == :ruby && assign_singular?
|
|
301
|
-
|
|
303
|
+
|
|
304
|
+
objects = if strategy == :lateral_subquery
|
|
305
|
+
apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
|
|
306
|
+
else
|
|
307
|
+
apply_eager_limit_strategy(ds, strategy, eager_limit).all
|
|
308
|
+
end
|
|
302
309
|
|
|
303
310
|
if strategy == :window_function
|
|
304
311
|
delete_rn = ds.row_number_column
|
|
@@ -366,7 +373,12 @@ module Sequel
|
|
|
366
373
|
# filtered. Works by using a subquery to test that the objects passed
|
|
367
374
|
# also meet the association filter criteria.
|
|
368
375
|
def filter_by_associations_conditions_expression(obj)
|
|
369
|
-
ds =
|
|
376
|
+
ds = if filter_by_associations_limit_strategy == :lateral_subquery
|
|
377
|
+
apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
|
|
378
|
+
else
|
|
379
|
+
filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
|
|
380
|
+
end
|
|
381
|
+
|
|
370
382
|
{filter_by_associations_conditions_key=>ds}
|
|
371
383
|
end
|
|
372
384
|
|
|
@@ -754,6 +766,61 @@ module Sequel
|
|
|
754
766
|
end
|
|
755
767
|
end
|
|
756
768
|
|
|
769
|
+
def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
|
|
770
|
+
ds.
|
|
771
|
+
where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys))).
|
|
772
|
+
limit(*limit_and_offset).
|
|
773
|
+
lateral
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
|
|
777
|
+
table_name = self[:model].table_name
|
|
778
|
+
associated_table_name = associated_class.table_name
|
|
779
|
+
|
|
780
|
+
associated_class.
|
|
781
|
+
from(table_name).
|
|
782
|
+
select_all(associated_table_name).
|
|
783
|
+
join(lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset).as(associated_table_name), true).
|
|
784
|
+
order(*self[:order])
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
# Return an expression to filter the filter by associations dataset to only
|
|
788
|
+
# rows related to given objects.
|
|
789
|
+
def _lateral_subquery_filter_limit_strategy_conditions(obj, key, value_method, value_column)
|
|
790
|
+
value = case obj
|
|
791
|
+
when Array
|
|
792
|
+
if key.is_a?(Array)
|
|
793
|
+
key_methods = Array(value_method)
|
|
794
|
+
obj.map{|o| key_methods.map{|meth| o.send(meth)}}
|
|
795
|
+
else
|
|
796
|
+
obj.map{|o| o.send(value_method)}
|
|
797
|
+
end
|
|
798
|
+
when Sequel::Dataset
|
|
799
|
+
obj.select(*Array(qualify(associated_class.table_name, value_column)))
|
|
800
|
+
else
|
|
801
|
+
if key.is_a?(Array)
|
|
802
|
+
return Array(key).zip(Array(value_method).map{|meth| obj.send(meth)})
|
|
803
|
+
else
|
|
804
|
+
obj.send(value_method)
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
{key => value}
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj)
|
|
812
|
+
lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds.
|
|
813
|
+
select(*qualify(associated_class.table_name, associated_class.primary_key)).
|
|
814
|
+
limit(*limit_and_offset).
|
|
815
|
+
lateral)
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
def apply_lateral_subquery_filter_limit_strategy(ds, obj)
|
|
819
|
+
lateral_subquery_filter_limit_strategy_filter_dataset(self[:model].
|
|
820
|
+
select(*lateral_subquery_filter_limit_strategy_lateral_dataset_select).
|
|
821
|
+
join(lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj).as(associated_class.table_name), filter_by_associations_conditions_subquery_conditions(obj)), obj)
|
|
822
|
+
end
|
|
823
|
+
|
|
757
824
|
# Whether to limit the associated dataset to a single row.
|
|
758
825
|
def limit_to_single_row?
|
|
759
826
|
!returns_array?
|
|
@@ -776,7 +843,12 @@ module Sequel
|
|
|
776
843
|
end
|
|
777
844
|
end
|
|
778
845
|
|
|
779
|
-
|
|
846
|
+
strategy = eager_limit_strategy
|
|
847
|
+
if strategy == :lateral_subquery
|
|
848
|
+
apply_lateral_subquery_eager_limit_strategy(ds, arg, limit_and_offset)
|
|
849
|
+
else
|
|
850
|
+
apply_eager_limit_strategy(ds.where(predicate_key=>arg), strategy)
|
|
851
|
+
end
|
|
780
852
|
end
|
|
781
853
|
end
|
|
782
854
|
end
|
|
@@ -1018,7 +1090,7 @@ module Sequel
|
|
|
1018
1090
|
class OneToManyAssociationReflection < AssociationReflection
|
|
1019
1091
|
ASSOCIATION_TYPES[:one_to_many] = self
|
|
1020
1092
|
|
|
1021
|
-
# Support a
|
|
1093
|
+
# Support a lateral_subquery and correlated_subquery limit strategy when using eager_graph.
|
|
1022
1094
|
def apply_eager_graph_limit_strategy(strategy, ds)
|
|
1023
1095
|
case strategy
|
|
1024
1096
|
when :correlated_subquery
|
|
@@ -1118,6 +1190,54 @@ module Sequel
|
|
|
1118
1190
|
ds.where(qualify(table_alias, primary_key)=>cs)
|
|
1119
1191
|
end
|
|
1120
1192
|
|
|
1193
|
+
# Use a LATERAL subquery to limit the dataset. Note that this will not
|
|
1194
|
+
# work correctly if the associated dataset uses qualified identifers in the WHERE clause,
|
|
1195
|
+
# as they would reference the containing query instead of the subquery.
|
|
1196
|
+
#
|
|
1197
|
+
# This does not contain the conditions that are necessary to join to the
|
|
1198
|
+
# query, since the necessary qualifier is not passed as an argument.
|
|
1199
|
+
def apply_lateral_subquery_eager_graph_limit_strategy(ds)
|
|
1200
|
+
table_name = ds.first_source_alias
|
|
1201
|
+
qualifier = ds.opts[:eager_options][:implicit_qualifier]
|
|
1202
|
+
graph_conditions = self[:_graph_conditions]
|
|
1203
|
+
|
|
1204
|
+
unless Sequel.condition_specifier?(graph_conditions)
|
|
1205
|
+
raise Error, "lateral_subquery eager graph limit strategy only supported when graph conditions are a hash or array of pairs"
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
ds.
|
|
1209
|
+
limit(*limit_and_offset).
|
|
1210
|
+
order(*self[:order]).
|
|
1211
|
+
where(graph_conditions.map{|k, v| [qualify(table_name, k), qualify(qualifier, v)]}).
|
|
1212
|
+
lateral
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
# Avoid setting duplicate predicate condition when using the lateral subquery
|
|
1216
|
+
# eager limit strategy.
|
|
1217
|
+
def eager_loading_set_predicate_condition(ds, eo)
|
|
1218
|
+
eager_limit_strategy == :lateral_subquery ? ds : super
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
|
|
1222
|
+
super.where(qualify(self[:model].table_name, self[:primary_key]) => ids)
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
def lateral_subquery_filter_limit_strategy_conditions(obj)
|
|
1226
|
+
_lateral_subquery_filter_limit_strategy_conditions(obj, filter_by_associations_conditions_key, self[:key_method], self[:key])
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1229
|
+
def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
|
|
1230
|
+
ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
|
|
1234
|
+
ds.where(lateral_subquery_filter_limit_strategy_conditions(obj))
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
def lateral_subquery_filter_limit_strategy_lateral_dataset_select
|
|
1238
|
+
qualified_primary_key
|
|
1239
|
+
end
|
|
1240
|
+
|
|
1121
1241
|
# Support correlated subquery strategy when filtering by limited associations.
|
|
1122
1242
|
def apply_filter_by_associations_limit_strategy(ds)
|
|
1123
1243
|
case filter_by_associations_limit_strategy
|
|
@@ -1432,6 +1552,52 @@ module Sequel
|
|
|
1432
1552
|
end
|
|
1433
1553
|
end
|
|
1434
1554
|
|
|
1555
|
+
def lateral_subquery_filter_limit_strategy_conditions(obj)
|
|
1556
|
+
_lateral_subquery_filter_limit_strategy_conditions(obj, lateral_subquery_filter_limit_strategy_conditions_key, right_primary_key_method, right_primary_key)
|
|
1557
|
+
end
|
|
1558
|
+
|
|
1559
|
+
def lateral_subquery_filter_limit_strategy_conditions_key
|
|
1560
|
+
self[:right_key]
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
|
|
1564
|
+
ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
|
|
1565
|
+
end
|
|
1566
|
+
|
|
1567
|
+
def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
|
|
1568
|
+
ds.where(filter_by_associations_conditions_key => ds.db.from(self[:join_table]).
|
|
1569
|
+
select(*self[:left_key]).
|
|
1570
|
+
where(lateral_subquery_filter_limit_strategy_conditions(obj)))
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
def lateral_subquery_filter_limit_strategy_lateral_dataset_select
|
|
1574
|
+
qualify(self[:model].table_name, self[:left_primary_key])
|
|
1575
|
+
end
|
|
1576
|
+
|
|
1577
|
+
def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
|
|
1578
|
+
ds = super
|
|
1579
|
+
select = ds.opts[:select].dup
|
|
1580
|
+
left_key_alias = self[:left_key_alias]
|
|
1581
|
+
select.pop while (s = select.last).is_a?(Sequel::SQL::AliasedExpression) && (left_key_alias.is_a?(Array) ? left_key_alias.include?(s.alias) : s.alias == left_key_alias)
|
|
1582
|
+
ds = ds.clone(:select=>select)
|
|
1583
|
+
end
|
|
1584
|
+
|
|
1585
|
+
def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
|
|
1586
|
+
table_name = self[:model].table_name
|
|
1587
|
+
super.
|
|
1588
|
+
select_all(associated_class.table_name).
|
|
1589
|
+
select_append(*qualify(table_name, self[:left_primary_keys]).zip(Array(self[:left_key_alias])).map{|id, aliaz| Sequel.as(id, aliaz)}).
|
|
1590
|
+
where(qualify(table_name, self[:left_primary_key]) => ids)
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
def apply_lateral_subquery_eager_graph_limit_strategy(ds)
|
|
1594
|
+
ds.
|
|
1595
|
+
limit(*limit_and_offset).
|
|
1596
|
+
where(qualify(join_table_alias, self[:left_keys]).zip(qualify(ds.opts[:eager_options][:implicit_qualifier], self[:left_primary_key_columns]))).
|
|
1597
|
+
order(*self[:order]).
|
|
1598
|
+
lateral
|
|
1599
|
+
end
|
|
1600
|
+
|
|
1435
1601
|
# Use the right_keys from the eager loading options if
|
|
1436
1602
|
# using a separate query per table.
|
|
1437
1603
|
def eager_loading_set_predicate_condition(ds, eo)
|
|
@@ -2008,6 +2174,7 @@ module Sequel
|
|
|
2008
2174
|
associate(:one_to_one, name, opts, &block)
|
|
2009
2175
|
end
|
|
2010
2176
|
|
|
2177
|
+
Plugins.model_instance_variables(self, :@associations, :@set_associated_object_if_same)
|
|
2011
2178
|
Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_association_options=>:dup, :@default_association_type_options=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
|
|
2012
2179
|
Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
|
|
2013
2180
|
|
|
@@ -2413,9 +2580,14 @@ module Sequel
|
|
|
2413
2580
|
conditions = opts[:graph_conditions]
|
|
2414
2581
|
opts[:cartesian_product_number] ||= one_to_one ? 0 : 1
|
|
2415
2582
|
graph_block = opts[:graph_block]
|
|
2583
|
+
graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
|
|
2416
2584
|
opts[:eager_grapher] ||= proc do |eo|
|
|
2417
2585
|
ds = eo[:self]
|
|
2418
|
-
|
|
2586
|
+
graph_limit_strategy = eo[:limit_strategy]
|
|
2587
|
+
egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
|
|
2588
|
+
graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
|
|
2589
|
+
|
|
2590
|
+
ds = ds.graph(egds, graph_conditions_true || graph_conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
|
|
2419
2591
|
# We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
|
|
2420
2592
|
ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
|
|
2421
2593
|
ds
|
|
@@ -2498,6 +2670,9 @@ module Sequel
|
|
|
2498
2670
|
# Return dataset to graph into given the association reflection, applying the :callback option if set.
|
|
2499
2671
|
def eager_graph_dataset(opts, eager_options)
|
|
2500
2672
|
ds = opts.associated_class.dataset
|
|
2673
|
+
if eager_options[:limit_strategy] == :lateral_subquery
|
|
2674
|
+
ds = ds.clone(:eager_options=>eager_options)
|
|
2675
|
+
end
|
|
2501
2676
|
if opts[:graph_use_association_block] && (b = opts[:block])
|
|
2502
2677
|
ds = b.call(ds)
|
|
2503
2678
|
end
|
data/lib/sequel/model/base.rb
CHANGED
|
@@ -84,6 +84,10 @@ module Sequel
|
|
|
84
84
|
# underlying table doesn't exist.
|
|
85
85
|
attr_accessor :require_valid_table
|
|
86
86
|
|
|
87
|
+
# Whether the model uses a shape friendly design (initializing all potentially
|
|
88
|
+
# used instance variables to nil).
|
|
89
|
+
attr_reader :shape_friendly
|
|
90
|
+
|
|
87
91
|
# Should be the literal primary key column name if this Model's table has a simple primary key, or
|
|
88
92
|
# nil if the model has a compound primary key or no primary key.
|
|
89
93
|
attr_reader :simple_pk
|
|
@@ -221,9 +225,7 @@ module Sequel
|
|
|
221
225
|
# Requires that values be a hash where all keys are symbols. It
|
|
222
226
|
# probably should not be used by external code.
|
|
223
227
|
def call(values)
|
|
224
|
-
|
|
225
|
-
o.instance_variable_set(:@values, values)
|
|
226
|
-
o
|
|
228
|
+
allocate.initialize_from_db(values)
|
|
227
229
|
end
|
|
228
230
|
|
|
229
231
|
# Clear the setter_methods cache
|
|
@@ -501,7 +503,13 @@ module Sequel
|
|
|
501
503
|
unless @plugins.include?(m)
|
|
502
504
|
@plugins << m
|
|
503
505
|
m.apply(self, *args, &block) if m.respond_to?(:apply)
|
|
504
|
-
|
|
506
|
+
if m.const_defined?(:ClassMethods, false)
|
|
507
|
+
class_methods = m::ClassMethods
|
|
508
|
+
extend(class_methods)
|
|
509
|
+
if class_methods.private_method_defined?(:each_model_instance_variable)
|
|
510
|
+
def_initialize_nil_instance_variables
|
|
511
|
+
end
|
|
512
|
+
end
|
|
505
513
|
include(m::InstanceMethods) if m.const_defined?(:InstanceMethods, false)
|
|
506
514
|
if m.const_defined?(:DatasetMethods, false)
|
|
507
515
|
dataset_extend(m::DatasetMethods, :create_class_methods=>false)
|
|
@@ -631,6 +639,13 @@ module Sequel
|
|
|
631
639
|
self
|
|
632
640
|
end
|
|
633
641
|
|
|
642
|
+
# Set instance variables used by instances of this model class.
|
|
643
|
+
# Only has an effect if shape_friendly is true.
|
|
644
|
+
def set_model_instance_variables(*ivs)
|
|
645
|
+
Plugins.model_instance_variables(singleton_class, *ivs)
|
|
646
|
+
def_initialize_nil_instance_variables
|
|
647
|
+
end
|
|
648
|
+
|
|
634
649
|
# Sets the primary key for this model. You can use either a regular
|
|
635
650
|
# or a composite primary key. To not use a primary key, set to nil
|
|
636
651
|
# or use +no_primary_key+. On most adapters, Sequel can automatically
|
|
@@ -665,6 +680,12 @@ module Sequel
|
|
|
665
680
|
@setter_methods || (@setter_methods = get_setter_methods)
|
|
666
681
|
end
|
|
667
682
|
|
|
683
|
+
# Set whether the model should be shape friendly.
|
|
684
|
+
def shape_friendly=(v)
|
|
685
|
+
@shape_friendly = v
|
|
686
|
+
def_initialize_nil_instance_variables
|
|
687
|
+
end
|
|
688
|
+
|
|
668
689
|
# Returns name of primary table for the dataset. If the table for the dataset
|
|
669
690
|
# is aliased, returns the aliased name.
|
|
670
691
|
#
|
|
@@ -814,6 +835,60 @@ END
|
|
|
814
835
|
mod.send(:alias_method, meth, meth)
|
|
815
836
|
end
|
|
816
837
|
|
|
838
|
+
# Defines the private _initialize_nil_instance_variables method.
|
|
839
|
+
# If shape_friendly is true, defines a method that initials the
|
|
840
|
+
# model's instance variables to nil. If shape_friendly is not
|
|
841
|
+
# true, does nothing.
|
|
842
|
+
def def_initialize_nil_instance_variables
|
|
843
|
+
if @shape_friendly
|
|
844
|
+
ivs = []
|
|
845
|
+
each_model_instance_variable do |iv|
|
|
846
|
+
unless iv.match(/\A@[a-z_][a-z0-9_]*\z/)
|
|
847
|
+
raise Error, "invalid model instance variable used"
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
ivs << iv
|
|
851
|
+
end
|
|
852
|
+
ivs.uniq!
|
|
853
|
+
ivs = ivs.reverse.join(" = ")
|
|
854
|
+
new_method_content = "#{ivs} = nil"
|
|
855
|
+
new_from_db_method_content = "#{ivs} = @new = @modified = nil"
|
|
856
|
+
end
|
|
857
|
+
|
|
858
|
+
class_eval(<<-RUBY, __FILE__, __LINE__+1)
|
|
859
|
+
def _initialize_nil_instance_variables
|
|
860
|
+
#{new_method_content}
|
|
861
|
+
end
|
|
862
|
+
def _initialize_from_db_nil_instance_variables
|
|
863
|
+
#{new_from_db_method_content}
|
|
864
|
+
end
|
|
865
|
+
RUBY
|
|
866
|
+
private :_initialize_nil_instance_variables, :_initialize_from_db_nil_instance_variables
|
|
867
|
+
alias_method :_initialize_nil_instance_variables, :_initialize_nil_instance_variables
|
|
868
|
+
alias_method :_initialize_from_db_nil_instance_variables, :_initialize_from_db_nil_instance_variables
|
|
869
|
+
nil
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# Yield the default model instance variables. Designed only for
|
|
873
|
+
# use by def_initialize_nil_instance_variables.
|
|
874
|
+
def each_model_instance_variable
|
|
875
|
+
[
|
|
876
|
+
:@changed_columns,
|
|
877
|
+
:@errors,
|
|
878
|
+
:@raise_on_save_failure,
|
|
879
|
+
:@raise_on_typecast_failure,
|
|
880
|
+
:@require_modification,
|
|
881
|
+
:@server,
|
|
882
|
+
:@singleton_setter_added,
|
|
883
|
+
:@skip_validation_on_next_save,
|
|
884
|
+
:@strict_param_setting,
|
|
885
|
+
:@this,
|
|
886
|
+
:@typecast_empty_string_to_nil,
|
|
887
|
+
:@typecast_on_assignment,
|
|
888
|
+
:@use_transactions,
|
|
889
|
+
].each{|iv| yield iv}
|
|
890
|
+
end
|
|
891
|
+
|
|
817
892
|
# Get the schema from the database, fall back on checking the columns
|
|
818
893
|
# via the database if that will return inaccurate results or if
|
|
819
894
|
# it raises an error.
|
|
@@ -937,6 +1012,7 @@ END
|
|
|
937
1012
|
:@require_valid_table=>nil,
|
|
938
1013
|
:@restrict_primary_key=>nil,
|
|
939
1014
|
:@setter_methods=>nil,
|
|
1015
|
+
:@shape_friendly=>nil,
|
|
940
1016
|
:@simple_pk=>nil,
|
|
941
1017
|
:@simple_table=>nil,
|
|
942
1018
|
:@strict_param_setting=>nil,
|
|
@@ -1094,7 +1170,7 @@ END
|
|
|
1094
1170
|
# standard attr_writer method for modifying that instance variable.
|
|
1095
1171
|
[:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting,
|
|
1096
1172
|
:raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions].each do |meth|
|
|
1097
|
-
class_eval("def #{meth};
|
|
1173
|
+
class_eval("def #{meth}; @#{meth}.nil? ? (frozen? ? self.class.#{meth} : (@#{meth} = self.class.#{meth})) : @#{meth} end", __FILE__, __LINE__)
|
|
1098
1174
|
attr_writer(meth)
|
|
1099
1175
|
end
|
|
1100
1176
|
|
|
@@ -1135,13 +1211,23 @@ END
|
|
|
1135
1211
|
# end
|
|
1136
1212
|
def initialize(values = OPTS)
|
|
1137
1213
|
@values = {}
|
|
1138
|
-
@new = true
|
|
1139
|
-
|
|
1214
|
+
@new = @modified = true
|
|
1215
|
+
_initialize_nil_instance_variables
|
|
1216
|
+
|
|
1140
1217
|
initialize_set(values)
|
|
1141
1218
|
_clear_changed_columns(:initialize)
|
|
1142
1219
|
yield self if defined?(yield)
|
|
1143
1220
|
end
|
|
1144
1221
|
|
|
1222
|
+
# Initialize a new record using values retrieved from a database.
|
|
1223
|
+
# This should not be called directly, only via Model.call.
|
|
1224
|
+
def initialize_from_db(values) # :nodoc:
|
|
1225
|
+
@values = values
|
|
1226
|
+
_initialize_from_db_nil_instance_variables
|
|
1227
|
+
|
|
1228
|
+
self
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1145
1231
|
# Returns value of the column's attribute.
|
|
1146
1232
|
#
|
|
1147
1233
|
# Artist[1][:id] #=> 1
|
|
@@ -1445,7 +1531,7 @@ END
|
|
|
1445
1531
|
# Artist.new.new? # => true
|
|
1446
1532
|
# Artist[1].new? # => false
|
|
1447
1533
|
def new?
|
|
1448
|
-
|
|
1534
|
+
@new || false
|
|
1449
1535
|
end
|
|
1450
1536
|
|
|
1451
1537
|
# Returns the primary key value identifying the model instance.
|
|
@@ -2337,6 +2423,7 @@ END
|
|
|
2337
2423
|
|
|
2338
2424
|
extend ClassMethods
|
|
2339
2425
|
plugin self
|
|
2426
|
+
def_initialize_nil_instance_variables
|
|
2340
2427
|
|
|
2341
2428
|
singleton_class.send(:undef_method, :dup, :clone, :initialize_copy)
|
|
2342
2429
|
# :nocov:
|
data/lib/sequel/model/plugins.rb
CHANGED
|
@@ -38,7 +38,8 @@ module Sequel
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
# Add method to +mod+ that overrides inherited_instance_variables to include the
|
|
41
|
-
# values in this hash.
|
|
41
|
+
# values in this hash. These affects how class instance variables will be treated
|
|
42
|
+
# during subclassing.
|
|
42
43
|
def self.inherited_instance_variables(mod, hash)
|
|
43
44
|
mod.send(:define_method, :inherited_instance_variables) do ||
|
|
44
45
|
super().merge!(hash)
|
|
@@ -46,6 +47,16 @@ module Sequel
|
|
|
46
47
|
mod.send(:private, :inherited_instance_variables)
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
# Sets the model instance variables used by the plugin. These instance variables
|
|
51
|
+
# will be initialized to nil for created model objects.
|
|
52
|
+
def self.model_instance_variables(mod, *ivs)
|
|
53
|
+
mod.send(:define_method, :each_model_instance_variable) do |&block|
|
|
54
|
+
super(&block)
|
|
55
|
+
ivs.each(&block)
|
|
56
|
+
end
|
|
57
|
+
mod.send(:private, :each_model_instance_variable)
|
|
58
|
+
end
|
|
59
|
+
|
|
49
60
|
# Add method to +mod+ that overrides set_dataset to call the method afterward.
|
|
50
61
|
def self.after_set_dataset(mod, meth)
|
|
51
62
|
mod.send(:define_method, :set_dataset) do |*a|
|
data/lib/sequel/model.rb
CHANGED
|
@@ -26,6 +26,10 @@ module Sequel
|
|
|
26
26
|
# # Make the Album instances record accessed columns
|
|
27
27
|
# Album.plugin :accessed_columns
|
|
28
28
|
module AccessedColumns
|
|
29
|
+
module ClassMethods
|
|
30
|
+
Plugins.model_instance_variables(self, :@accessed_columns)
|
|
31
|
+
end
|
|
32
|
+
|
|
29
33
|
module InstanceMethods
|
|
30
34
|
# Record the column access before retrieving the value.
|
|
31
35
|
def [](c)
|
|
@@ -29,6 +29,8 @@ module Sequel
|
|
|
29
29
|
module ClassMethods
|
|
30
30
|
include ::ActiveModel::Naming
|
|
31
31
|
|
|
32
|
+
Plugins.model_instance_variables(self, :@destroyed, :@rollback_checker)
|
|
33
|
+
|
|
32
34
|
# Cache model_name and to_partial path value before freezing.
|
|
33
35
|
def freeze
|
|
34
36
|
model_name
|
|
@@ -58,13 +60,10 @@ module Sequel
|
|
|
58
60
|
|
|
59
61
|
# False if the object is new? or has been destroyed, true otherwise.
|
|
60
62
|
def persisted?
|
|
61
|
-
return false if new?
|
|
62
|
-
return false if defined?(@destroyed)
|
|
63
|
+
return false if new? || @destroyed
|
|
63
64
|
|
|
64
|
-
if
|
|
65
|
-
|
|
66
|
-
return false
|
|
67
|
-
end
|
|
65
|
+
if @rollback_checker && @rollback_checker.call
|
|
66
|
+
return false
|
|
68
67
|
end
|
|
69
68
|
|
|
70
69
|
true
|
|
@@ -15,6 +15,10 @@ module Sequel
|
|
|
15
15
|
# # Make the Album class store the columns hash used for updating
|
|
16
16
|
# Album.plugin :columns_updated
|
|
17
17
|
module ColumnsUpdated
|
|
18
|
+
module ClassMethods
|
|
19
|
+
Plugins.model_instance_variables(self, :@columns_updated)
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
module InstanceMethods
|
|
19
23
|
private
|
|
20
24
|
|
|
@@ -82,8 +82,27 @@ module Sequel
|
|
|
82
82
|
# database supports window functions.
|
|
83
83
|
def associated(name)
|
|
84
84
|
raise Error, "unrecognized association name: #{name.inspect}" unless r = model.association_reflection(name)
|
|
85
|
-
|
|
85
|
+
klass = r.associated_class
|
|
86
86
|
sds = opts[:limit] ? self : unordered
|
|
87
|
+
|
|
88
|
+
if r.send(:filter_by_associations_limit_strategy) == :lateral_subquery
|
|
89
|
+
ds = r.send(:associated_eager_dataset)
|
|
90
|
+
|
|
91
|
+
case r[:type]
|
|
92
|
+
when :one_to_one, :one_to_many
|
|
93
|
+
sds = sds.select(*Array(r.qualified_primary_key))
|
|
94
|
+
else
|
|
95
|
+
sds = sds.select(*r[:left_primary_keys])
|
|
96
|
+
ds = ds.select_all(klass.table_name)
|
|
97
|
+
update_select = true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
ds = r.send(:apply_lateral_subquery_eager_limit_strategy, ds, sds, r.limit_and_offset)
|
|
101
|
+
ds = ds.clone(:select=>ds.opts[:select][0,1]) if update_select
|
|
102
|
+
return ds.clone(:eager=>nil, :eager_graph=>nil)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
ds = klass.dataset
|
|
87
106
|
ds = case r[:type]
|
|
88
107
|
when :many_to_one
|
|
89
108
|
ds.where(r.qualified_primary_key=>sds.select(*Array(r[:qualified_key])))
|
data/lib/sequel/plugins/dirty.rb
CHANGED
|
@@ -63,6 +63,10 @@ module Sequel
|
|
|
63
63
|
# # Make the Album class record previous values
|
|
64
64
|
# Album.plugin :dirty
|
|
65
65
|
module Dirty
|
|
66
|
+
module ClassMethods
|
|
67
|
+
Plugins.model_instance_variables(self, :@previous_changes, :@initial_values, :@missing_initial_values)
|
|
68
|
+
end
|
|
69
|
+
|
|
66
70
|
module InstanceMethods
|
|
67
71
|
# A hash of previous changes before the object was
|
|
68
72
|
# saved, in the same format as #column_changes.
|
|
@@ -42,6 +42,10 @@ module Sequel
|
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
module ClassMethods
|
|
46
|
+
Plugins.model_instance_variables(self, :@insert_conflict_opts)
|
|
47
|
+
end
|
|
48
|
+
|
|
45
49
|
module InstanceMethods
|
|
46
50
|
# Set the insert_conflict options to pass to the dataset when inserting.
|
|
47
51
|
def insert_conflict(opts=OPTS)
|
|
@@ -50,6 +50,10 @@ module Sequel
|
|
|
50
50
|
model.require_modification = true
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
module ClassMethods
|
|
54
|
+
Plugins.model_instance_variables(self, :@instance_filters)
|
|
55
|
+
end
|
|
56
|
+
|
|
53
57
|
module InstanceMethods
|
|
54
58
|
# Clear the instance filters after successfully destroying the object.
|
|
55
59
|
def after_destroy
|
|
@@ -28,6 +28,10 @@ module Sequel
|
|
|
28
28
|
# # Add the instance hook methods just to Album instances
|
|
29
29
|
# Album.plugin :instance_hooks
|
|
30
30
|
module InstanceHooks
|
|
31
|
+
module ClassMethods
|
|
32
|
+
Plugins.model_instance_variables(self, :@instance_hooks)
|
|
33
|
+
end
|
|
34
|
+
|
|
31
35
|
module InstanceMethods
|
|
32
36
|
Sequel::Model::HOOKS.each{|h| class_eval(<<-END , __FILE__, __LINE__+1)}
|
|
33
37
|
def #{h}_hook(&block)
|
|
@@ -146,6 +146,27 @@ module Sequel
|
|
|
146
146
|
ds
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
+
def lateral_subquery_filter_limit_strategy_conditions_key
|
|
150
|
+
qualify(reverse_edges.first[:table], reverse_edges.first[:left])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
|
|
154
|
+
ds.where(Array(qualify(edges.first[:table], edges.first[:right])).zip(Array(qualify(self[:model].table_name, edges.first[:left]))))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
|
|
158
|
+
first_edge, *remaining_edges = edges
|
|
159
|
+
filter_ds = ds.db.from(first_edge[:table]).
|
|
160
|
+
select(*qualify(first_edge[:table], first_edge[:right])).
|
|
161
|
+
where(lateral_subquery_filter_limit_strategy_conditions(obj))
|
|
162
|
+
|
|
163
|
+
remaining_edges.each do |edge|
|
|
164
|
+
filter_ds = filter_ds.join(edge[:table], Array(edge[:right]).zip(Array(edge[:left])))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
ds.where(qualify(self[:model].table_name, self[:left_primary_key]) => filter_ds)
|
|
168
|
+
end
|
|
169
|
+
|
|
149
170
|
# Make sure to use unique table aliases when lazy loading or eager loading
|
|
150
171
|
def calculate_reverse_edge_aliases(reverse_edges)
|
|
151
172
|
aliases = [associated_class.table_name]
|
|
@@ -34,6 +34,8 @@ module Sequel
|
|
|
34
34
|
# Album.plugin :modification_detection
|
|
35
35
|
module ModificationDetection
|
|
36
36
|
module ClassMethods
|
|
37
|
+
Plugins.model_instance_variables(self, :@values_hashes)
|
|
38
|
+
|
|
37
39
|
# Calculate the hashes for all of the column values, so that they
|
|
38
40
|
# can be compared later to determine if the column value has changed.
|
|
39
41
|
def call(_)
|
|
@@ -113,6 +113,8 @@ module Sequel
|
|
|
113
113
|
register_format(:json, Sequel.method(:object_to_json), Sequel.method(:parse_json))
|
|
114
114
|
|
|
115
115
|
module ClassMethods
|
|
116
|
+
Plugins.model_instance_variables(self, :@deserialized_values)
|
|
117
|
+
|
|
116
118
|
# A hash with column name symbols and callable values, with the value
|
|
117
119
|
# called to deserialize the column.
|
|
118
120
|
attr_reader :deserialization_map
|
|
@@ -30,6 +30,10 @@ module Sequel
|
|
|
30
30
|
def self.apply(model)
|
|
31
31
|
model.plugin :serialization
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
module ClassMethods
|
|
35
|
+
Plugins.model_instance_variables(self, :@original_deserialized_values)
|
|
36
|
+
end
|
|
33
37
|
|
|
34
38
|
module InstanceMethods
|
|
35
39
|
# Clear the cache of original deserialized values after saving so that it doesn't
|
|
@@ -36,6 +36,8 @@ module Sequel
|
|
|
36
36
|
# Album.plugin :split_values
|
|
37
37
|
module SplitValues
|
|
38
38
|
module ClassMethods
|
|
39
|
+
Plugins.model_instance_variables(self, :@noncolumn_values)
|
|
40
|
+
|
|
39
41
|
# Split the noncolumn values when creating a new object retrieved from
|
|
40
42
|
# the database.
|
|
41
43
|
def call(_)
|
|
@@ -124,6 +124,10 @@ module Sequel
|
|
|
124
124
|
# # Make the Album class use tactical eager loading
|
|
125
125
|
# Album.plugin :tactical_eager_loading
|
|
126
126
|
module TacticalEagerLoading
|
|
127
|
+
module ClassMethods
|
|
128
|
+
Plugins.model_instance_variables(self, :@retrieved_by, :@retrieved_with)
|
|
129
|
+
end
|
|
130
|
+
|
|
127
131
|
module InstanceMethods
|
|
128
132
|
# The dataset that retrieved this object, set if the object was
|
|
129
133
|
# reteived via Dataset#all.
|
|
@@ -22,6 +22,10 @@ module Sequel
|
|
|
22
22
|
# # Make the Album class support primary key updates
|
|
23
23
|
# Album.plugin :update_primary_key
|
|
24
24
|
module UpdatePrimaryKey
|
|
25
|
+
module ClassMethods
|
|
26
|
+
Plugins.model_instance_variables(self, :@pk_hash)
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
module InstanceMethods
|
|
26
30
|
# Clear the cached primary key.
|
|
27
31
|
def after_update
|
|
@@ -29,6 +29,10 @@ module Sequel
|
|
|
29
29
|
# of that model, you can no longer specify a validation context when
|
|
30
30
|
# validating the instance.
|
|
31
31
|
module ValidationContexts
|
|
32
|
+
module ClassMethods
|
|
33
|
+
Plugins.model_instance_variables(self, :@validation_context)
|
|
34
|
+
end
|
|
35
|
+
|
|
32
36
|
module InstanceMethods
|
|
33
37
|
# The validation context to use for the current validation.
|
|
34
38
|
# Set via the :validation_context option passed to save/valid?.
|
data/lib/sequel/version.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Sequel
|
|
|
6
6
|
|
|
7
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
|
8
8
|
# release, generally around once a month.
|
|
9
|
-
MINOR =
|
|
9
|
+
MINOR = 105
|
|
10
10
|
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
|
12
12
|
# releases that fix regressions from previous versions.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sequel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.105.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Evans
|
|
@@ -455,7 +455,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
455
455
|
- !ruby/object:Gem::Version
|
|
456
456
|
version: '0'
|
|
457
457
|
requirements: []
|
|
458
|
-
rubygems_version: 4.0.
|
|
458
|
+
rubygems_version: 4.0.10
|
|
459
459
|
specification_version: 4
|
|
460
460
|
summary: The Database Toolkit for Ruby
|
|
461
461
|
test_files: []
|