@@ -13,8 +13,7 @@ def included(base) # :nodoc:
13
13
:truncate_tables , :rollback_to_savepoint , :rollback_db_transaction , :restart_db_transaction ,
14
14
:exec_insert_all
15
15
16
- base . set_callback :checkout , :after , :configure_query_cache!
17
- base . set_callback :checkin , :after , :disable_query_cache!
16
+ base . set_callback :checkin , :after , :unset_query_cache!
18
17
end
19
18
20
19
def dirties_query_cache ( base , *method_names )
@@ -29,60 +28,146 @@ def #{method_name}(...)
29
28
end
30
29
end
31
30
32
- module ConnectionPoolConfiguration
33
- def initialize ( *)
31
+ class Store # :nodoc:
32
+ attr_reader :enabled
33
+ alias_method :enabled? , :enabled
34
+
35
+ def initialize ( max_size )
36
+ @map = { }
37
+ @max_size = max_size
38
+ @enabled = false
39
+ end
40
+
41
+ def enabled = ( enabled )
42
+ clear if @enabled && !enabled
43
+ @enabled = enabled
44
+ end
45
+
46
+ def size
47
+ @map . size
48
+ end
49
+
50
+ def empty?
51
+ @map . empty?
52
+ end
53
+
54
+ def []( key )
55
+ return unless @enabled
56
+
57
+ if entry = @map . delete ( key )
58
+ @map [ key ] = entry
59
+ end
60
+ end
61
+
62
+ def compute_if_absent ( key )
63
+ return yield unless @enabled
64
+
65
+ if entry = @map . delete ( key )
66
+ return @map [ key ] = entry
67
+ end
68
+
69
+ if @max_size && @map . size >= @max_size
70
+ @map . shift # evict the oldest entry
71
+ end
72
+
73
+ @map [ key ] ||= yield
74
+ end
75
+
76
+ def clear
77
+ @map . clear
78
+ self
79
+ end
80
+ end
81
+
82
+ module ConnectionPoolConfiguration # :nodoc:
83
+ def initialize ( ...)
34
84
super
35
- @query_cache_enabled = Concurrent ::Map . new { false }
85
+ @thread_query_caches = Concurrent ::Map . new ( initial_capacity : @size )
86
+ @query_cache_max_size = \
87
+ case query_cache = db_config &.query_cache
88
+ when 0 , false
89
+ nil
90
+ when Integer
91
+ query_cache
92
+ when nil
93
+ DEFAULT_SIZE
94
+ end
95
+ end
96
+
97
+ def checkout ( ...)
98
+ connection = super
99
+ connection . query_cache ||= query_cache
100
+ connection
101
+ end
102
+
103
+ # Disable the query cache within the block.
104
+ def disable_query_cache
105
+ cache = query_cache
106
+ old , cache . enabled = cache . enabled , false
107
+ begin
108
+ yield
109
+ ensure
110
+ cache . enabled = old
111
+ end
112
+ end
113
+
114
+ def enable_query_cache
115
+ cache = query_cache
116
+ old , cache . enabled = cache . enabled , true
117
+ begin
118
+ yield
119
+ ensure
120
+ cache . enabled = old
121
+ end
36
122
end
37
123
38
124
def enable_query_cache!
39
- @query_cache_enabled [ connection_cache_key ( ActiveSupport ::IsolatedExecutionState . context ) ] = true
40
- connection . enable_query_cache! if active_connection?
125
+ query_cache . enabled = true
41
126
end
42
127
43
128
def disable_query_cache!
44
- @query_cache_enabled . delete connection_cache_key ( ActiveSupport ::IsolatedExecutionState . context )
45
- connection . disable_query_cache! if active_connection?
129
+ query_cache . enabled = false
46
130
end
47
131
48
132
def query_cache_enabled
49
- @query_cache_enabled [ connection_cache_key ( ActiveSupport :: IsolatedExecutionState . context ) ]
133
+ query_cache . enabled
50
134
end
135
+
136
+ private
137
+ def query_cache
138
+ @thread_query_caches . compute_if_absent ( connection_cache_key ( ActiveSupport ::IsolatedExecutionState . context ) ) do
139
+ Store . new ( @query_cache_max_size )
140
+ end
141
+ end
51
142
end
52
143
53
- attr_reader :query_cache , :query_cache_enabled
144
+ attr_accessor :query_cache
54
145
55
146
def initialize ( *)
56
147
super
57
- @query_cache = { }
58
- @query_cache_enabled = false
59
- @query_cache_max_size = nil
148
+ @query_cache = nil
149
+ end
150
+
151
+ def query_cache_enabled
152
+ @query_cache &.enabled?
60
153
end
61
154
62
155
# Enable the query cache within the block.
63
- def cache
64
- old , @query_cache_enabled = @query_cache_enabled , true
65
- yield
66
- ensure
67
- @query_cache_enabled = old
68
- clear_query_cache unless @query_cache_enabled
156
+ def cache ( &)
157
+ pool . enable_query_cache ( &)
69
158
end
70
159
71
160
def enable_query_cache!
72
- @query_cache_enabled = true
161
+ pool . enable_query_cache!
73
162
end
74
163
75
- def disable_query_cache!
76
- @query_cache_enabled = false
77
- clear_query_cache
164
+ # Disable the query cache within the block.
165
+ def uncached ( & )
166
+ pool . disable_query_cache ( & )
78
167
end
79
168
80
- # Disable the query cache within the block.
81
- def uncached
82
- old , @query_cache_enabled = @query_cache_enabled , false
83
- yield
84
- ensure
85
- @query_cache_enabled = old
169
+ def disable_query_cache!
170
+ pool . disable_query_cache!
86
171
end
87
172
88
173
# Clears the query cache.
@@ -93,7 +178,7 @@ def uncached
93
178
# undermining the randomness you were expecting.
94
179
def clear_query_cache
95
180
@lock . synchronize do
96
- @query_cache . clear
181
+ @query_cache & .clear
97
182
end
98
183
end
99
184
@@ -102,7 +187,7 @@ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :n
102
187
103
188
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
104
189
# Such queries should not be cached.
105
- if @query_cache_enabled && !( arel . respond_to? ( :locked ) && arel . locked )
190
+ if @query_cache &. enabled? && !( arel . respond_to? ( :locked ) && arel . locked )
106
191
sql , binds , preparable = to_sql_and_binds ( arel , binds , preparable )
107
192
108
193
if async
@@ -117,42 +202,37 @@ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :n
117
202
end
118
203
119
204
private
205
+ def unset_query_cache!
206
+ @query_cache = nil
207
+ end
208
+
120
209
def lookup_sql_cache ( sql , name , binds )
121
210
key = binds . empty? ? sql : [ sql , binds ]
122
- hit = false
123
- result = nil
124
211
212
+ result = nil
125
213
@lock . synchronize do
126
- if ( result = @query_cache . delete ( key ) )
127
- hit = true
128
- @query_cache [ key ] = result
129
- end
214
+ result = @query_cache [ key ]
130
215
end
131
216
132
- if hit
217
+ if result
133
218
ActiveSupport ::Notifications . instrument (
134
219
"sql.active_record" ,
135
220
cache_notification_info ( sql , name , binds )
136
221
)
137
-
138
- result
139
222
end
223
+
224
+ result
140
225
end
141
226
142
227
def cache_sql ( sql , name , binds )
143
228
key = binds . empty? ? sql : [ sql , binds ]
144
229
result = nil
145
- hit = false
230
+ hit = true
146
231
147
232
@lock . synchronize do
148
- if ( result = @query_cache . delete ( key ) )
149
- hit = true
150
- @query_cache [ key ] = result
151
- else
152
- result = @query_cache [ key ] = yield
153
- if @query_cache_max_size && @query_cache . size > @query_cache_max_size
154
- @query_cache . shift
155
- end
233
+ result = @query_cache . compute_if_absent ( key ) do
234
+ hit = false
235
+ yield
156
236
end
157
237
end
158
238
@@ -178,23 +258,6 @@ def cache_notification_info(sql, name, binds)
178
258
cached : true
179
259
}
180
260
end
181
-
182
- def configure_query_cache!
183
- case query_cache = pool . db_config . query_cache
184
- when 0 , false
185
- return
186
- when Integer
187
- @query_cache_max_size = query_cache
188
- when nil
189
- @query_cache_max_size = DEFAULT_SIZE
190
- else
191
- @query_cache_max_size = nil # no limit
192
- end
193
-
194
- if pool . query_cache_enabled
195
- enable_query_cache!
196
- end
197
- end
198
261
end
199
262
end
200
263
end
0 commit comments