sequel 5.101.0 → 5.104.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/lib/sequel/adapters/jdbc/derby.rb +2 -0
  4. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  5. data/lib/sequel/adapters/postgres.rb +1 -1
  6. data/lib/sequel/adapters/shared/mssql.rb +3 -3
  7. data/lib/sequel/adapters/shared/mysql.rb +5 -4
  8. data/lib/sequel/adapters/shared/postgres.rb +16 -16
  9. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  10. data/lib/sequel/adapters/sqlite.rb +1 -1
  11. data/lib/sequel/adapters/tinytds.rb +1 -1
  12. data/lib/sequel/connection_pool/sharded_timed_queue.rb +36 -11
  13. data/lib/sequel/connection_pool/timed_queue.rb +27 -9
  14. data/lib/sequel/connection_pool.rb +6 -0
  15. data/lib/sequel/database/schema_generator.rb +36 -5
  16. data/lib/sequel/database/schema_methods.rb +1 -1
  17. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -0
  18. data/lib/sequel/dataset/prepared_statements.rb +7 -4
  19. data/lib/sequel/dataset/query.rb +6 -3
  20. data/lib/sequel/dataset/sql.rb +6 -1
  21. data/lib/sequel/extensions/connection_checkout_event_callback.rb +151 -0
  22. data/lib/sequel/extensions/connection_expiration.rb +1 -1
  23. data/lib/sequel/extensions/connection_validator.rb +1 -1
  24. data/lib/sequel/extensions/dataset_run.rb +2 -2
  25. data/lib/sequel/extensions/date_arithmetic.rb +6 -6
  26. data/lib/sequel/extensions/lit_require_frozen.rb +131 -0
  27. data/lib/sequel/extensions/migration.rb +14 -17
  28. data/lib/sequel/extensions/pg_enum.rb +1 -1
  29. data/lib/sequel/model/associations.rb +180 -6
  30. data/lib/sequel/plugins/dataset_associations.rb +20 -1
  31. data/lib/sequel/plugins/dirty.rb +5 -2
  32. data/lib/sequel/plugins/many_through_many.rb +21 -0
  33. data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
  34. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1 -1
  35. data/lib/sequel/plugins/serialization.rb +10 -1
  36. data/lib/sequel/version.rb +1 -1
  37. metadata +3 -1
@@ -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
- objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
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
@@ -320,7 +327,7 @@ module Sequel
320
327
  end
321
328
  loader.append_sql(sql, *k)
322
329
  end
323
- objects.concat(ds.with_sql(sql).to_a)
330
+ objects.concat(ds.with_sql(sql.freeze).to_a)
324
331
  end
325
332
  ds = ds.eager(cascade) if cascade
326
333
  ds.send(:post_load, objects)
@@ -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 = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
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
- apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
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 correlated subquery limit strategy when using eager_graph.
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)
@@ -2413,9 +2579,14 @@ module Sequel
2413
2579
  conditions = opts[:graph_conditions]
2414
2580
  opts[:cartesian_product_number] ||= one_to_one ? 0 : 1
2415
2581
  graph_block = opts[:graph_block]
2582
+ graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
2416
2583
  opts[:eager_grapher] ||= proc do |eo|
2417
2584
  ds = eo[:self]
2418
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
2585
+ graph_limit_strategy = eo[:limit_strategy]
2586
+ egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
2587
+ graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
2588
+
2589
+ 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
2590
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
2420
2591
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
2421
2592
  ds
@@ -2498,6 +2669,9 @@ module Sequel
2498
2669
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
2499
2670
  def eager_graph_dataset(opts, eager_options)
2500
2671
  ds = opts.associated_class.dataset
2672
+ if eager_options[:limit_strategy] == :lateral_subquery
2673
+ ds = ds.clone(:eager_options=>eager_options)
2674
+ end
2501
2675
  if opts[:graph_use_association_block] && (b = opts[:block])
2502
2676
  ds = b.call(ds)
2503
2677
  end
@@ -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
- ds = r.associated_class.dataset
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])))
@@ -234,8 +234,11 @@ module Sequel
234
234
  iv.delete(column)
235
235
  end
236
236
  else
237
- check_missing_initial_value(column)
238
- iv[column] = get_column_value(column)
237
+ if db_schema[column]
238
+ check_missing_initial_value(column)
239
+ iv[column] = get_column_value(column)
240
+ end
241
+
239
242
  super
240
243
  end
241
244
  end
@@ -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]
@@ -55,7 +55,7 @@ module Sequel
55
55
  def _update_without_checking(columns)
56
56
  ds = _update_dataset
57
57
  lc = model.lock_column
58
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
58
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
59
59
  values[lc] = rows.first[lc] unless rows.empty?
60
60
  rows.length
61
61
  end
@@ -99,7 +99,7 @@ module Sequel
99
99
  # Add an RETURNING clause to fetch the updated xmin when updating the row.
100
100
  def _update_without_checking(columns)
101
101
  ds = _update_dataset
102
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
102
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
103
103
  values[:xmin] = rows.first[:xmin] unless rows.empty?
104
104
  rows.length
105
105
  end
@@ -30,6 +30,15 @@ module Sequel
30
30
  # Otherwise, it is possible that the default column accessors will take
31
31
  # precedence.
32
32
  #
33
+ # Note that use of an unsafe serialization method can result in an attack vector
34
+ # (potentially allowing remote code execution) if an attacker has the ability to
35
+ # store data directly in the underlying column. This would affect the marshal
36
+ # serialization format, and on older versions of Ruby, potentially the yaml and
37
+ # json serialization formats as well. It can also affect custom formats. You
38
+ # should ensure that attackers do not have access to store data directly in the
39
+ # underlying column when using this plugin (especially when using an unsafe
40
+ # serialization method).
41
+ #
33
42
  # == Example
34
43
  #
35
44
  # # Require json if you plan to use it, as the plugin doesn't require it for you.
@@ -97,7 +106,7 @@ module Sequel
97
106
  register_format(:marshal, lambda{|v| [Marshal.dump(v)].pack('m')},
98
107
  lambda do |v|
99
108
  # Handle unpacked marshalled data for backwards compat
100
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
109
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
101
110
  Marshal.load(v)
102
111
  end)
103
112
  register_format(:yaml, :to_yaml.to_proc, lambda{|s| YAML.load(s)})
@@ -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 = 101
9
+ MINOR = 104
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.101.0
4
+ version: 5.104.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -212,6 +212,7 @@ files:
212
212
  - lib/sequel/extensions/blank.rb
213
213
  - lib/sequel/extensions/caller_logging.rb
214
214
  - lib/sequel/extensions/columns_introspection.rb
215
+ - lib/sequel/extensions/connection_checkout_event_callback.rb
215
216
  - lib/sequel/extensions/connection_expiration.rb
216
217
  - lib/sequel/extensions/connection_validator.rb
217
218
  - lib/sequel/extensions/constant_sql_override.rb
@@ -240,6 +241,7 @@ files:
240
241
  - lib/sequel/extensions/inflector.rb
241
242
  - lib/sequel/extensions/integer64.rb
242
243
  - lib/sequel/extensions/is_distinct_from.rb
244
+ - lib/sequel/extensions/lit_require_frozen.rb
243
245
  - lib/sequel/extensions/looser_typecasting.rb
244
246
  - lib/sequel/extensions/migration.rb
245
247
  - lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb