sequel 5.96.0 → 5.99.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.
- checksums.yaml +4 -4
- data/lib/sequel/adapters/amalgalite.rb +16 -1
- data/lib/sequel/adapters/postgres.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/shared/mysql.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +39 -5
- data/lib/sequel/adapters/sqlite.rb +15 -1
- data/lib/sequel/ast_transformer.rb +2 -0
- data/lib/sequel/core.rb +15 -1
- data/lib/sequel/database/misc.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +1 -0
- data/lib/sequel/dataset/actions.rb +65 -10
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/dataset/sql.rb +10 -2
- data/lib/sequel/extensions/constraint_validations.rb +3 -3
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -0
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -12
- data/lib/sequel/extensions/pg_auto_parameterize.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +2 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
- data/lib/sequel/extensions/pg_json_ops.rb +12 -5
- data/lib/sequel/extensions/pg_range.rb +11 -0
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/set_literalizer.rb +20 -39
- data/lib/sequel/extensions/split_array_nil.rb +12 -2
- data/lib/sequel/extensions/to_dot.rb +5 -0
- data/lib/sequel/model/associations.rb +7 -7
- data/lib/sequel/model/base.rb +31 -3
- data/lib/sequel/plugins/deprecated_associations.rb +151 -0
- data/lib/sequel/plugins/insert_returning_select.rb +10 -1
- data/lib/sequel/plugins/pg_array_associations.rb +2 -2
- data/lib/sequel/plugins/rcte_tree.rb +5 -5
- data/lib/sequel/plugins/split_values.rb +10 -0
- data/lib/sequel/plugins/static_cache.rb +13 -0
- data/lib/sequel/plugins/subset_static_cache.rb +15 -0
- data/lib/sequel/plugins/table_select.rb +7 -0
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- metadata +3 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The pg_auto_parameterize_duplicate_query_detection extension builds on the
|
|
4
|
+
# pg_auto_parameterize extension, adding support for detecting duplicate
|
|
5
|
+
# queries inside a block that occur at the same location. This is designed
|
|
6
|
+
# mostly to catch duplicate query issues (e.g. N+1 queries) during testing.
|
|
7
|
+
#
|
|
8
|
+
# To detect duplicate queries inside a block of code, wrap the code with
|
|
9
|
+
# +detect_duplicate_queries+:
|
|
10
|
+
#
|
|
11
|
+
# DB.detect_duplicate_queries{your_code}
|
|
12
|
+
#
|
|
13
|
+
# With this approach, if the test runs code where the same query is executed
|
|
14
|
+
# more than once with the same call stack, a
|
|
15
|
+
# Sequel::Postgres::AutoParameterizeDuplicateQueryDetection::DuplicateQueries
|
|
16
|
+
# exception will be raised.
|
|
17
|
+
#
|
|
18
|
+
# You can pass the +:warn+ option to +detect_duplicate_queries+ to warn
|
|
19
|
+
# instead of raising. Note that if the block passed to +detect_duplicate_queries+
|
|
20
|
+
# raises, this extension will warn, and raise the original exception.
|
|
21
|
+
#
|
|
22
|
+
# For more control, you can pass the +:handler+ option to
|
|
23
|
+
# +detect_duplicate_queries+. If the +:handler+ option is provided, this
|
|
24
|
+
# extension will call the +:handler+ option with the hash of duplicate
|
|
25
|
+
# query information, and will not raise or warn. This can be useful in
|
|
26
|
+
# production environments, to record duplicate queries for later analysis.
|
|
27
|
+
#
|
|
28
|
+
# For accuracy, the entire call stack is always used as part of the hash key
|
|
29
|
+
# to determine whether a query is duplicate. However, you can filter the
|
|
30
|
+
# displayed backtrace by using the +:backtrace_filter+ option.
|
|
31
|
+
#
|
|
32
|
+
# +detect_duplicate_queries+ is concurrency aware, it uses the same approach
|
|
33
|
+
# that Sequel's default connection pools use. So if you are running multiple
|
|
34
|
+
# threads, +detect_duplicate_queries+ will only report duplicate queries for
|
|
35
|
+
# the current thread (or fiber if the fiber_concurrency extension is used).
|
|
36
|
+
#
|
|
37
|
+
# When testing applications, it's probably best to use this to wrap the
|
|
38
|
+
# application being tested. For example, testing with rack-test, if your app
|
|
39
|
+
# is +App+, you would want to wrap it:
|
|
40
|
+
#
|
|
41
|
+
# include Rack::Test::Methods
|
|
42
|
+
#
|
|
43
|
+
# WrappedApp = lambda do |env|
|
|
44
|
+
# DB.detect_duplicate_queries{App.call(env)}
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# def app
|
|
48
|
+
# WrappedApp
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# It is possible to use this to wrap each separate spec using an around hook,
|
|
52
|
+
# but that can result in false positives when using libraries that have
|
|
53
|
+
# implicit retrying (such as Capybara), as you can have the same call stack
|
|
54
|
+
# for multiple requests.
|
|
55
|
+
#
|
|
56
|
+
# Related module: Sequel::Postgres::AutoParameterizeDuplicateQueryDetection
|
|
57
|
+
|
|
58
|
+
module Sequel
|
|
59
|
+
module Postgres
|
|
60
|
+
# Enable detecting duplicate queries inside a block
|
|
61
|
+
module AutoParameterizeDuplicateQueryDetection
|
|
62
|
+
def self.extended(db)
|
|
63
|
+
db.instance_exec do
|
|
64
|
+
@duplicate_query_detection_contexts = {}
|
|
65
|
+
@duplicate_query_detection_mutex = Mutex.new
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Exception class raised when duplicate queries are detected.
|
|
70
|
+
class DuplicateQueries < Sequel::Error
|
|
71
|
+
# A hash of queries that were duplicate. Keys are arrays
|
|
72
|
+
# with 2 entries, the first being the query SQL, and the
|
|
73
|
+
# second being the related call stack (backtrace).
|
|
74
|
+
# The values are the number of query executions.
|
|
75
|
+
attr_reader :queries
|
|
76
|
+
|
|
77
|
+
def initialize(message, queries)
|
|
78
|
+
@queries = queries
|
|
79
|
+
super(message)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Record each query executed so duplicates can be detected,
|
|
84
|
+
# if queries are being recorded.
|
|
85
|
+
def execute(sql, opts=OPTS, &block)
|
|
86
|
+
record, queries = duplicate_query_recorder_state
|
|
87
|
+
|
|
88
|
+
if record
|
|
89
|
+
queries[[sql.is_a?(Symbol) ? sql : sql.to_s, caller].freeze] += 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Ignore (do not record) queries inside given block. This can
|
|
96
|
+
# be useful in situations where you want to run your entire test suite
|
|
97
|
+
# with duplicate query detection, but you have duplicate queries in
|
|
98
|
+
# some parts of your application where it is not trivial to use a
|
|
99
|
+
# different approach. You can mark those specific sections with
|
|
100
|
+
# +ignore_duplicate_queries+, and still get duplicate query detection
|
|
101
|
+
# for the rest of the application.
|
|
102
|
+
def ignore_duplicate_queries(&block)
|
|
103
|
+
if state = duplicate_query_recorder_state
|
|
104
|
+
change_duplicate_query_recorder_state(state, false, &block)
|
|
105
|
+
else
|
|
106
|
+
# If we are not inside a detect_duplicate_queries block, there is
|
|
107
|
+
# no need to do anything, since we are not recording queries.
|
|
108
|
+
yield
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Run the duplicate query detector during the block.
|
|
113
|
+
# Options:
|
|
114
|
+
#
|
|
115
|
+
# :backtrace_filter :: Regexp used to filter the displayed backtrace.
|
|
116
|
+
# :handler :: If present, called with hash of duplicate query information,
|
|
117
|
+
# instead of raising or warning.
|
|
118
|
+
# :warn :: Always warn instead of raising for duplicate queries.
|
|
119
|
+
#
|
|
120
|
+
# Note that if you nest calls to this method, only the top
|
|
121
|
+
# level call will respect the passed options.
|
|
122
|
+
def detect_duplicate_queries(opts=OPTS, &block)
|
|
123
|
+
current = Sequel.current
|
|
124
|
+
if state = duplicate_query_recorder_state(current)
|
|
125
|
+
return change_duplicate_query_recorder_state(state, true, &block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@duplicate_query_detection_mutex.synchronize do
|
|
129
|
+
@duplicate_query_detection_contexts[current] = [true, Hash.new(0)]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
begin
|
|
133
|
+
yield
|
|
134
|
+
rescue Exception => e
|
|
135
|
+
raise
|
|
136
|
+
ensure
|
|
137
|
+
_, queries = @duplicate_query_detection_mutex.synchronize do
|
|
138
|
+
@duplicate_query_detection_contexts.delete(current)
|
|
139
|
+
end
|
|
140
|
+
queries.delete_if{|_,v| v < 2}
|
|
141
|
+
|
|
142
|
+
unless queries.empty?
|
|
143
|
+
if handler = opts[:handler]
|
|
144
|
+
handler.call(queries)
|
|
145
|
+
else
|
|
146
|
+
backtrace_filter = opts[:backtrace_filter]
|
|
147
|
+
backtrace_filter_note = backtrace_filter ? " (filtered)" : ""
|
|
148
|
+
query_info = queries.map do |k,v|
|
|
149
|
+
backtrace = k[1]
|
|
150
|
+
backtrace = backtrace.grep(backtrace_filter) if backtrace_filter
|
|
151
|
+
"times:#{v}\nsql:#{k[0]}\nbacktrace#{backtrace_filter_note}:\n#{backtrace.join("\n")}\n"
|
|
152
|
+
end
|
|
153
|
+
message = "duplicate queries detected:\n\n#{query_info.join("\n")}"
|
|
154
|
+
|
|
155
|
+
if e || opts[:warn]
|
|
156
|
+
warn(message)
|
|
157
|
+
else
|
|
158
|
+
raise DuplicateQueries.new(message, queries)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
# Get the query record state for the given context.
|
|
168
|
+
def duplicate_query_recorder_state(current=Sequel.current)
|
|
169
|
+
@duplicate_query_detection_mutex.synchronize{@duplicate_query_detection_contexts[current]}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Temporarily change whether to record queries for the block, resetting the
|
|
173
|
+
# previous setting after the block exits.
|
|
174
|
+
def change_duplicate_query_recorder_state(state, setting)
|
|
175
|
+
prev = state[0]
|
|
176
|
+
state[0] = setting
|
|
177
|
+
|
|
178
|
+
begin
|
|
179
|
+
yield
|
|
180
|
+
ensure
|
|
181
|
+
state[0] = prev
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
Database.register_extension(:pg_auto_parameterize_duplicate_query_detection) do |db|
|
|
188
|
+
db.extension(:pg_auto_parameterize)
|
|
189
|
+
db.extend(Postgres::AutoParameterizeDuplicateQueryDetection)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -120,7 +120,7 @@ module Sequel
|
|
|
120
120
|
# The bound variable type string to use for the bound variable array.
|
|
121
121
|
# Returns nil if a bound variable should not be used for the array.
|
|
122
122
|
def _bound_variable_type_for_array(r)
|
|
123
|
-
return unless Array === r && r.size >= pg_auto_parameterize_min_array_size
|
|
123
|
+
return unless (Array === r || Set === r) && r.size >= pg_auto_parameterize_min_array_size
|
|
124
124
|
classes = r.map(&:class)
|
|
125
125
|
classes.uniq!
|
|
126
126
|
classes.delete(NilClass)
|
|
@@ -165,6 +165,7 @@ module Sequel
|
|
|
165
165
|
|
|
166
166
|
# Convert RHS of IN/NOT IN operator to PGArray with given type.
|
|
167
167
|
def _convert_array_to_pg_array_with_type(r, type)
|
|
168
|
+
r = r.to_a if Set === r
|
|
168
169
|
Sequel.pg_array(r, type)
|
|
169
170
|
end
|
|
170
171
|
end
|
|
@@ -294,13 +294,18 @@ module Sequel
|
|
|
294
294
|
SQL::Function.new(name, self, *args)
|
|
295
295
|
end
|
|
296
296
|
|
|
297
|
-
# Wrap argument in a PGArray if it is an array
|
|
297
|
+
# Wrap argument in a PGArray if it is an array or a set
|
|
298
298
|
def wrap_input_array(obj)
|
|
299
|
-
if
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
if Sequel.respond_to?(:pg_array)
|
|
300
|
+
case obj
|
|
301
|
+
when Array
|
|
302
|
+
return Sequel.pg_array(obj)
|
|
303
|
+
when Set
|
|
304
|
+
return Sequel.pg_array(obj.to_a)
|
|
305
|
+
end
|
|
303
306
|
end
|
|
307
|
+
|
|
308
|
+
obj
|
|
304
309
|
end
|
|
305
310
|
|
|
306
311
|
# Wrap argument in an Hstore if it is a hash
|
|
@@ -841,13 +841,20 @@ module Sequel
|
|
|
841
841
|
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
|
|
842
842
|
end
|
|
843
843
|
|
|
844
|
-
# Wrap argument in a PGArray if it is an array
|
|
844
|
+
# Wrap argument in a PGArray if it is an array or a set
|
|
845
845
|
def wrap_input_array(obj)
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
obj
|
|
846
|
+
# :nocov:
|
|
847
|
+
if Sequel.respond_to?(:pg_array)
|
|
848
|
+
# :nocov:
|
|
849
|
+
case obj
|
|
850
|
+
when Array
|
|
851
|
+
return Sequel.pg_array(obj)
|
|
852
|
+
when Set
|
|
853
|
+
return Sequel.pg_array(obj.to_a)
|
|
854
|
+
end
|
|
850
855
|
end
|
|
856
|
+
|
|
857
|
+
obj
|
|
851
858
|
end
|
|
852
859
|
|
|
853
860
|
# Wrap argument in a JSONBArray or JSONBHash if it is an array or hash.
|
|
@@ -421,6 +421,17 @@ module Sequel
|
|
|
421
421
|
@exclude_end
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
+
# Support a friendly output
|
|
425
|
+
def inspect
|
|
426
|
+
range = if empty?
|
|
427
|
+
"empty"
|
|
428
|
+
else
|
|
429
|
+
"#{@exclude_begin ? "(" : "["}#{@begin},#{@end}#{@exclude_end ? ")" : "]"}"
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
"#<#{self.class.name} #{range}#{"::#{@db_type}" if @db_type}>"
|
|
433
|
+
end
|
|
434
|
+
|
|
424
435
|
# Append a literalize version of the receiver to the sql.
|
|
425
436
|
def sql_literal_append(ds, sql)
|
|
426
437
|
if (s = @db_type) && !empty?
|
|
@@ -319,8 +319,8 @@ module Sequel
|
|
|
319
319
|
end
|
|
320
320
|
|
|
321
321
|
# Typecast the given object to the appropriate type using the
|
|
322
|
-
# typecaster. Note that this does
|
|
323
|
-
# of the composite type, since those
|
|
322
|
+
# typecaster. Note that this does no conversion for the members
|
|
323
|
+
# of the composite type, since those conversions expect strings and
|
|
324
324
|
# strings may not be provided.
|
|
325
325
|
def typecast(obj)
|
|
326
326
|
case obj
|
|
@@ -1,56 +1,37 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
#
|
|
3
|
-
# The set_literalizer extension
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# DB.extension :set_literalizer
|
|
3
|
+
# The set_literalizer extension should no longer be used, as Sequel
|
|
4
|
+
# now supports Set values by default. For backwards compatibility
|
|
5
|
+
# the set_literalizer extension will treat a set that contains only
|
|
6
|
+
# 2 element arrays as a condition specifier (matching the behavior
|
|
7
|
+
# for arrays where all elements are 2 element arrays). This is not
|
|
8
|
+
# compatible with Sequel's current default behavior. If you are
|
|
9
|
+
# relying on this behavior, it is recommended you convert the set
|
|
10
|
+
# to an array.
|
|
12
11
|
#
|
|
13
12
|
# Related module: Sequel::Dataset::SetLiteralizer
|
|
14
13
|
|
|
15
|
-
require 'set'
|
|
16
|
-
|
|
17
14
|
module Sequel
|
|
15
|
+
# SEQUEL6: Remove
|
|
16
|
+
Sequel::Deprecation.deprecate("The set_literalizer extension", "Sequel now supports set literalization by default")
|
|
17
|
+
|
|
18
18
|
class Dataset
|
|
19
19
|
module SetLiteralizer
|
|
20
|
-
# Try to generate the same SQL for Set instances used in datasets
|
|
21
|
-
# that would be used for equivalent Array instances.
|
|
22
|
-
def complex_expression_sql_append(sql, op, args)
|
|
23
|
-
# Array instances are treated specially by
|
|
24
|
-
# Sequel::SQL::BooleanExpression.from_value_pairs. That cannot
|
|
25
|
-
# be modified by a dataset extension, so this tries to convert
|
|
26
|
-
# the complex expression values generated by default to what would
|
|
27
|
-
# be the complex expression values used for the equivalent array.
|
|
28
|
-
case op
|
|
29
|
-
when :'=', :'!='
|
|
30
|
-
if (set = args[1]).is_a?(Set)
|
|
31
|
-
op = op == :'=' ? :IN : :'NOT IN'
|
|
32
|
-
col = args[0]
|
|
33
|
-
array = set.to_a
|
|
34
|
-
if Sequel.condition_specifier?(array) && col.is_a?(Array)
|
|
35
|
-
array = Sequel.value_list(array)
|
|
36
|
-
end
|
|
37
|
-
args = [col, array]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
super
|
|
42
|
-
end
|
|
43
|
-
|
|
44
20
|
private
|
|
45
21
|
|
|
46
|
-
#
|
|
47
|
-
def
|
|
48
|
-
if Set
|
|
49
|
-
|
|
22
|
+
# Allow using sets as condition specifiers.
|
|
23
|
+
def filter_expr(expr = nil, &block)
|
|
24
|
+
if expr.is_a?(Set)
|
|
25
|
+
expr
|
|
50
26
|
else
|
|
51
27
|
super
|
|
52
28
|
end
|
|
53
29
|
end
|
|
30
|
+
|
|
31
|
+
# Literalize Set instances by converting the set to array.
|
|
32
|
+
def literal_set_append(sql, v)
|
|
33
|
+
literal_append(sql, v.to_a)
|
|
34
|
+
end
|
|
54
35
|
end
|
|
55
36
|
|
|
56
37
|
register_extension(:set_literalizer, SetLiteralizer)
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
# # with split_array_nils extension:
|
|
26
26
|
# # SELECT * FROM table WHERE ((column NOT IN (1)) AND (column IS NOT NULL)))
|
|
27
27
|
#
|
|
28
|
+
# In addition to arrays, this splitting is also done for sets:
|
|
29
|
+
#
|
|
30
|
+
# ds = DB[:table].where(column: Set[1, nil])
|
|
31
|
+
# # SELECT * FROM table WHERE ((column IN (1)) OR (column IS NULL)))
|
|
32
|
+
#
|
|
28
33
|
# To use this extension with a single dataset:
|
|
29
34
|
#
|
|
30
35
|
# ds = ds.extension(:split_array_nil)
|
|
@@ -47,9 +52,14 @@ module Sequel
|
|
|
47
52
|
case op
|
|
48
53
|
when :IN, :"NOT IN"
|
|
49
54
|
vals = args[1]
|
|
50
|
-
if vals.is_a?(Array) && vals.any?(&:nil?)
|
|
55
|
+
if (vals.is_a?(Array) || vals.is_a?(Set)) && vals.any?(&:nil?)
|
|
51
56
|
cols = args[0]
|
|
52
|
-
|
|
57
|
+
if vals.is_a?(Set)
|
|
58
|
+
vals = vals.dup
|
|
59
|
+
vals.delete(nil)
|
|
60
|
+
else
|
|
61
|
+
vals = vals.compact
|
|
62
|
+
end
|
|
53
63
|
c = Sequel::SQL::BooleanExpression
|
|
54
64
|
if op == :IN
|
|
55
65
|
literal_append(sql, c.new(:OR, c.new(:IN, cols, vals), c.new(:IS, cols, nil)))
|
|
@@ -302,7 +302,7 @@ module Sequel
|
|
|
302
302
|
|
|
303
303
|
if strategy == :window_function
|
|
304
304
|
delete_rn = ds.row_number_column
|
|
305
|
-
objects.each{|obj| obj.
|
|
305
|
+
objects.each{|obj| obj.remove_key!(delete_rn)}
|
|
306
306
|
end
|
|
307
307
|
elsif strategy == :union
|
|
308
308
|
objects = []
|
|
@@ -1291,11 +1291,11 @@ module Sequel
|
|
|
1291
1291
|
name = self[:name]
|
|
1292
1292
|
|
|
1293
1293
|
self[:model].eager_load_results(self, eo) do |assoc_record|
|
|
1294
|
-
assoc_record.
|
|
1294
|
+
assoc_record.remove_key!(delete_rn) if delete_rn
|
|
1295
1295
|
hash_key = if uses_lcks
|
|
1296
|
-
left_key_alias.map{|k| assoc_record.
|
|
1296
|
+
left_key_alias.map{|k| assoc_record.remove_key!(k)}
|
|
1297
1297
|
else
|
|
1298
|
-
assoc_record.
|
|
1298
|
+
assoc_record.remove_key!(left_key_alias)
|
|
1299
1299
|
end
|
|
1300
1300
|
|
|
1301
1301
|
objects = h[hash_key]
|
|
@@ -2387,7 +2387,7 @@ module Sequel
|
|
|
2387
2387
|
delete_rn = opts.delete_row_number_column
|
|
2388
2388
|
|
|
2389
2389
|
eager_load_results(opts, eo) do |assoc_record|
|
|
2390
|
-
assoc_record.
|
|
2390
|
+
assoc_record.remove_key!(delete_rn) if delete_rn
|
|
2391
2391
|
hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
|
|
2392
2392
|
objects = h[hash_key]
|
|
2393
2393
|
if assign_singular
|
|
@@ -3063,7 +3063,7 @@ module Sequel
|
|
|
3063
3063
|
if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
|
|
3064
3064
|
(multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
|
|
3065
3065
|
l = args[0]
|
|
3066
|
-
if ar = model.
|
|
3066
|
+
if ar = model.association_reflection(l)
|
|
3067
3067
|
raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
|
|
3068
3068
|
|
|
3069
3069
|
if multiple
|
|
@@ -3544,7 +3544,7 @@ module Sequel
|
|
|
3544
3544
|
else
|
|
3545
3545
|
vals = Array(obj).reject{|o| !meths.all?{|m| o.get_column_value(m)}}
|
|
3546
3546
|
return SQL::Constants::FALSE if vals.empty?
|
|
3547
|
-
if obj.is_a?(Array)
|
|
3547
|
+
if obj.is_a?(Array) || obj.is_a?(Set)
|
|
3548
3548
|
if keys.length == 1
|
|
3549
3549
|
meth = meths.first
|
|
3550
3550
|
{keys.first=>vals.map{|o| o.get_column_value(meth)}}
|
data/lib/sequel/model/base.rb
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module Sequel
|
|
4
4
|
class Model
|
|
5
|
+
# SEQUEL6: Remove Enumerable here, and send all Enumerable methods to dataset
|
|
6
|
+
# by default, with a plugin for the current behavior.
|
|
5
7
|
extend Enumerable
|
|
6
8
|
extend Inflections
|
|
7
9
|
|
|
8
10
|
# Class methods for Sequel::Model that implement basic model functionality.
|
|
9
11
|
#
|
|
10
12
|
# * All of the following methods have class methods created that send the method
|
|
11
|
-
# to the model's dataset: all, as_hash, avg, count, cross_join, distinct, each,
|
|
13
|
+
# to the model's dataset: all, any?, as_hash, as_set, avg, count, cross_join, distinct, each,
|
|
12
14
|
# each_server, empty?, except, exclude, exclude_having, fetch_rows,
|
|
13
15
|
# filter, first, first!, for_update, from, from_self, full_join, full_outer_join,
|
|
14
16
|
# get, graph, grep, group, group_and_count, group_append, group_by, having, import,
|
|
@@ -17,7 +19,7 @@ module Sequel
|
|
|
17
19
|
# natural_join, natural_left_join, natural_right_join, offset, order, order_append, order_by,
|
|
18
20
|
# order_more, order_prepend, paged_each, qualify, reverse, reverse_order, right_join,
|
|
19
21
|
# right_outer_join, select, select_all, select_append, select_group, select_hash,
|
|
20
|
-
# select_hash_groups, select_map, select_more, select_order_map, select_prepend, server,
|
|
22
|
+
# select_hash_groups, select_map, select_more, select_order_map, select_prepend, select_set, server,
|
|
21
23
|
# single_record, single_record!, single_value, single_value!, sum, to_hash, to_hash_groups,
|
|
22
24
|
# truncate, unfiltered, ungraphed, ungrouped, union, unlimited, unordered, where, where_all,
|
|
23
25
|
# where_each, where_single_value, with, with_recursive, with_sql
|
|
@@ -695,7 +697,7 @@ module Sequel
|
|
|
695
697
|
end
|
|
696
698
|
|
|
697
699
|
# Add model methods that call dataset methods
|
|
698
|
-
Plugins.def_dataset_methods(self, (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server]) - [:<<, :or, :[], :columns, :columns!, :delete, :update, :set_graph_aliases, :add_graph_aliases])
|
|
700
|
+
Plugins.def_dataset_methods(self, (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:any?, :each_server]) - [:<<, :or, :[], :columns, :columns!, :delete, :update, :set_graph_aliases, :add_graph_aliases])
|
|
699
701
|
|
|
700
702
|
private
|
|
701
703
|
|
|
@@ -1499,6 +1501,20 @@ END
|
|
|
1499
1501
|
refresh
|
|
1500
1502
|
end
|
|
1501
1503
|
|
|
1504
|
+
# Remove a key from the instances values, and return the value
|
|
1505
|
+
# of the key.
|
|
1506
|
+
#
|
|
1507
|
+
# a = Album[1]
|
|
1508
|
+
# a.values
|
|
1509
|
+
# # => {id: 1, artist_id: 2}
|
|
1510
|
+
# a.remove_key!(:artist_id)
|
|
1511
|
+
# # => 2
|
|
1512
|
+
# a.values
|
|
1513
|
+
# # => {id: 1}
|
|
1514
|
+
def remove_key!(key)
|
|
1515
|
+
@values.delete(key)
|
|
1516
|
+
end
|
|
1517
|
+
|
|
1502
1518
|
# Creates or updates the record, after making sure the record
|
|
1503
1519
|
# is valid and before hooks execute successfully. Fails if:
|
|
1504
1520
|
#
|
|
@@ -2328,5 +2344,17 @@ END
|
|
|
2328
2344
|
# :nocov:
|
|
2329
2345
|
singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
|
|
2330
2346
|
end
|
|
2347
|
+
|
|
2348
|
+
# :nocov:
|
|
2349
|
+
if defined?(Sequel::Postgres::SEQUEL_PG_VERSION_INTEGER) && Sequel::Postgres::SEQUEL_PG_VERSION_INTEGER >= 11800
|
|
2350
|
+
# Automatically optimize model loading when sequel/core was loaded,
|
|
2351
|
+
# then sequel/adapters/postgres (with sequel_pg), then sequel/model
|
|
2352
|
+
begin
|
|
2353
|
+
require "sequel_pg/model"
|
|
2354
|
+
rescue LoadError
|
|
2355
|
+
# nothing
|
|
2356
|
+
end
|
|
2357
|
+
end
|
|
2358
|
+
# :nocov:
|
|
2331
2359
|
end
|
|
2332
2360
|
end
|