sequel 4.43.0 → 4.44.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +40 -0
  3. data/doc/active_record.rdoc +2 -2
  4. data/doc/code_order.rdoc +15 -0
  5. data/doc/dataset_filtering.rdoc +1 -1
  6. data/doc/model_dataset_method_design.rdoc +132 -0
  7. data/doc/opening_databases.rdoc +2 -2
  8. data/doc/release_notes/4.44.0.txt +125 -0
  9. data/lib/sequel/adapters/jdbc.rb +5 -1
  10. data/lib/sequel/adapters/jdbc/as400.rb +1 -1
  11. data/lib/sequel/adapters/postgres.rb +23 -14
  12. data/lib/sequel/core.rb +1 -1
  13. data/lib/sequel/database/schema_generator.rb +27 -0
  14. data/lib/sequel/dataset/actions.rb +1 -1
  15. data/lib/sequel/dataset/query.rb +5 -1
  16. data/lib/sequel/extensions/eval_inspect.rb +4 -4
  17. data/lib/sequel/extensions/implicit_subquery.rb +48 -0
  18. data/lib/sequel/extensions/to_dot.rb +1 -1
  19. data/lib/sequel/model.rb +3 -5
  20. data/lib/sequel/model/associations.rb +107 -4
  21. data/lib/sequel/model/base.rb +98 -12
  22. data/lib/sequel/model/dataset_module.rb +1 -1
  23. data/lib/sequel/plugins/active_model.rb +11 -3
  24. data/lib/sequel/plugins/association_dependencies.rb +7 -0
  25. data/lib/sequel/plugins/auto_validations.rb +10 -0
  26. data/lib/sequel/plugins/blacklist_security.rb +13 -4
  27. data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
  28. data/lib/sequel/plugins/column_conflicts.rb +8 -0
  29. data/lib/sequel/plugins/composition.rb +12 -2
  30. data/lib/sequel/plugins/constraint_validations.rb +12 -0
  31. data/lib/sequel/plugins/csv_serializer.rb +9 -0
  32. data/lib/sequel/plugins/defaults_setter.rb +6 -0
  33. data/lib/sequel/plugins/force_encoding.rb +4 -3
  34. data/lib/sequel/plugins/hook_class_methods.rb +6 -0
  35. data/lib/sequel/plugins/input_transformer.rb +9 -0
  36. data/lib/sequel/plugins/insert_returning_select.rb +8 -0
  37. data/lib/sequel/plugins/instance_hooks.rb +4 -3
  38. data/lib/sequel/plugins/json_serializer.rb +9 -0
  39. data/lib/sequel/plugins/lazy_attributes.rb +7 -0
  40. data/lib/sequel/plugins/many_through_many.rb +13 -2
  41. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  42. data/lib/sequel/plugins/pg_array_associations.rb +18 -2
  43. data/lib/sequel/plugins/pg_row.rb +3 -3
  44. data/lib/sequel/plugins/pg_typecast_on_load.rb +7 -0
  45. data/lib/sequel/plugins/prepared_statements.rb +2 -2
  46. data/lib/sequel/plugins/prepared_statements_safe.rb +7 -0
  47. data/lib/sequel/plugins/serialization.rb +9 -0
  48. data/lib/sequel/plugins/single_table_inheritance.rb +13 -1
  49. data/lib/sequel/plugins/subclasses.rb +15 -1
  50. data/lib/sequel/plugins/touch.rb +7 -0
  51. data/lib/sequel/plugins/tree.rb +7 -0
  52. data/lib/sequel/plugins/typecast_on_load.rb +7 -0
  53. data/lib/sequel/plugins/update_refresh.rb +24 -13
  54. data/lib/sequel/plugins/validation_class_methods.rb +13 -0
  55. data/lib/sequel/sql.rb +28 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/postgres_spec.rb +18 -15
  58. data/spec/core/dataset_spec.rb +5 -0
  59. data/spec/core/expression_filters_spec.rb +33 -0
  60. data/spec/extensions/active_model_spec.rb +15 -1
  61. data/spec/extensions/association_dependencies_spec.rb +8 -0
  62. data/spec/extensions/auto_validations_spec.rb +8 -0
  63. data/spec/extensions/blacklist_security_spec.rb +6 -0
  64. data/spec/extensions/class_table_inheritance_spec.rb +9 -0
  65. data/spec/extensions/column_conflicts_spec.rb +6 -0
  66. data/spec/extensions/composition_spec.rb +8 -0
  67. data/spec/extensions/constraint_validations_plugin_spec.rb +12 -0
  68. data/spec/extensions/csv_serializer_spec.rb +7 -0
  69. data/spec/extensions/defaults_setter_spec.rb +7 -0
  70. data/spec/extensions/force_encoding_spec.rb +14 -0
  71. data/spec/extensions/hook_class_methods_spec.rb +10 -0
  72. data/spec/extensions/implicit_subquery_spec.rb +60 -0
  73. data/spec/extensions/input_transformer_spec.rb +10 -0
  74. data/spec/extensions/insert_returning_select_spec.rb +6 -0
  75. data/spec/extensions/json_serializer_spec.rb +7 -0
  76. data/spec/extensions/lazy_attributes_spec.rb +6 -0
  77. data/spec/extensions/many_through_many_spec.rb +44 -0
  78. data/spec/extensions/nested_attributes_spec.rb +5 -0
  79. data/spec/extensions/pg_array_associations_spec.rb +46 -0
  80. data/spec/extensions/pg_typecast_on_load_spec.rb +5 -0
  81. data/spec/extensions/prepared_statements_safe_spec.rb +5 -0
  82. data/spec/extensions/serialization_spec.rb +7 -0
  83. data/spec/extensions/single_table_inheritance_spec.rb +19 -2
  84. data/spec/extensions/subclasses_spec.rb +13 -0
  85. data/spec/extensions/to_dot_spec.rb +1 -1
  86. data/spec/extensions/touch_spec.rb +6 -0
  87. data/spec/extensions/tree_spec.rb +6 -0
  88. data/spec/extensions/typecast_on_load_spec.rb +6 -0
  89. data/spec/extensions/update_refresh_spec.rb +7 -1
  90. data/spec/extensions/validation_class_methods_spec.rb +13 -0
  91. data/spec/model/association_reflection_spec.rb +177 -0
  92. data/spec/model/associations_spec.rb +16 -0
  93. data/spec/model/dataset_methods_spec.rb +59 -0
  94. data/spec/model/model_spec.rb +59 -0
  95. metadata +8 -2
@@ -204,7 +204,7 @@ module Sequel
204
204
  array.map do |i|
205
205
  if i.is_a?(Array)
206
206
  recursive_map(i, converter)
207
- elsif i
207
+ elsif !i.nil?
208
208
  converter.call(i)
209
209
  end
210
210
  end
@@ -414,6 +414,33 @@ module Sequel
414
414
  # CreateTableGenerator#index for available options.
415
415
  #
416
416
  # add_index(:artist_id) # CREATE INDEX table_artist_id_index ON table (artist_id)
417
+ #
418
+ # Options:
419
+ #
420
+ # :name :: Give a specific name for the index. Highly recommended if you plan on
421
+ # dropping the index later.
422
+ # :where :: A filter expression, used to setup a partial index (if supported).
423
+ # :unique :: Create a unique index.
424
+ #
425
+ # PostgreSQL specific options:
426
+ #
427
+ # :concurrently :: Create the index concurrently, so it doesn't require an exclusive lock
428
+ # on the table.
429
+ # :index_type :: The underlying index type to use for a full_text index, gin by default).
430
+ # :language :: The language to use for a full text index (simple by default).
431
+ # :opclass :: Set an opclass to use for all columns (per-column opclasses require
432
+ # custom SQL).
433
+ # :type :: Set the index type (e.g. full_text, spatial, hash, gin, gist, btree).
434
+ #
435
+ # MySQL specific options:
436
+ #
437
+ # :type :: Set the index type, with full_text and spatial indexes handled specially.
438
+ #
439
+ # Microsoft SQL Server specific options:
440
+ #
441
+ # :include :: Includes additional columns in the index.
442
+ # :key_index :: Sets the KEY INDEX to the given value.
443
+ # :type :: clustered uses a clustered index, full_text uses a full text index.
417
444
  def add_index(columns, opts = OPTS)
418
445
  @operations << {:op => :add_index, :columns => Array(columns)}.merge!(opts)
419
446
  end
@@ -791,7 +791,7 @@ module Sequel
791
791
  # # {[1, 3]=>['Jim', 'bo'], [2, 4]=>['Bob', 'be'], ...}
792
792
  #
793
793
  # DB[:table].to_hash([:id, :name]) # SELECT * FROM table
794
- # # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob'=>{:id=>2, :name=>'Bob'}, ...}
794
+ # # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob']=>{:id=>2, :name=>'Bob'}, ...}
795
795
  #
796
796
  # Options:
797
797
  # :all :: Use all instead of each to retrieve the objects
@@ -281,7 +281,11 @@ module Sequel
281
281
  fs = {}
282
282
  non_sql = non_sql_options
283
283
  @opts.keys.each{|k| fs[k] = nil unless non_sql.include?(k)}
284
- clone(fs).from(opts[:alias] ? as(opts[:alias], opts[:column_aliases]) : self)
284
+ c = clone(fs).from(opts[:alias] ? as(opts[:alias], opts[:column_aliases]) : self)
285
+ if cols = _columns
286
+ c.send(:columns=, cols)
287
+ end
288
+ c
285
289
  end
286
290
 
287
291
  # Match any of the columns to any of the patterns. The terms can be
@@ -26,8 +26,10 @@ module Sequel
26
26
  # for eval.
27
27
  def eval_inspect(obj)
28
28
  case obj
29
- when Sequel::SQL::Blob, Sequel::LiteralString, Sequel::SQL::ValueList
30
- "#{obj.class}.new(#{obj.inspect})"
29
+ when Sequel::SQL::Blob, Sequel::LiteralString, BigDecimal
30
+ "#{obj.class}.new(#{obj.to_s.inspect})"
31
+ when Sequel::SQL::ValueList
32
+ "#{obj.class}.new(#{obj.to_a.inspect})"
31
33
  when Array
32
34
  "[#{obj.map{|o| eval_inspect(o)}.join(', ')}]"
33
35
  when Hash
@@ -50,8 +52,6 @@ module Sequel
50
52
  when Date
51
53
  # Ignore offset and date of calendar reform
52
54
  "Date.new(#{obj.year}, #{obj.month}, #{obj.day})"
53
- when BigDecimal
54
- "BigDecimal.new(#{obj.to_s.inspect})"
55
55
  else
56
56
  obj.inspect
57
57
  end
@@ -0,0 +1,48 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The implicit_subquery extension changes most dataset methods that
4
+ # return modified datasets to implicitly call from_self if the database
5
+ # currently uses raw SQL. Sequel's by default does not do this:
6
+ #
7
+ # DB["SELECT * FROM table"].select(:column).sql
8
+ # # => "SELECT * FROM table"
9
+ #
10
+ # With this extension, datasets that use raw SQL are implicitly wrapped
11
+ # in a subquery:
12
+ #
13
+ # DB["SELECT * FROM table"].select(:column).sql
14
+ # # => "SELECT column FROM (SELECT * FROM table) AS t1"
15
+ #
16
+ # To add this extension to an existing dataset:
17
+ #
18
+ # ds = ds.extension(:implicit_subquery)
19
+ #
20
+ # To set this as the default behavior for all datasets on a single database:
21
+ #
22
+ # DB.extension(:implicit_subquery)
23
+ #
24
+ # Related module: Sequel::Dataset::ImplicitSubquery
25
+
26
+ #
27
+ module Sequel
28
+ class Dataset
29
+ module ImplicitSubquery
30
+ exceptions = [:and, :add_graph_aliases, :filter, :from, :from_self, :naked, :or, :order_more,
31
+ :qualify, :reverse, :reverse_order, :select_all, :select_more, :server,
32
+ :set_graph_aliases, :unfiltered, :ungraphed, :ungrouped, :unlimited, :unordered,
33
+ :with_sql]
34
+ additions = [:join_table]
35
+ (Dataset::QUERY_METHODS - Dataset::JOIN_METHODS - exceptions + additions).each do |meth|
36
+ define_method(meth) do |*a, &b|
37
+ if opts[:sql]
38
+ from_self.send(meth, *a, &b)
39
+ else
40
+ super(*a, &b)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ register_extension(:implicit_subquery, ImplicitSubquery)
47
+ end
48
+ end
@@ -65,7 +65,7 @@ module Sequel
65
65
  @stack.push(@i)
66
66
  case e
67
67
  when LiteralString
68
- dot "#{e.inspect}.lit" # core_sql use
68
+ dot "Sequel.lit(#{e.to_s.inspect})"
69
69
  when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
70
70
  dot e.inspect
71
71
  when Array
@@ -37,8 +37,8 @@ module Sequel
37
37
  ANONYMOUS_MODEL_CLASSES_MUTEX = @Model_mutex = Mutex.new
38
38
 
39
39
  # Class methods added to model that call the method of the same name on the dataset
40
- DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
41
- [:each_server]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
40
+ DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server, :where_all, :where_each, :where_single_value]) -
41
+ [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
42
42
 
43
43
  # Boolean settings that can be modified at the global, class, or instance level.
44
44
  BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
@@ -71,8 +71,7 @@ module Sequel
71
71
  # If the value is +nil+, the superclass's instance variable is used directly in the subclass.
72
72
  INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup,
73
73
  :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
74
- :@raise_on_save_failure=>nil, :@require_modification=>nil,
75
- :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
74
+ :@raise_on_save_failure=>nil, :@require_modification=>nil, :@restrict_primary_key=>nil,
76
75
  :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
77
76
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
78
77
  :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
@@ -110,7 +109,6 @@ module Sequel
110
109
  @require_modification = nil
111
110
  @require_valid_table = false
112
111
  @restrict_primary_key = true
113
- @restricted_columns = nil
114
112
  @setter_methods = nil
115
113
  @simple_pk = nil
116
114
  @simple_table = nil
@@ -7,7 +7,7 @@ module Sequel
7
7
  module Associations
8
8
  # Map of association type symbols to association reflection classes.
9
9
  ASSOCIATION_TYPES = {}
10
-
10
+
11
11
  # Set an empty association reflection hash in the model
12
12
  def self.apply(model)
13
13
  model.instance_eval do
@@ -23,7 +23,7 @@ module Sequel
23
23
  # be instantiated by the user.
24
24
  class AssociationReflection < Hash
25
25
  include Sequel::Inflections
26
-
26
+
27
27
  # Name symbol for the _add internal association method
28
28
  def _add_method
29
29
  :"_add_#{singularize(self[:name])}"
@@ -56,7 +56,13 @@ module Sequel
56
56
 
57
57
  # The class associated to the current model class via this association
58
58
  def associated_class
59
- cached_fetch(:class){constantize(self[:class_name])}
59
+ cached_fetch(:class) do
60
+ begin
61
+ constantize(self[:class_name])
62
+ rescue NameError => e
63
+ raise NameError, "#{e.message} (this happened when attempting to find the associated class for #{inspect})", e.backtrace
64
+ end
65
+ end
60
66
  end
61
67
 
62
68
  # The dataset associated via this association, with the non-instance specific
@@ -339,6 +345,38 @@ module Sequel
339
345
  {filter_by_associations_conditions_key=>ds}
340
346
  end
341
347
 
348
+ # Finalize the association by first attempting to populate the thread-safe cache,
349
+ # and then transfering the thread-safe cache value to the association itself,
350
+ # so that a mutex is not needed to get the value.
351
+ def finalize
352
+ return unless cache = self[:cache]
353
+
354
+ finalize_settings.each do |meth, key|
355
+ next if has_key?(key)
356
+
357
+ send(meth)
358
+ self[key] = cache.delete(key) if cache.has_key?(key)
359
+ end
360
+
361
+ nil
362
+ end
363
+
364
+ # Map of methods to cache keys used for finalizing associations.
365
+ FINALIZE_SETTINGS = {
366
+ :associated_class=>:class,
367
+ :associated_dataset=>:_dataset,
368
+ :associated_eager_dataset=>:associated_eager_dataset,
369
+ :eager_limit_strategy=>:_eager_limit_strategy,
370
+ :filter_by_associations_conditions_dataset=>:filter_by_associations_conditions_dataset,
371
+ :placeholder_loader=>:placeholder_loader,
372
+ :predicate_key=>:predicate_key,
373
+ :predicate_keys=>:predicate_keys,
374
+ :reciprocal=>:reciprocal,
375
+ }.freeze
376
+ def finalize_settings
377
+ FINALIZE_SETTINGS
378
+ end
379
+
342
380
  # Whether to handle silent modification failure when adding/removing
343
381
  # associated records, false by default.
344
382
  def handle_silent_modification_failure?
@@ -355,6 +393,18 @@ module Sequel
355
393
  end
356
394
  end
357
395
 
396
+ # Show which type of reflection this is, and a guess at what line was used to create the
397
+ # association.
398
+ def inspect
399
+ o = self[:orig_opts].dup
400
+ o.delete(:class)
401
+ o.delete(:class_name)
402
+ o.delete(:block) unless o[:block]
403
+ o[:class] = self[:orig_class] if self[:orig_class]
404
+
405
+ "#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
406
+ end
407
+
358
408
  # The limit and offset for this association (returned as a two element array).
359
409
  def limit_and_offset
360
410
  if (v = self[:limit]).is_a?(Array)
@@ -782,6 +832,18 @@ module Sequel
782
832
  nil
783
833
  end
784
834
 
835
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
836
+ :primary_key=>:primary_key,
837
+ :primary_keys=>:primary_keys,
838
+ :primary_key_method=>:primary_key_method,
839
+ :primary_key_methods=>:primary_key_methods,
840
+ :qualified_primary_key=>:qualified_primary_key,
841
+ :reciprocal_type=>:reciprocal_type
842
+ ).freeze
843
+ def finalize_settings
844
+ FINALIZE_SETTINGS
845
+ end
846
+
785
847
  # The expression to use on the left hand side of the IN lookup when eager loading
786
848
  def predicate_key
787
849
  cached_fetch(:predicate_key){qualified_primary_key}
@@ -926,6 +988,13 @@ module Sequel
926
988
  :"#{underscore(demodulize(self[:model].name))}_id"
927
989
  end
928
990
 
991
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
992
+ :qualified_primary_key=>:qualified_primary_key
993
+ ).freeze
994
+ def finalize_settings
995
+ FINALIZE_SETTINGS
996
+ end
997
+
929
998
  # Handle silent failure of add/remove methods if raise_on_save_failure is false.
930
999
  def handle_silent_modification_failure?
931
1000
  self[:raise_on_save_failure] == false
@@ -1196,6 +1265,22 @@ module Sequel
1196
1265
  :"#{singularize(self[:name])}_id"
1197
1266
  end
1198
1267
 
1268
+ FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
1269
+ :associated_key_array=>:associated_key_array,
1270
+ :qualified_right_key=>:qualified_right_key,
1271
+ :join_table_source=>:join_table_source,
1272
+ :join_table_alias=>:join_table_alias,
1273
+ :qualified_right_primary_key=>:qualified_right_primary_key,
1274
+ :right_primary_key=>:right_primary_key,
1275
+ :right_primary_keys=>:right_primary_keys,
1276
+ :right_primary_key_method=>:right_primary_key_method,
1277
+ :right_primary_key_methods=>:right_primary_key_methods,
1278
+ :select=>:select
1279
+ ).freeze
1280
+ def finalize_settings
1281
+ FINALIZE_SETTINGS
1282
+ end
1283
+
1199
1284
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
1200
1285
  # The left key qualified by the join table.
1201
1286
  def predicate_key
@@ -1668,7 +1753,7 @@ module Sequel
1668
1753
  # Defaults to :right_primary_key option.
1669
1754
  # :uniq :: Adds a after_load callback that makes the array of objects unique.
1670
1755
  def associate(type, name, opts = OPTS, &block)
1671
- raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
1756
+ raise(Error, 'invalid association type') unless assoc_class = Sequel.synchronize{ASSOCIATION_TYPES[type]}
1672
1757
  raise(Error, 'Model.associate name argument must be a symbol') unless name.is_a?(Symbol)
1673
1758
 
1674
1759
  # dup early so we don't modify opts
@@ -1716,6 +1801,7 @@ module Sequel
1716
1801
  def_association_instance_methods(opts)
1717
1802
 
1718
1803
  orig_opts.delete(:clone)
1804
+ opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
1719
1805
  orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>opts[:block])
1720
1806
  opts[:orig_opts] = orig_opts
1721
1807
  # don't add to association_reflections until we are sure there are no errors
@@ -1737,6 +1823,23 @@ module Sequel
1737
1823
  opts.eager_load_results(eo, &block)
1738
1824
  end
1739
1825
 
1826
+ # Freeze association related metadata when freezing model class.
1827
+ def freeze
1828
+ @association_reflections.freeze.each_value(&:freeze)
1829
+ @autoreloading_associations.freeze.each_value(&:freeze)
1830
+ @default_association_options.freeze
1831
+
1832
+ super
1833
+ end
1834
+
1835
+ # Finalize all associations such that values that are looked up
1836
+ # dynamically in associated classes are set statically.
1837
+ # As this modifies the associations, it must be done before
1838
+ # calling freeze.
1839
+ def finalize_associations
1840
+ @association_reflections.each_value(&:finalize)
1841
+ end
1842
+
1740
1843
  # Shortcut for adding a many_to_many association, see #associate
1741
1844
  def many_to_many(name, opts=OPTS, &block)
1742
1845
  associate(:many_to_many, name, opts, &block)
@@ -238,7 +238,7 @@ module Sequel
238
238
 
239
239
  # Clear the setter_methods cache
240
240
  def clear_setter_methods_cache
241
- @setter_methods = nil
241
+ @setter_methods = nil unless frozen?
242
242
  end
243
243
 
244
244
  # Returns the columns in the result set in their original order.
@@ -249,7 +249,9 @@ module Sequel
249
249
  # Artist.columns
250
250
  # # => [:id, :name]
251
251
  def columns
252
- @columns || set_columns(dataset.naked.columns)
252
+ return @columns if @columns
253
+ return nil if frozen?
254
+ set_columns(dataset.naked.columns)
253
255
  end
254
256
 
255
257
  # Creates instance using new with the given values and block, and saves it.
@@ -323,7 +325,7 @@ module Sequel
323
325
  # Album.released.by_release_date.for_select_options.sql
324
326
  # # => "SELECT id, name, release_date FROM artists WHERE (release_date <= CURRENT_DATE) ORDER BY release_date"
325
327
  #
326
- # The following methods are supported: distinct, exclude, exclude_having, grep, group, group_and_count,
328
+ # The following methods are supported: distinct, eager, exclude, exclude_having, grep, group, group_and_count,
327
329
  # group_append, having, limit, offset, order, order_append, order_prepend, select, select_all,
328
330
  # select_append, select_group, where, and server.
329
331
  #
@@ -388,7 +390,9 @@ module Sequel
388
390
  # # {:id=>{:type=>:integer, :primary_key=>true, ...},
389
391
  # # :name=>{:type=>:string, :primary_key=>false, ...}}
390
392
  def db_schema
391
- @db_schema ||= get_db_schema
393
+ return @db_schema if @db_schema
394
+ return nil if frozen?
395
+ @db_schema = get_db_schema
392
396
  end
393
397
 
394
398
  # Create a column alias, where the column methods have one name, but the underlying storage uses a
@@ -579,7 +583,7 @@ module Sequel
579
583
  end
580
584
  end
581
585
 
582
- Sequel.synchronize{@finder_loaders[meth_name] = loader_proc}
586
+ @finder_loaders[meth_name] = loader_proc
583
587
  mod = opts[:mod] || (class << self; self; end)
584
588
  if prepare
585
589
  def_prepare_method(mod, meth_name)
@@ -605,6 +609,33 @@ module Sequel
605
609
  first(*args, &block) || raise(Sequel::NoMatchingRow.new(dataset))
606
610
  end
607
611
 
612
+ # Freeze a model class, disallowing any further changes to it.
613
+ def freeze
614
+ dataset_module.freeze
615
+ overridable_methods_module.freeze
616
+
617
+ @finder_loaders.freeze
618
+
619
+ if @dataset
620
+ @dataset.freeze
621
+ @instance_dataset.freeze
622
+ db_schema.freeze.each_value(&:freeze)
623
+ columns.freeze
624
+ setter_methods.freeze
625
+ @finder_loaders.each_key{|k| finder_for(k)}
626
+ else
627
+ @setter_methods = [].freeze
628
+ end
629
+
630
+ @dataset_method_modules.freeze
631
+ @default_set_fields_options.freeze
632
+ @finders.freeze
633
+ @plugins.freeze
634
+ @allowed_columns.freeze if @allowed_columns
635
+
636
+ super
637
+ end
638
+
608
639
  # Clear the setter_methods cache when a module is included, as it
609
640
  # may contain setter methods.
610
641
  def include(*mods)
@@ -856,14 +887,16 @@ module Sequel
856
887
  end
857
888
  end
858
889
  self.simple_pk = if key && !key.is_a?(Array)
859
- (@dataset || db).literal(key)
890
+ (@dataset || db).literal(key).freeze
860
891
  end
861
892
  @primary_key = key
862
893
  end
863
894
 
864
895
  # Cache of setter methods to allow by default, in order to speed up new/set/update instance methods.
865
896
  def setter_methods
866
- @setter_methods ||= get_setter_methods
897
+ return @setter_methods if @setter_methods
898
+ raise if frozen?
899
+ @setter_methods = get_setter_methods
867
900
  end
868
901
 
869
902
  # Sets up a dataset method that returns a filtered dataset.
@@ -951,11 +984,11 @@ module Sequel
951
984
  def convert_input_dataset(ds)
952
985
  case ds
953
986
  when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
954
- self.simple_table = db.literal(ds)
987
+ self.simple_table = db.literal(ds).freeze
955
988
  ds = db.from(ds)
956
989
  when Dataset
957
990
  self.simple_table = if ds.send(:simple_select_all?)
958
- ds.literal(ds.first_source_table)
991
+ ds.literal(ds.first_source_table).freeze
959
992
  end
960
993
  @db = ds.db
961
994
  else
@@ -1028,7 +1061,7 @@ module Sequel
1028
1061
  # for the method, load the finder and set correctly in the finders hash, then
1029
1062
  # return the finder.
1030
1063
  def finder_for(meth)
1031
- unless finder = Sequel.synchronize{@finders[meth]}
1064
+ unless finder = (frozen? ? @finders[meth] : Sequel.synchronize{@finders[meth]})
1032
1065
  finder_loader = @finder_loaders.fetch(meth)
1033
1066
  finder = finder_loader.call(self)
1034
1067
  Sequel.synchronize{@finders[meth] = finder}
@@ -1219,7 +1252,7 @@ module Sequel
1219
1252
  # Reset the instance dataset to a modified copy of the current dataset,
1220
1253
  # should be used whenever the model's dataset is modified.
1221
1254
  def reset_instance_dataset
1222
- @finders.clear if @finders
1255
+ Sequel.synchronize{@finders.clear if @finders}
1223
1256
  @instance_dataset = @dataset.limit(1).naked if @dataset
1224
1257
  end
1225
1258
 
@@ -2042,7 +2075,7 @@ module Sequel
2042
2075
 
2043
2076
  # Get the row of column data from the database.
2044
2077
  def _refresh_get(dataset)
2045
- if (sql = (m = model).fast_pk_lookup_sql) && !dataset.opts[:lock]
2078
+ if (sql = model.fast_pk_lookup_sql) && !dataset.opts[:lock]
2046
2079
  sql = sql.dup
2047
2080
  ds = use_server(dataset)
2048
2081
  ds.literal_append(sql, pk)
@@ -2528,6 +2561,52 @@ module Sequel
2528
2561
  end
2529
2562
  end
2530
2563
 
2564
+ # Return an array of all rows matching the given filter condition, also
2565
+ # yielding each row to the given block. Basically the same as where(cond).all(&block),
2566
+ # except it can be optimized to not create an intermediate dataset.
2567
+ #
2568
+ # Artist.where_all(:id=>[1,2,3])
2569
+ # # SELECT * FROM artists WHERE (id IN (1, 2, 3))
2570
+ def where_all(cond, &block)
2571
+ if loader = _model_where_loader
2572
+ loader.all(filter_expr(cond), &block)
2573
+ else
2574
+ where(cond).all(&block)
2575
+ end
2576
+ end
2577
+
2578
+ # Iterate over all rows matching the given filter condition,
2579
+ # yielding each row to the given block. Basically the same as where(cond).each(&block),
2580
+ # except it can be optimized to not create an intermediate dataset.
2581
+ #
2582
+ # Artist.where_each(:id=>[1,2,3]){|row| p row}
2583
+ # # SELECT * FROM artists WHERE (id IN (1, 2, 3))
2584
+ def where_each(cond, &block)
2585
+ if loader = _model_where_loader
2586
+ loader.each(filter_expr(cond), &block)
2587
+ else
2588
+ where(cond).each(&block)
2589
+ end
2590
+ end
2591
+
2592
+ # Filter the datasets using the given filter condition, then return a single value.
2593
+ # This assumes that the dataset has already been setup to limit the selection to
2594
+ # a single column. Basically the same as where(cond).single_value,
2595
+ # except it can be optimized to not create an intermediate dataset.
2596
+ #
2597
+ # Artist.select(:name).where_single_value(:id=>1)
2598
+ # # SELECT name FROM artists WHERE (id = 1) LIMIT 1
2599
+ def where_single_value(cond)
2600
+ if loader = cached_placeholder_literalizer(:_model_where_single_value_loader) do |pl|
2601
+ single_value_ds.where(pl.arg)
2602
+ end
2603
+
2604
+ loader.get(filter_expr(cond))
2605
+ else
2606
+ where(cond).single_value
2607
+ end
2608
+ end
2609
+
2531
2610
  # Given a primary key value, return the first record in the dataset with that primary key
2532
2611
  # value. If no records matches, returns nil.
2533
2612
  #
@@ -2554,6 +2633,13 @@ module Sequel
2554
2633
 
2555
2634
  private
2556
2635
 
2636
+ # Loader used for where_all and where_each.
2637
+ def _model_where_loader
2638
+ cached_placeholder_literalizer(:_model_where_loader) do |pl|
2639
+ where(pl.arg)
2640
+ end
2641
+ end
2642
+
2557
2643
  # If the dataset is not already ordered, and the model has a primary key,
2558
2644
  # return a clone ordered by the primary key.
2559
2645
  def _primary_key_order