Skip to content

Commit 2a74e38

Browse files
committed
Support Database#allow_queries in the query_blocker extension
This is useful if you want to block queries more generally, and only allow them in specific places.
1 parent 5502c60 commit 2a74e38

File tree

2 files changed

+151
-23
lines changed

2 files changed

+151
-23
lines changed

lib/sequel/extensions/query_blocker.rb

+60-23
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# DB.block_queries do
1010
# ds = DB[:table] # No exception
1111
# ds = ds.where(column: 1) # No exception
12-
# ds.all # Attempts query, exception raised
12+
# ds.all # Exception raised
1313
# end
1414
#
1515
# To handle concurrency, you can pass a :scope option:
@@ -26,6 +26,25 @@
2626
# # Specific Fiber
2727
# DB.block_queries(scope: Fiber.current){}
2828
#
29+
# Database#block_queries is useful for blocking queries inside
30+
# the block. However, there may be cases where you want to
31+
# allow queries in specific places inside a block_queries block.
32+
# You can use Database#allow_queries for that:
33+
#
34+
# DB.block_queries do
35+
# DB.allow_queries do
36+
# DB[:table].all # Query allowed
37+
# end
38+
#
39+
# DB[:table].all # Exception raised
40+
# end
41+
#
42+
# When mixing block_queries and allow_queries with scopes, the
43+
# narrowest scope has priority. So if you are blocking with
44+
# :thread scope, and allowing with :fiber scope, queries in the
45+
# current fiber will be allowed, but queries in different fibers of
46+
# the current thread will be blocked.
47+
#
2948
# Note that this should catch all queries executed through the
3049
# Database instance. Whether it catches queries executed directly
3150
# on a connection object depends on the adapter in use.
@@ -64,7 +83,18 @@ def log_connection_yield(sql, conn, args=nil)
6483
# Whether queries are currently blocked.
6584
def block_queries?
6685
b = @blocked_query_scopes
67-
Sequel.synchronize{b[:global] || b[Thread.current] || b[Fiber.current]} || false
86+
b.fetch(Fiber.current) do
87+
b.fetch(Thread.current) do
88+
b.fetch(:global, false)
89+
end
90+
end
91+
end
92+
93+
# Allow queries inside the block. Only useful if they are already blocked
94+
# for the same scope. Useful for blocking queries generally, and only allowing
95+
# them in specific places. Takes the same :scope option as #block_queries.
96+
def allow_queries(opts=OPTS, &block)
97+
_allow_or_block_queries(false, opts, &block)
6898
end
6999

70100
# Reject (raise an BlockedQuery exception) if there is an attempt to execute
@@ -77,44 +107,51 @@ def block_queries?
77107
# :fiber :: Reject all queries in the current fiber.
78108
# Thread :: Reject all queries in the given thread.
79109
# Fiber :: Reject all queries in the given fiber.
80-
def block_queries(opts=OPTS)
81-
case scope = opts[:scope]
82-
when nil
83-
scope = :global
84-
when :global
85-
# nothing
86-
when :thread
87-
scope = Thread.current
88-
when :fiber
89-
scope = Fiber.current
90-
when Thread, Fiber
91-
# nothing
92-
else
93-
raise Sequel::Error, "invalid scope given to block_queries: #{scope.inspect}"
94-
end
110+
def block_queries(opts=OPTS, &block)
111+
_allow_or_block_queries(true, opts, &block)
112+
end
113+
114+
private
95115

116+
# Internals of block_queries and allow_queries.
117+
def _allow_or_block_queries(value, opts)
118+
scope = query_blocker_scope(opts)
96119
prev_value = nil
97120
scopes = @blocked_query_scopes
98121

99122
begin
100123
Sequel.synchronize do
101124
prev_value = scopes[scope]
102-
scopes[scope] = true
125+
scopes[scope] = value
103126
end
104127

105128
yield
106129
ensure
107130
Sequel.synchronize do
108-
if prev_value
109-
scopes[scope] = prev_value
110-
else
131+
if prev_value.nil?
111132
scopes.delete(scope)
133+
else
134+
scopes[scope] = prev_value
112135
end
113136
end
114137
end
115138
end
116-
117-
private
139+
140+
# The scope for the query block, either :global, or a Thread or Fiber instance.
141+
def query_blocker_scope(opts)
142+
case scope = opts[:scope]
143+
when nil
144+
:global
145+
when :global, Thread, Fiber
146+
scope
147+
when :thread
148+
Thread.current
149+
when :fiber
150+
Fiber.current
151+
else
152+
raise Sequel::Error, "invalid scope given to block_queries: #{scope.inspect}"
153+
end
154+
end
118155

119156
# Raise a BlockQuery exception if queries are currently blocked.
120157
def check_blocked_queries!

spec/extensions/query_blocker_spec.rb

+91
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,95 @@
121121
@db.block_queries{@db.block_queries?}.must_equal true
122122
@db.block_queries?.must_equal false
123123
end
124+
125+
it "#allow_queries should work outside a block_queries block" do
126+
@ds.all.must_equal []
127+
@db.allow_queries{@ds.all}.must_equal []
128+
end
129+
130+
it "#allow_queries should allow_queries inside a block_queries block" do
131+
@ds.all.must_equal []
132+
@db.block_queries do
133+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
134+
@db.allow_queries do
135+
@ds.all.must_equal []
136+
end
137+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
138+
end
139+
@ds.all.must_equal []
140+
end
141+
142+
it "scoping priority for #block_queries and #allow_queries should be fiber, thread, global, in that order" do
143+
@db.block_queries do
144+
@db.allow_queries(:scope=>:fiber) do
145+
@ds.all.must_equal []
146+
end
147+
@db.allow_queries(:scope=>:thread) do
148+
@ds.all.must_equal []
149+
end
150+
@db.allow_queries do
151+
@ds.all.must_equal []
152+
end
153+
end
154+
155+
@db.block_queries(:scope=>:thread) do
156+
@db.allow_queries(:scope=>:fiber) do
157+
@ds.all.must_equal []
158+
end
159+
@db.allow_queries(:scope=>:thread) do
160+
@ds.all.must_equal []
161+
end
162+
@db.allow_queries do
163+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
164+
end
165+
end
166+
167+
@db.block_queries(:scope=>:fiber) do
168+
@db.allow_queries(:scope=>:fiber) do
169+
@ds.all.must_equal []
170+
end
171+
@db.allow_queries(:scope=>:thread) do
172+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
173+
end
174+
@db.allow_queries do
175+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
176+
end
177+
end
178+
179+
@db.allow_queries do
180+
@db.block_queries(:scope=>:fiber) do
181+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
182+
end
183+
@db.block_queries(:scope=>:thread) do
184+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
185+
end
186+
@db.block_queries do
187+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
188+
end
189+
end
190+
191+
@db.allow_queries(:scope=>:thread) do
192+
@db.block_queries(:scope=>:fiber) do
193+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
194+
end
195+
@db.block_queries(:scope=>:thread) do
196+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
197+
end
198+
@db.block_queries do
199+
@ds.all.must_equal []
200+
end
201+
end
202+
203+
@db.allow_queries(:scope=>:fiber) do
204+
@db.block_queries(:scope=>:fiber) do
205+
proc{@ds.all}.must_raise Sequel::QueryBlocker::BlockedQuery
206+
end
207+
@db.block_queries(:scope=>:thread) do
208+
@ds.all.must_equal []
209+
end
210+
@db.block_queries do
211+
@ds.all.must_equal []
212+
end
213+
end
214+
end
124215
end

0 commit comments

Comments
 (0)