sequel 5.19.0 → 5.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +102 -0
  3. data/doc/dataset_filtering.rdoc +15 -0
  4. data/doc/opening_databases.rdoc +5 -1
  5. data/doc/release_notes/5.20.0.txt +89 -0
  6. data/doc/release_notes/5.21.0.txt +87 -0
  7. data/doc/release_notes/5.22.0.txt +48 -0
  8. data/doc/release_notes/5.23.0.txt +56 -0
  9. data/doc/release_notes/5.24.0.txt +56 -0
  10. data/doc/sharding.rdoc +2 -0
  11. data/doc/testing.rdoc +1 -0
  12. data/doc/transactions.rdoc +38 -0
  13. data/lib/sequel/adapters/ado.rb +27 -19
  14. data/lib/sequel/adapters/jdbc.rb +7 -1
  15. data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  18. data/lib/sequel/adapters/mysql2.rb +2 -3
  19. data/lib/sequel/adapters/shared/mssql.rb +7 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +37 -19
  21. data/lib/sequel/adapters/shared/sqlite.rb +27 -3
  22. data/lib/sequel/adapters/sqlite.rb +1 -1
  23. data/lib/sequel/adapters/tinytds.rb +12 -0
  24. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
  25. data/lib/sequel/database/logging.rb +7 -1
  26. data/lib/sequel/database/query.rb +1 -1
  27. data/lib/sequel/database/schema_generator.rb +12 -3
  28. data/lib/sequel/database/schema_methods.rb +2 -0
  29. data/lib/sequel/database/transactions.rb +57 -5
  30. data/lib/sequel/dataset.rb +4 -2
  31. data/lib/sequel/dataset/actions.rb +3 -2
  32. data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
  33. data/lib/sequel/dataset/query.rb +5 -1
  34. data/lib/sequel/dataset/sql.rb +11 -7
  35. data/lib/sequel/extensions/named_timezones.rb +52 -8
  36. data/lib/sequel/extensions/pg_array.rb +4 -0
  37. data/lib/sequel/extensions/pg_json.rb +387 -123
  38. data/lib/sequel/extensions/pg_range.rb +3 -2
  39. data/lib/sequel/extensions/pg_row.rb +3 -1
  40. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  41. data/lib/sequel/extensions/server_block.rb +15 -4
  42. data/lib/sequel/model/associations.rb +35 -9
  43. data/lib/sequel/model/plugins.rb +104 -0
  44. data/lib/sequel/plugins/association_dependencies.rb +3 -3
  45. data/lib/sequel/plugins/association_pks.rb +14 -4
  46. data/lib/sequel/plugins/association_proxies.rb +3 -2
  47. data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
  48. data/lib/sequel/plugins/composition.rb +13 -9
  49. data/lib/sequel/plugins/finder.rb +2 -2
  50. data/lib/sequel/plugins/hook_class_methods.rb +17 -5
  51. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  52. data/lib/sequel/plugins/inverted_subsets.rb +2 -2
  53. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
  54. data/lib/sequel/plugins/rcte_tree.rb +6 -0
  55. data/lib/sequel/plugins/static_cache.rb +8 -3
  56. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  57. data/lib/sequel/plugins/subset_conditions.rb +2 -2
  58. data/lib/sequel/plugins/validation_class_methods.rb +5 -3
  59. data/lib/sequel/sql.rb +15 -3
  60. data/lib/sequel/timezones.rb +50 -11
  61. data/lib/sequel/version.rb +1 -1
  62. data/spec/adapters/mssql_spec.rb +24 -0
  63. data/spec/adapters/mysql_spec.rb +0 -5
  64. data/spec/adapters/postgres_spec.rb +319 -1
  65. data/spec/bin_spec.rb +1 -1
  66. data/spec/core/database_spec.rb +123 -2
  67. data/spec/core/dataset_spec.rb +33 -1
  68. data/spec/core/expression_filters_spec.rb +25 -1
  69. data/spec/core/schema_spec.rb +24 -0
  70. data/spec/extensions/class_table_inheritance_spec.rb +30 -8
  71. data/spec/extensions/core_refinements_spec.rb +1 -1
  72. data/spec/extensions/hook_class_methods_spec.rb +22 -0
  73. data/spec/extensions/insert_conflict_spec.rb +103 -0
  74. data/spec/extensions/migration_spec.rb +13 -0
  75. data/spec/extensions/named_timezones_spec.rb +109 -2
  76. data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
  77. data/spec/extensions/pg_json_spec.rb +218 -29
  78. data/spec/extensions/pg_range_spec.rb +76 -9
  79. data/spec/extensions/rcte_tree_spec.rb +6 -0
  80. data/spec/extensions/s_spec.rb +1 -1
  81. data/spec/extensions/schema_dumper_spec.rb +4 -2
  82. data/spec/extensions/server_block_spec.rb +38 -0
  83. data/spec/extensions/spec_helper.rb +8 -1
  84. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  85. data/spec/integration/dataset_test.rb +25 -9
  86. data/spec/integration/plugin_test.rb +42 -0
  87. data/spec/integration/schema_test.rb +7 -2
  88. data/spec/integration/transaction_test.rb +50 -0
  89. data/spec/model/associations_spec.rb +84 -4
  90. data/spec/model/plugins_spec.rb +111 -0
  91. metadata +16 -2
@@ -35,8 +35,8 @@ module Sequel
35
35
  module Finder
36
36
  FINDER_TYPES = [:first, :all, :each, :get].freeze
37
37
 
38
- def self.apply(mod)
39
- mod.instance_exec do
38
+ def self.apply(model)
39
+ model.instance_exec do
40
40
  @finders ||= {}
41
41
  @finder_loaders ||= {}
42
42
  end
@@ -59,7 +59,14 @@ module Sequel
59
59
 
60
60
  # Yield every block related to the given hook.
61
61
  def hook_blocks(hook)
62
- @hooks[hook].each{|k,v| yield v}
62
+ # SEQUEL6: Remove
63
+ Sequel::Deprecation.deprecate("The hook_blocks class method in the hook_class_methods plugin is deprecated and will be removed in Sequel 6.")
64
+ @hooks[hook].each{|_,v,_| yield v}
65
+ end
66
+
67
+ # Yield every method related to the given hook.
68
+ def hook_methods_for(hook)
69
+ @hooks[hook].each{|_,_,m| yield m}
63
70
  end
64
71
 
65
72
  Plugins.inherited_instance_variables(self, :@hooks=>:hash_dup)
@@ -75,23 +82,28 @@ module Sequel
75
82
  # Allow calling private hook methods
76
83
  block = proc {send(tag)}
77
84
  end
85
+
78
86
  h = @hooks[hook]
87
+
79
88
  if tag && (old = h.find{|x| x[0] == tag})
80
89
  old[1] = block
90
+ Plugins.def_sequel_method(self, old[2], 0, &block)
81
91
  else
92
+ meth = Plugins.def_sequel_method(self, "validation_class_methods_#{hook}", 0, &block)
82
93
  if hook.to_s =~ /^before/
83
- h.unshift([tag,block])
94
+ h.unshift([tag, block, meth])
84
95
  else
85
- h << [tag, block]
96
+ h << [tag, block, meth]
86
97
  end
87
98
  end
88
99
  end
89
100
  end
90
101
 
91
102
  module InstanceMethods
92
- [:before_create, :before_update, :before_validation, :before_save, :before_destroy].each{|h| class_eval("def #{h}; model.hook_blocks(:#{h}){|b| instance_exec(&b)}; super end", __FILE__, __LINE__)}
103
+ # hook methods are private
104
+ [:before_create, :before_update, :before_validation, :before_save, :before_destroy].each{|h| class_eval("def #{h}; model.hook_methods_for(:#{h}){|m| send(m)}; super end", __FILE__, __LINE__)}
93
105
 
94
- [:after_create, :after_update, :after_validation, :after_save, :after_destroy].each{|h| class_eval("def #{h}; super; model.hook_blocks(:#{h}){|b| instance_exec(&b)}; end", __FILE__, __LINE__)}
106
+ [:after_create, :after_update, :after_validation, :after_save, :after_destroy].each{|h| class_eval("def #{h}; super; model.hook_methods_for(:#{h}){|m| send(m)}; end", __FILE__, __LINE__)}
95
107
  end
96
108
  end
97
109
  end
@@ -0,0 +1,72 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The insert_conflict plugin allows handling conflicts due to unique
6
+ # constraints when saving new model instance, using the INSERT ON CONFLICT
7
+ # support in PostgreSQL 9.5+ and SQLite 3.24.0+. Example:
8
+ #
9
+ # class Album < Sequel::Model
10
+ # plugin :insert_conflict
11
+ # end
12
+ #
13
+ # Album.new(name: 'Foo', copies_sold: 1000).
14
+ # insert_conflict(
15
+ # target: :name,
16
+ # update: {copies_sold: Sequel[:excluded][:b]}
17
+ # ).
18
+ # save
19
+ #
20
+ # This example will try to insert the album, but if there is an existing
21
+ # album with the name 'Foo', this will update the copies_sold attribute
22
+ # for that album. See the PostgreSQL and SQLite adapter documention for
23
+ # the options you can pass to the insert_conflict method.
24
+ #
25
+ # Usage:
26
+ #
27
+ # # Make all model subclasses support insert_conflict
28
+ # Sequel::Model.plugin :insert_conflict
29
+ #
30
+ # # Make the Album class support insert_conflict
31
+ # Album.plugin :insert_conflict
32
+ module InsertConflict
33
+ def self.configure(model)
34
+ model.instance_exec do
35
+ if @dataset && !@dataset.respond_to?(:insert_conflict)
36
+ raise Error, "#{self}'s dataset does not support insert_conflict"
37
+ end
38
+ end
39
+ end
40
+
41
+ module InstanceMethods
42
+ # Set the insert_conflict options to pass to the dataset when inserting.
43
+ def insert_conflict(opts=OPTS)
44
+ raise Error, "Model#insert_conflict is only supported on new model instances" unless new?
45
+ @insert_conflict_opts = opts
46
+ self
47
+ end
48
+
49
+ private
50
+
51
+ # Set the dataset used for inserting to use INSERT ON CONFLICT
52
+ # Model#insert_conflict has been called on the instance previously.
53
+ def _insert_dataset
54
+ ds = super
55
+
56
+ if @insert_conflict_opts
57
+ ds = ds.insert_conflict(@insert_conflict_opts)
58
+ end
59
+
60
+ ds
61
+ end
62
+
63
+ # Disable the use of prepared insert statements, as they are not compatible
64
+ # with this plugin.
65
+ def use_prepared_statements_for?(type)
66
+ return false if type == :insert || type == :insert_select
67
+ super if defined?(super)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -29,8 +29,8 @@ module Sequel
29
29
  # # SELECT * FROM albums WHERE (published IS NOT TRUE)
30
30
  #
31
31
  module InvertedSubsets
32
- def self.apply(mod, &block)
33
- mod.instance_exec do
32
+ def self.apply(model, &block)
33
+ model.instance_exec do
34
34
  @dataset_module_class = Class.new(@dataset_module_class) do
35
35
  include DatasetModuleMethods
36
36
  if block
@@ -22,8 +22,9 @@ module Sequel
22
22
  # This plugin is not intended as a replacement for other validations,
23
23
  # it is intended as a last resort. The purpose of validations is to provide nice
24
24
  # error messages for the user, and the error messages generated by this plugin are
25
- # fairly generic. The error messages can be customized using the :messages plugin
26
- # option, but there is only a single message used per constraint type.
25
+ # fairly generic by default. The error messages can be customized per constraint type
26
+ # using the :messages plugin option, and individually per constraint using
27
+ # +pg_auto_constraint_validation_override+ (see below).
27
28
  #
28
29
  # This plugin only works on the postgres adapter when using the pg 0.16+ driver,
29
30
  # PostgreSQL 9.3+ server, and PostgreSQL 9.3+ client library (libpq). In other cases
@@ -37,6 +38,38 @@ module Sequel
37
38
  # rescue Sequel::ValidationFailed
38
39
  # album.errors.on(:artist_id) # ['is invalid']
39
40
  # end
41
+ #
42
+ # While the database usually provides enough information to correctly associated
43
+ # constraint violations with model columns, there are cases where it does not.
44
+ # In those cases, you can override the handling of specific constraint violations
45
+ # to be associated to particular column(s), and use a specific error message:
46
+ #
47
+ # Album.pg_auto_constraint_validation_override(:constraint_name, [:column1], "validation error message")
48
+ #
49
+ # Using the pg_auto_constraint_validations plugin requires 5 queries per
50
+ # model at load time in order to gather the necessary metadata. For applications
51
+ # with a large number of models, this can result in a noticeable delay during model
52
+ # initialization. To mitigate this issue, you can cache the necessary metadata in
53
+ # a file with the :cache_file option:
54
+ #
55
+ # Sequel::Model.plugin :pg_auto_constraint_validations, cache_file: 'db/pgacv.cache'
56
+ #
57
+ # The file does not have to exist when loading the plugin. If it exists, the plugin
58
+ # will load the cache and use the cached results instead of issuing queries if there
59
+ # is an entry in the cache. If there is no entry in the cache, it will update the
60
+ # in-memory cache with the metadata results. To save the in in-memory cache back to
61
+ # the cache file, run:
62
+ #
63
+ # Sequel::Model.dump_pg_auto_constraint_validations_cache
64
+ #
65
+ # Note that when using the :cache_file option, it is up to the application to ensure
66
+ # that the dumped cached metadata reflects the current state of the database. Sequel
67
+ # does no checking to ensure this, as checking would take time and the
68
+ # purpose of this code is to take a shortcut.
69
+ #
70
+ # The cached schema is dumped in Marshal format, since it is the fastest
71
+ # and it handles all ruby objects used in the metadata. Because of this,
72
+ # you should not attempt to load the metadata from a untrusted file.
40
73
  #
41
74
  # Usage:
42
75
  #
@@ -59,13 +92,28 @@ module Sequel
59
92
  }.freeze).each_value(&:freeze)
60
93
 
61
94
  # Setup the constraint violation metadata. Options:
95
+ # :cache_file :: File storing cached metadata, to avoid queries for each model
62
96
  # :messages :: Override the default error messages for each constraint
63
97
  # violation type (:not_null, :check, :unique, :foreign_key, :referenced_by)
64
98
  def self.configure(model, opts=OPTS)
65
99
  model.instance_exec do
100
+ if @pg_auto_constraint_validations_cache_file = opts[:cache_file]
101
+ @pg_auto_constraint_validations_cache = if ::File.file?(@pg_auto_constraint_validations_cache_file)
102
+ cache = Marshal.load(File.read(@pg_auto_constraint_validations_cache_file))
103
+ cache.each_value do |hash|
104
+ hash.freeze.each_value(&:freeze)
105
+ end
106
+ else
107
+ {}
108
+ end
109
+ else
110
+ @pg_auto_constraint_validations_cache = nil
111
+ end
112
+
66
113
  setup_pg_auto_constraint_validations
67
114
  @pg_auto_constraint_validations_messages = (@pg_auto_constraint_validations_messages || DEFAULT_ERROR_MESSAGES).merge(opts[:messages] || OPTS).freeze
68
115
  end
116
+ nil
69
117
  end
70
118
 
71
119
  module ClassMethods
@@ -77,9 +125,26 @@ module Sequel
77
125
  # generated validation failures.
78
126
  attr_reader :pg_auto_constraint_validations_messages
79
127
 
80
- Plugins.inherited_instance_variables(self, :@pg_auto_constraint_validations=>nil, :@pg_auto_constraint_validations_messages=>nil)
128
+ Plugins.inherited_instance_variables(self, :@pg_auto_constraint_validations=>nil, :@pg_auto_constraint_validations_messages=>nil, :@pg_auto_constraint_validations_cache=>nil, :@pg_auto_constraint_validations_cache_file=>nil)
81
129
  Plugins.after_set_dataset(self, :setup_pg_auto_constraint_validations)
82
130
 
131
+ # Dump the in-memory cached metadata to the cache file.
132
+ def dump_pg_auto_constraint_validations_cache
133
+ raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
134
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
135
+ nil
136
+ end
137
+
138
+ # Override the constraint validation columns and message for a given constraint
139
+ def pg_auto_constraint_validation_override(constraint, columns, message)
140
+ pgacv = Hash[@pg_auto_constraint_validations]
141
+ overrides = pgacv[:overrides] = Hash[pgacv[:overrides]]
142
+ overrides[constraint] = [Array(columns), message].freeze
143
+ overrides.freeze
144
+ @pg_auto_constraint_validations = pgacv.freeze
145
+ nil
146
+ end
147
+
83
148
  private
84
149
 
85
150
  # Get the list of constraints, unique indexes, foreign keys in the current
@@ -104,38 +169,51 @@ module Sequel
104
169
  return
105
170
  end
106
171
 
107
- checks = {}
108
- indexes = {}
109
- foreign_keys = {}
110
- referenced_by = {}
172
+ cache = @pg_auto_constraint_validations_cache
173
+ literal_table_name = dataset.literal(table_name)
174
+ unless cache && (metadata = cache[literal_table_name])
175
+ checks = {}
176
+ indexes = {}
177
+ foreign_keys = {}
178
+ referenced_by = {}
111
179
 
112
- db.check_constraints(table_name).each do |k, v|
113
- checks[k] = v[:columns].dup.freeze
114
- end
115
- db.indexes(table_name, :include_partial=>true).each do |k, v|
116
- if v[:unique]
117
- indexes[k] = v[:columns].dup.freeze
180
+ db.check_constraints(table_name).each do |k, v|
181
+ checks[k] = v[:columns].dup.freeze unless v[:columns].empty?
182
+ end
183
+ db.indexes(table_name, :include_partial=>true).each do |k, v|
184
+ if v[:unique]
185
+ indexes[k] = v[:columns].dup.freeze
186
+ end
187
+ end
188
+ db.foreign_key_list(table_name, :schema=>false).each do |fk|
189
+ foreign_keys[fk[:name]] = fk[:columns].dup.freeze
190
+ end
191
+ db.foreign_key_list(table_name, :reverse=>true, :schema=>false).each do |fk|
192
+ referenced_by[[fk[:schema], fk[:table], fk[:name]].freeze] = fk[:key].dup.freeze
193
+ end
194
+
195
+ schema, table = db[:pg_class].
196
+ join(:pg_namespace, :oid=>:relnamespace, db.send(:regclass_oid, table_name)=>:oid).
197
+ get([:nspname, :relname])
198
+
199
+ metadata = {
200
+ :schema=>schema,
201
+ :table=>table,
202
+ :check=>checks,
203
+ :unique=>indexes,
204
+ :foreign_key=>foreign_keys,
205
+ :referenced_by=>referenced_by,
206
+ :overrides=>OPTS
207
+ }.freeze
208
+ metadata.each_value(&:freeze)
209
+
210
+ if cache
211
+ cache[literal_table_name] = metadata
118
212
  end
119
- end
120
- db.foreign_key_list(table_name, :schema=>false).each do |fk|
121
- foreign_keys[fk[:name]] = fk[:columns].dup.freeze
122
- end
123
- db.foreign_key_list(table_name, :reverse=>true, :schema=>false).each do |fk|
124
- referenced_by[[fk[:schema], fk[:table], fk[:name]].freeze] = fk[:key].dup.freeze
125
213
  end
126
214
 
127
- schema, table = db[:pg_class].
128
- join(:pg_namespace, :oid=>:relnamespace, db.send(:regclass_oid, table_name)=>:oid).
129
- get([:nspname, :relname])
130
-
131
- (@pg_auto_constraint_validations = {
132
- :schema=>schema,
133
- :table=>table,
134
- :check=>checks,
135
- :unique=>indexes,
136
- :foreign_key=>foreign_keys,
137
- :referenced_by=>referenced_by
138
- }.freeze).each_value(&:freeze)
215
+ @pg_auto_constraint_validations = metadata
216
+ nil
139
217
  end
140
218
  end
141
219
 
@@ -158,40 +236,50 @@ module Sequel
158
236
  m = ds.method(:output_identifier)
159
237
  schema = info[:schema]
160
238
  table = info[:table]
239
+
161
240
  if constraint = info[:constraint]
162
241
  constraint = m.call(constraint)
242
+
243
+ columns, message = cv_info[:overrides][constraint]
244
+ if columns
245
+ override = true
246
+ add_pg_constraint_validation_error(columns, message)
247
+ end
163
248
  end
249
+
164
250
  messages = model.pg_auto_constraint_validations_messages
165
251
 
166
- case e
167
- when Sequel::NotNullConstraintViolation
168
- if column = info[:column]
169
- add_pg_constraint_validation_error([m.call(column)], messages[:not_null])
170
- end
171
- when Sequel::CheckConstraintViolation
172
- if columns = cv_info[:check][constraint]
173
- add_pg_constraint_validation_error(columns, messages[:check])
174
- end
175
- when Sequel::UniqueConstraintViolation
176
- if columns = cv_info[:unique][constraint]
177
- add_pg_constraint_validation_error(columns, messages[:unique])
178
- end
179
- when Sequel::ForeignKeyConstraintViolation
180
- message_primary = info[:message_primary]
181
- if message_primary.start_with?('update')
182
- # This constraint violation is different from the others, because the constraint
183
- # referenced is a constraint for a different table, not for this table. This
184
- # happens when another table references the current table, and the referenced
185
- # column in the current update is modified such that referential integrity
186
- # would be broken. Use the reverse foreign key information to figure out
187
- # which column is affected in that case.
188
- skip_schema_table_check = true
189
- if columns = cv_info[:referenced_by][[m.call(schema), m.call(table), constraint]]
190
- add_pg_constraint_validation_error(columns, messages[:referenced_by])
252
+ unless override
253
+ case e
254
+ when Sequel::NotNullConstraintViolation
255
+ if column = info[:column]
256
+ add_pg_constraint_validation_error([m.call(column)], messages[:not_null])
257
+ end
258
+ when Sequel::CheckConstraintViolation
259
+ if columns = cv_info[:check][constraint]
260
+ add_pg_constraint_validation_error(columns, messages[:check])
261
+ end
262
+ when Sequel::UniqueConstraintViolation
263
+ if columns = cv_info[:unique][constraint]
264
+ add_pg_constraint_validation_error(columns, messages[:unique])
191
265
  end
192
- elsif message_primary.start_with?('insert')
193
- if columns = cv_info[:foreign_key][constraint]
194
- add_pg_constraint_validation_error(columns, messages[:foreign_key])
266
+ when Sequel::ForeignKeyConstraintViolation
267
+ message_primary = info[:message_primary]
268
+ if message_primary.start_with?('update')
269
+ # This constraint violation is different from the others, because the constraint
270
+ # referenced is a constraint for a different table, not for this table. This
271
+ # happens when another table references the current table, and the referenced
272
+ # column in the current update is modified such that referential integrity
273
+ # would be broken. Use the reverse foreign key information to figure out
274
+ # which column is affected in that case.
275
+ skip_schema_table_check = true
276
+ if columns = cv_info[:referenced_by][[m.call(schema), m.call(table), constraint]]
277
+ add_pg_constraint_validation_error(columns, messages[:referenced_by])
278
+ end
279
+ elsif message_primary.start_with?('insert')
280
+ if columns = cv_info[:foreign_key][constraint]
281
+ add_pg_constraint_validation_error(columns, messages[:foreign_key])
282
+ end
195
283
  end
196
284
  end
197
285
  end
@@ -126,6 +126,9 @@ module Sequel
126
126
  a = opts.merge(opts.fetch(:ancestors, OPTS))
127
127
  ancestors = a.fetch(:name, :ancestors)
128
128
  a[:read_only] = true unless a.has_key?(:read_only)
129
+ a[:eager_grapher] = proc do |_|
130
+ raise Sequel::Error, "the #{ancestors} association for #{self} does not support eager graphing"
131
+ end
129
132
  a[:eager_loader_key] = key
130
133
  a[:dataset] ||= proc do
131
134
  base_ds = model.where(prkey_array.zip(key_array.map{|k| get_column_value(k)}))
@@ -221,6 +224,9 @@ module Sequel
221
224
  d = opts.merge(opts.fetch(:descendants, OPTS))
222
225
  descendants = d.fetch(:name, :descendants)
223
226
  d[:read_only] = true unless d.has_key?(:read_only)
227
+ d[:eager_grapher] = proc do |_|
228
+ raise Sequel::Error, "the #{descendants} association for #{self} does not support eager graphing"
229
+ end
224
230
  la = d[:level_alias] ||= :x_level_x
225
231
  d[:dataset] ||= proc do
226
232
  base_ds = model.where(key_array.zip(prkey_array.map{|k| get_column_value(k)}))
@@ -211,18 +211,23 @@ module Sequel
211
211
  # Reload the cache for this model by retrieving all of the instances in the dataset
212
212
  # freezing them, and populating the cached array and hash.
213
213
  def load_cache
214
- a = dataset.all
214
+ @all = load_static_cache_rows
215
215
  h = {}
216
- a.each do |o|
216
+ @all.each do |o|
217
217
  o.errors.freeze
218
218
  h[o.pk.freeze] = o.freeze
219
219
  end
220
- @all = a.freeze
221
220
  @cache = h.freeze
222
221
  end
223
222
 
224
223
  private
225
224
 
225
+ # Load the static cache rows from the database.
226
+ def load_static_cache_rows
227
+ ret = super if defined?(super)
228
+ ret || dataset.all.freeze
229
+ end
230
+
226
231
  # Return the frozen object with the given pk, or nil if no such object exists
227
232
  # in the cache, without issuing a database query.
228
233
  def primary_key_lookup(pk)