sequel 4.43.0 → 4.44.0

Sign up to get free protection for your applications and to get access to all the features.
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