sequel 5.19.0 → 5.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +102 -0
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +5 -1
- data/doc/release_notes/5.20.0.txt +89 -0
- data/doc/release_notes/5.21.0.txt +87 -0
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/sharding.rdoc +2 -0
- data/doc/testing.rdoc +1 -0
- data/doc/transactions.rdoc +38 -0
- data/lib/sequel/adapters/ado.rb +27 -19
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +2 -3
- data/lib/sequel/adapters/shared/mssql.rb +7 -7
- data/lib/sequel/adapters/shared/postgres.rb +37 -19
- data/lib/sequel/adapters/shared/sqlite.rb +27 -3
- data/lib/sequel/adapters/sqlite.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +12 -3
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/database/transactions.rb +57 -5
- data/lib/sequel/dataset.rb +4 -2
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
- data/lib/sequel/dataset/query.rb +5 -1
- data/lib/sequel/dataset/sql.rb +11 -7
- data/lib/sequel/extensions/named_timezones.rb +52 -8
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +387 -123
- data/lib/sequel/extensions/pg_range.rb +3 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/extensions/server_block.rb +15 -4
- data/lib/sequel/model/associations.rb +35 -9
- data/lib/sequel/model/plugins.rb +104 -0
- data/lib/sequel/plugins/association_dependencies.rb +3 -3
- data/lib/sequel/plugins/association_pks.rb +14 -4
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
- data/lib/sequel/plugins/composition.rb +13 -9
- data/lib/sequel/plugins/finder.rb +2 -2
- data/lib/sequel/plugins/hook_class_methods.rb +17 -5
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/inverted_subsets.rb +2 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
- data/lib/sequel/plugins/rcte_tree.rb +6 -0
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/subset_conditions.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +5 -3
- data/lib/sequel/sql.rb +15 -3
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +24 -0
- data/spec/adapters/mysql_spec.rb +0 -5
- data/spec/adapters/postgres_spec.rb +319 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +123 -2
- data/spec/core/dataset_spec.rb +33 -1
- data/spec/core/expression_filters_spec.rb +25 -1
- data/spec/core/schema_spec.rb +24 -0
- data/spec/extensions/class_table_inheritance_spec.rb +30 -8
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/hook_class_methods_spec.rb +22 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/migration_spec.rb +13 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
- data/spec/extensions/pg_json_spec.rb +218 -29
- data/spec/extensions/pg_range_spec.rb +76 -9
- data/spec/extensions/rcte_tree_spec.rb +6 -0
- data/spec/extensions/s_spec.rb +1 -1
- data/spec/extensions/schema_dumper_spec.rb +4 -2
- data/spec/extensions/server_block_spec.rb +38 -0
- data/spec/extensions/spec_helper.rb +8 -1
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/integration/dataset_test.rb +25 -9
- data/spec/integration/plugin_test.rb +42 -0
- data/spec/integration/schema_test.rb +7 -2
- data/spec/integration/transaction_test.rb +50 -0
- data/spec/model/associations_spec.rb +84 -4
- data/spec/model/plugins_spec.rb +111 -0
- metadata +16 -2
@@ -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
|
-
|
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
|
-
|
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.
|
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(
|
33
|
-
|
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
|
26
|
-
#
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
214
|
+
@all = load_static_cache_rows
|
215
215
|
h = {}
|
216
|
-
|
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)
|