PluginProbe
SQLite Object Cache / 1.6.0
SQLite Object Cache v1.6.0
1.6.5 trunk 0.1.7 1.0.0 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.4.0 1.4.1 1.5.1 1.5.4 1.5.5 1.5.6 1.5.7 All 30 releases
← All changes | assets/drop-in/object-cache.php +2573 -2731 trunk1.6.0 View file →
@@ -1,8 +1,8 @@
1 1 <?php
2 2 /**
3 3 * Plugin Name: SQLite Object Cache (Drop-in)
4 - * Version: 1.6.5
4 + * Version: 1.6.0
5 5 * Note: This Version number must match the one in SQLite_Object_Cache::_construct.
6 6 * Plugin URI: https://wordpress.org/plugins/sqlite-object-cache/
7 7 * Description: A persistent object cache backend powered by SQLite3.
8 8 * Author: Oliver Jones
@@ -9,10 +9,10 @@
9 9 * Author URI: https://plumislandmedia.net
10 10 * License: GPLv2+
11 11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 12 * Requires PHP: 5.6
13 - * Tested up to: 7.1
14 - * Stable tag: 1.6.5
13 + * Tested up to: 6.9
14 + * Stable tag: 1.6.0
15 15 *
16 16 * NOTE: This uses the file .../wp-content/.ht.object_cache.sqlite
17 17 * and the associated files .../wp-content/.ht.object_cache.sqlite-shm
18 18 * and .../wp-content/.ht.object_cache.sqlite-wal to hold cached data.
@@ -31,9 +31,8 @@
31 31 * It can be DELETE | TRUNCATE | PERSIST | MEMORY | WAL. See https://www.sqlite.org/pragma.html#pragma_journal_mode.
32 32 * WP_SQLITE_OBJECT_CACHE_INTKEY_LENGTH is the number of digits for optimizing consecutive integer cache keys, default 6.
33 33 * WP_SQLITE_OBJECT_CACHE_INTKEY_ERODE_GAPS allows fewer SQL statements but can retrieve extra items, default 2.
34 34 * WP_SQLITE_OBJECT_CACHE_MMAP_SIZE sets SQLite's mmap_size in MiB. Default 0: disabled.
35 - * WP_SQLITE_OBJECT_CACHE_CHECKPOINT_FREQ. How often, probabilistically, to force checkpointing SQLite3. Make this number smaller on busy sites if your WAL file gets too long. Default 5000.
36 35 *
37 36 * Credit: Till Krüss's https://wordpress.org/plugins/redis-cache/ plugin. Thanks, Till!
38 37 *
39 38 * @package SQLiteCache
@@ -40,15 +39,11 @@
40 39 */
41 40
42 41 /** @noinspection SqlDialectInspection */
43 42
44 -if ( ! defined( 'ABSPATH' ) ) {
45 - exit;
46 -}
43 +use JetBrains\PhpStorm\NoReturn;
47 44
48 -if ( defined( 'WP_SQLITE_OBJECT_CACHE_DISABLED' ) && WP_SQLITE_OBJECT_CACHE_DISABLED ) {
49 - return;
50 -}
45 +defined( '\\ABSPATH' ) || exit;
51 46
52 47 /**
53 48 * hrtime polyfill if needed, pre php 7.3.
54 49 */
@@ -62,756 +57,720 @@
62 57 return $result;
63 58 }
64 59 }
65 60
66 -/**
67 - * Object Cache API: WP_Object_Cache class, reworked for SQLite3 drop-in.
68 - *
69 - * NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3.
70 - *
71 - * @package WordPress
72 - * @subpackage Cache
73 - * @since 5.4.0
74 - */
61 +// phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect
62 +if ( ! defined( 'WP_SQLITE_OBJECT_CACHE_DISABLED' ) || ! WP_SQLITE_OBJECT_CACHE_DISABLED ) :
75 63
76 -/**
77 - * Core class that implements an object cache.
78 - *
79 - * The WordPress Object Cache is used to save on trips to the database. The
80 - * Object Cache stores cache data to memory and makes the cache
81 - * contents available by using a key, which is used to name and later retrieve
82 - * the cache contents.
83 - *
84 - * This module is a drop-in, placed in the WP_CONTENT folder, implementing
85 - * the WordPress Object Cache class, while using SQLite3 for persistent storage.
86 - *
87 - * @since 0.1.0
88 - */
89 -class WP_Object_Cache {
90 - const OBJECT_STATS_TABLE = 'object_stats';
91 - const OBJECT_CACHE_TABLE = 'object_cache';
92 - const OBJECT_FLAGS_TABLE = 'object_flags';
93 - const NOEXPIRE_TIMESTAMP_OFFSET = 500000000000;
94 - const INTKEY_LENGTH = 6;
95 - const MMAP_SIZE = 0.0;
96 - const INTKEY_ERODE_GAPS = 2;
97 - const INTKEY_SENTINEL = "\x1f"; /* Only one character allowed here. */
98 - const SQLITE_TIMEOUT = 5000;
99 - const SQLITE_FILENAME = '.ht.object-cache.sqlite';
100 - const JOURNAL_MODE = 'WAL'; /* or 'MEMORY' */
101 - const TRANSACTION_SIZE_LIMIT = 64;
102 - const CHECKPOINT_FREQ = 2000;
103 -
104 - private $dropin_version = '1.6.5';
105 - /** @var bool True if a transaction is active. */
106 - private $transaction_active = false;
107 - /** Path to SQLite file. @var string */
108 - public $sqlite_path;
109 -
110 64 /**
111 - * @var string|null Version of SQLite3 software in use.
112 - */
113 - private $sqlite_version;
114 -
115 - /**
116 - * @var int Checkpoint frequency. Probabilistic.
117 - */
118 - private $checkpoint_freq;
119 -
120 - /**
121 - * SQLite's journal mode.
65 + * Object Cache API: WP_Object_Cache class, reworked for SQLite3 drop-in.
122 66 *
123 - * Avoid the OFF journal mode, especially in pre-3.24 versions of SQLite.
67 + * NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3.
124 68 *
125 - * @see https://www.sqlite.org/pragma.html#pragma_journal_mode
126 - *
127 - * @var string MEMORY, WAL, DELETE, TRUNCATE, PERSIST, OFF
69 + * @package WordPress
70 + * @subpackage Cache
71 + * @since 5.4.0
128 72 */
129 - private $sqlite_journal_mode;
73 +
130 74 /**
131 - * Timeout waiting for transaction completion.
75 + * Core class that implements an object cache.
132 76 *
133 - * @var int
134 - */
135 - private $sqlite_timeout;
136 - /**
137 - * The amount of times the cache data was already stored in the cache.
77 + * The WordPress Object Cache is used to save on trips to the database. The
78 + * Object Cache stores cache data to memory and makes the cache
79 + * contents available by using a key, which is used to name and later retrieve
80 + * the cache contents.
138 81 *
139 - * @since 2.5.0
140 - * @var int
141 - */
142 - public $cache_hits = 0;
143 - /**
144 - * Amount of times the cache did not have the request in cache.
82 + * This module is a drop-in, placed in the WP_CONTENT folder, implementing
83 + * the WordPress Object Cache class, while using SQLite3 for persistent storage.
145 84 *
146 - * @since 2.0.0
147 - * @var int
85 + * @since 0.1.0
148 86 */
149 - public $cache_misses = 0;
150 - /**
151 - * The amount of times the cache data was already stored in the persistent cache.
152 - *
153 - * @since 2.5.0
154 - * @var int
155 - */
156 - public $persistent_hits = 0;
157 - /**
158 - * Amount of times the cache did not have the request in persistent cache.
159 - *
160 - * @since 2.0.0
161 - * @var int
162 - */
163 - public $persistent_misses = 0;
164 - /**
165 - * Amount of times the apcu cache had the item.
166 - *
167 - * @since 2.0.0
168 - * @var int
169 - */
170 - public $apcu_hits = 0;
171 - /**
172 - * Amount of times the apcu cache did not have the item.
173 - *
174 - * @since 2.0.0
175 - * @var int
176 - */
177 - public $apcu_misses = 0;
178 - /**
179 - * The blog prefix to prepend to keys in non-global groups.
180 - *
181 - * @since 3.5.0
182 - * @var string For multisite, n:, For single site, empty.
183 - */
184 - public $blog_prefix;
185 - /**
186 - * List of groups that will not be flushed.
187 - *
188 - * @var array
189 - */
190 - public $unflushable_groups = array();
191 - /**
192 - * List of groups not saved to cache.
193 - *
194 - * @var array
195 - */
196 - public $ignored_groups = array(
197 - 'counts',
198 - 'plugins',
199 - 'themes',
200 - );
201 - /**
202 - * List of groups and their types.
203 - *
204 - * @var array
205 - */
206 - public $group_type = array();
207 - /**
208 - * Prefix used for global groups.
209 - *
210 - * @var string
211 - */
212 - public $global_prefix = '';
213 - /**
214 - * List of global groups.
215 - *
216 - * @var array
217 - */
218 - protected $global_groups = array(
219 - 'blog-details',
220 - 'blog-id-cache',
221 - 'blog-lookup',
222 - 'global-posts',
223 - 'networks',
224 - 'rss',
225 - 'sites',
226 - 'site-details',
227 - 'site-lookup',
228 - 'site-options',
229 - 'site-transient',
230 - 'users',
231 - 'useremail',
232 - 'userlogins',
233 - 'usermeta',
234 - 'user_meta',
235 - 'userslugs',
236 - );
87 + class WP_Object_Cache {
88 + const OBJECT_STATS_TABLE = 'object_stats';
89 + const OBJECT_CACHE_TABLE = 'object_cache';
90 + const OBJECT_FLAGS_TABLE = 'object_flags';
91 + const NOEXPIRE_TIMESTAMP_OFFSET = 500000000000;
92 + const INTKEY_LENGTH = 6;
93 + const MMAP_SIZE = 0.0;
94 + const INTKEY_ERODE_GAPS = 2;
95 + const INTKEY_SENTINEL = "\x1f"; /* Only one character allowed here. */
96 + const SQLITE_TIMEOUT = 5000;
97 + const SQLITE_FILENAME = '.ht.object-cache.sqlite';
98 + const JOURNAL_MODE = 'WAL'; /* or 'MEMORY' */
99 + const TRANSACTION_SIZE_LIMIT = 64;
237 100
238 - /**
239 - * @var array One-level associative array $name=>$value
240 - */
241 - private $cache = array();
242 - /**
243 - * Holds the value of is_multisite().
244 - *
245 - * @since 3.5.0
246 - * @var bool
247 - */
248 - private $multisite;
101 + private $dropin_version = '1.6.0';
102 + /** @var bool True if a transaction is active. */
103 + private $transaction_active = false;
104 + /** Path to SQLite file. @var string */
105 + public $sqlite_path;
249 106
250 - /**
251 - * Prepared statement to get one cache element.
252 - *
253 - * @var SQLite3Stmt SELECT statement.
254 - */
255 - private $getone_stmt;
107 + /**
108 + * @var string|null Version of SQLite3 software in use.
109 + */
110 + private $sqlite_version;
256 111
257 - /**
258 - * Prepared statement to get a range of cache elements, for get_multiple.
259 - *
260 - * @var SQLite3Stmt SELECT statement.
261 - */
262 - private $getrange_stmt;
112 + /**
113 + * SQLite's journal mode.
114 + *
115 + * Avoid the OFF journal mode, especially in pre-3.24 versions of SQLite.
116 + *
117 + * @see https://www.sqlite.org/pragma.html#pragma_journal_mode
118 + *
119 + * @var string MEMORY, WAL, DELETE, TRUNCATE, PERSIST, OFF
120 + */
121 + private $sqlite_journal_mode;
122 + /**
123 + * Timeout waiting for transaction completion.
124 + *
125 + * @var int
126 + */
127 + private $sqlite_timeout;
128 + /**
129 + * The amount of times the cache data was already stored in the cache.
130 + *
131 + * @since 2.5.0
132 + * @var int
133 + */
134 + public $cache_hits = 0;
135 + /**
136 + * Amount of times the cache did not have the request in cache.
137 + *
138 + * @since 2.0.0
139 + * @var int
140 + */
141 + public $cache_misses = 0;
142 + /**
143 + * The amount of times the cache data was already stored in the persistent cache.
144 + *
145 + * @since 2.5.0
146 + * @var int
147 + */
148 + public $persistent_hits = 0;
149 + /**
150 + * Amount of times the cache did not have the request in persistent cache.
151 + *
152 + * @since 2.0.0
153 + * @var int
154 + */
155 + public $persistent_misses = 0;
156 + /**
157 + * Amount of times the apcu cache had the item.
158 + *
159 + * @since 2.0.0
160 + * @var int
161 + */
162 + public $apcu_hits = 0;
163 + /**
164 + * Amount of times the apcu cache did not have the item.
165 + *
166 + * @since 2.0.0
167 + * @var int
168 + */
169 + public $apcu_misses = 0;
170 + /**
171 + * The blog prefix to prepend to keys in non-global groups.
172 + *
173 + * @since 3.5.0
174 + * @var string For multisite, n:, For single site, empty.
175 + */
176 + public $blog_prefix;
177 + /**
178 + * List of groups that will not be flushed.
179 + *
180 + * @var array
181 + */
182 + public $unflushable_groups = array();
183 + /**
184 + * List of groups not saved to cache.
185 + *
186 + * @var array
187 + */
188 + public $ignored_groups = array(
189 + 'counts',
190 + 'plugins',
191 + 'themes',
192 + );
193 + /**
194 + * List of groups and their types.
195 + *
196 + * @var array
197 + */
198 + public $group_type = array();
199 + /**
200 + * Prefix used for global groups.
201 + *
202 + * @var string
203 + */
204 + public $global_prefix = '';
205 + /**
206 + * List of global groups.
207 + *
208 + * @var array
209 + */
210 + protected $global_groups = array(
211 + 'blog-details',
212 + 'blog-id-cache',
213 + 'blog-lookup',
214 + 'global-posts',
215 + 'networks',
216 + 'rss',
217 + 'sites',
218 + 'site-details',
219 + 'site-lookup',
220 + 'site-options',
221 + 'site-transient',
222 + 'users',
223 + 'useremail',
224 + 'userlogins',
225 + 'usermeta',
226 + 'user_meta',
227 + 'userslugs',
228 + );
263 229
264 - /**
265 - * Prepared statement to delete one cache element.
266 - *
267 - * @var SQLite3Stmt DELETE statement.
268 - */
269 - private $deleteone_stmt;
230 + /**
231 + * @var array One-level associative array $name=>$value
232 + */
233 + private $cache = array();
234 + /**
235 + * Holds the value of is_multisite().
236 + *
237 + * @since 3.5.0
238 + * @var bool
239 + */
240 + private $multisite;
270 241
271 - /**
272 - * Prepared statement to delete a group of cache elements.
273 - *
274 - * @var SQLite3Stmt
275 - */
276 - private $deletegroup_stmt;
242 + /**
243 + * Prepared statement to get one cache element.
244 + *
245 + * @var SQLite3Stmt SELECT statement.
246 + */
247 + private $getone_stmt;
277 248
278 - /**
279 - * Prepared statement to upsert one cache element.
280 - *
281 - * @var SQLite3Stmt
282 - */
283 - private $upsertone_stmt;
249 + /**
250 + * Prepared statement to get a range of cache elements, for get_multiple.
251 + *
252 + * @var SQLite3Stmt SELECT statement.
253 + */
254 + private $getrange_stmt;
284 255
285 - /**
286 - * Prepared statement to insert one cache element.
287 - *
288 - * @var SQLite3Stmt
289 - */
290 - private $insertone_stmt;
256 + /**
257 + * Prepared statement to delete one cache element.
258 + *
259 + * @var SQLite3Stmt DELETE statement.
260 + */
261 + private $deleteone_stmt;
291 262
292 - /**
293 - * Prepared statement to update one cache element.
294 - *
295 - * @var SQLite3Stmt
296 - */
297 - private $updateone_stmt;
263 + /**
264 + * Prepared statement to delete a group of cache elements.
265 + *
266 + * @var SQLite3Stmt
267 + */
268 + private $deletegroup_stmt;
298 269
299 - /**
300 - * Prepared statement to clear a flagt.
301 - *
302 - * @var SQLite3Stmt
303 - */
304 - private $clearflag_stmt;
270 + /**
271 + * Prepared statement to upsert one cache element.
272 + *
273 + * @var SQLite3Stmt
274 + */
275 + private $upsertone_stmt;
305 276
306 - /**
307 - * Prepared statement to set a flagt.
308 - *
309 - * @var SQLite3Stmt
310 - */
311 - private $setflag_stmt;
277 + /**
278 + * Prepared statement to insert one cache element.
279 + *
280 + * @var SQLite3Stmt
281 + */
282 + private $insertone_stmt;
312 283
313 - /**
314 - * Associative array of items we know ARE NOT in SQLite.
315 - *
316 - * When a name is not in this array it means we don't know if it is in SQLite or not.
317 - *
318 - * @var array Keys are cached item names. Values are true.
319 - */
320 - private $not_in_persistent_cache = array();
321 - /**
322 - * Cache table name.
323 - *
324 - * @var string Usually 'object_cache'.
325 - */
326 - private $cache_table_name;
327 - /**
328 - * Flags table name.
329 - *
330 - * @var string Usually 'object_flags'.
331 - */
332 - private $flags_table_name;
333 - /**
334 - * Flag for availability of igbinary serialization extension.
335 - * This will be false if igbinary is not available or if WP_SQLITE_OBJECT_CACHE_SERIALIZE is true.
336 - *
337 - * @var bool true if it is available.
338 - */
339 - private $has_igbinary;
340 - /**
341 - * The expiration time of non-expiring cache entries has this added to the timestamp.
342 - *
343 - * This is a sentinel value, marking a non-expiring cache entry AND
344 - * recording when it was inserted or updated.
345 - * It allows a least-recently-changed cache-entry purging strategy.
346 - *
347 - * If we wanted a least-recently-used purge, we would need to
348 - * update each cache item's row whenever we accessed it. That
349 - * would cost more than it's worth.
350 - *
351 - * @var int a large number of seconds, much larger than 2**32
352 - */
353 - private $noexpire_timestamp_offset;
354 - /**
355 - * The starting time of the request, from time().
356 - * @var
357 - */
358 - private $start_time;
359 - /**
360 - * The starting time of the request, from hrtime().
361 - * @var
362 - */
363 - private $start_hrtime;
364 - /**
365 - * An array of overall get times, excluding RAM cache.
366 - * @var array
367 - */
368 - private $get_times = array();
369 - /**
370 - * An array of elapsed times for each cache-retrieval operation.
371 - *
372 - * @var array[float]
373 - */
374 - private $select_times = array();
375 - /**
376 - * An array of elapsed times for each cache-insertion / update operation.
377 - *
378 - * @var array[float]
379 - */
380 - private $insert_times = array();
381 - /**
382 - * An array of elapsed times for each single-row cache deletion operation.
383 - *
384 - * @var array[float]
385 - */
386 - private $delete_times = array();
284 + /**
285 + * Prepared statement to update one cache element.
286 + *
287 + * @var SQLite3Stmt
288 + */
289 + private $updateone_stmt;
387 290
388 - /**
389 - * The times for individual checkpoint -- PRAGMA wal_checkpoint(RESTART) -- times
390 - * @var array
391 - */
392 - private $checkpoint_times = array();
393 - /**
394 - * The times for individual get_multiple operations.
395 - *
396 - * @var array[float]
397 - */
398 - private $get_multiple_times = array();
399 - /**
400 - * The times for apcu_store operations.
401 - * @var array
402 - */
403 - private $apcu_fetch_hit_times = array();
404 - /**
405 - * The times for apcu_store operations.
406 - * @var array
407 - */
408 - private $apcu_fetch_miss_times = array();
409 - /**
410 - * The times for apcu_store operations.
411 - * @var array
412 - */
413 - private $apcu_store_times = array();
291 + /**
292 + * Prepared statement to clear a flagt.
293 + *
294 + * @var SQLite3Stmt
295 + */
296 + private $clearflag_stmt;
414 297
415 - /**
416 - * The humber of keys for individual get_multiple operations.
417 - *
418 - * @var array[int]
419 - */
420 - private $get_multiple_keys = array();
421 - /**
422 - * The time it took to open the db.
423 - *
424 - * @var float
425 - */
426 - private $open_time;
298 + /**
299 + * Prepared statement to set a flagt.
300 + *
301 + * @var SQLite3Stmt
302 + */
303 + private $setflag_stmt;
427 304
428 - /**
429 - * Monitoring options for the SQLite cache.
430 - *
431 - * Options in array [
432 - * 'capture' => (bool)
433 - * 'resolution' => how often in seconds (float)
434 - * 'lifetime' => how long until entries expire in seconds (int)
435 - * 'verbose' => (bool) capture extra stuff.
436 - * ]
437 - *
438 - * @var array $options Option list.
439 - */
440 - private $monitoring_options;
305 + /**
306 + * Associative array of items we know ARE NOT in SQLite.
307 + *
308 + * When a name is not in this array it means we don't know if it is in SQLite or not.
309 + *
310 + * @var array Keys are cached item names. Values are true.
311 + */
312 + private $not_in_persistent_cache = array();
313 + /**
314 + * Cache table name.
315 + *
316 + * @var string Usually 'object_cache'.
317 + */
318 + private $cache_table_name;
319 + /**
320 + * Flags table name.
321 + *
322 + * @var string Usually 'object_flags'.
323 + */
324 + private $flags_table_name;
325 + /**
326 + * Flag for availability of igbinary serialization extension.
327 + * This will be false if igbinary is not available or if WP_SQLITE_OBJECT_CACHE_SERIALIZE is true.
328 + *
329 + * @var bool true if it is available.
330 + */
331 + private $has_igbinary;
332 + /**
333 + * The expiration time of non-expiring cache entries has this added to the timestamp.
334 + *
335 + * This is a sentinel value, marking a non-expiring cache entry AND
336 + * recording when it was inserted or updated.
337 + * It allows a least-recently-changed cache-entry purging strategy.
338 + *
339 + * If we wanted a least-recently-used purge, we would need to
340 + * update each cache item's row whenever we accessed it. That
341 + * would cost more than it's worth.
342 + *
343 + * @var int a large number of seconds, much larger than 2**32
344 + */
345 + private $noexpire_timestamp_offset;
346 + /**
347 + * The starting time of the request.
348 + * @var
349 + */
350 + private $start_time;
351 + /**
352 + * An array of overall get times, excluding RAM cache.
353 + * @var array
354 + */
355 + private $get_times = array();
356 + /**
357 + * An array of elapsed times for each cache-retrieval operation.
358 + *
359 + * @var array[float]
360 + */
361 + private $select_times = array();
362 + /**
363 + * An array of elapsed times for each cache-insertion / update operation.
364 + *
365 + * @var array[float]
366 + */
367 + private $insert_times = array();
368 + /**
369 + * An array of elapsed times for each single-row cache deletion operation.
370 + *
371 + * @var array[float]
372 + */
373 + private $delete_times = array();
441 374
442 - /**
443 - * Recursion count.
444 - *
445 - * @var int Recursion in the get command.
446 - */
447 - private $get_depth = 31;
448 - /**
449 - * Database object.
450 - * @var SQLite3 instance.
451 - */
452 - private $sqlite;
453 - /**
454 - * @var int The max number of digits in optimized integer cache keys.
455 - *
456 - * Longer integers than this are treated as text.
457 - */
458 - private $intkey_length;
459 - /**
460 - * @var int The maximum value of integer keys before we handle them as strings.
461 - *
462 - * Longer integers than this are treated as text.
463 - */
464 - private $intkey_max;
465 - /**
466 - * @var int Erode gaps in consecutive runs of integers by this amount.
467 - *
468 - * This makes for fewer SQL queries at the cost of some extra retrieved items.
469 - */
470 - private $erode_gaps;
375 + /**
376 + * The times for individual checkpoint -- PRAGMA wal_checkpoint(RESTART) -- times
377 + * @var array
378 + */
379 + private $checkpoint_times = array();
380 + /**
381 + * The times for individual get_multiple operations.
382 + *
383 + * @var array[float]
384 + */
385 + private $get_multiple_times = array();
386 + /**
387 + * The times for apcu_store operations.
388 + * @var array
389 + */
390 + private $apcu_fetch_hit_times = array();
391 + /**
392 + * The times for apcu_store operations.
393 + * @var array
394 + */
395 + private $apcu_fetch_miss_times = array();
396 + /**
397 + * The times for apcu_store operations.
398 + * @var array
399 + */
400 + private $apcu_store_times = array();
471 401
472 - /**
473 - * @var int mmap_size setting for SQLite. Zero to disable.
474 - */
475 - private $mmap_size = 0;
476 - /**
477 - * The APCu cache is active in this request
478 - * @var bool
479 - */
480 - private $apcu_active = false;
481 - /**
482 - * The APCu cache is active in this site, but not in this request.
483 - *
484 - * This happens for wp-cli programs.
485 - * @var bool
486 - */
487 - private $apcu_supported = false;
488 - private $salt;
489 - /**
490 - * @var string The APCu prefix, ending with '|'.
491 - */
492 - public $apcusalt;
402 + /**
403 + * The humber of keys for individual get_multiple operations.
404 + *
405 + * @var array[int]
406 + */
407 + private $get_multiple_keys = array();
408 + /**
409 + * The time it took to open the db.
410 + *
411 + * @var float
412 + */
413 + private $open_time;
493 414
494 - /**
495 - * Constructor for SQLite Object Cache.
496 - *
497 - * @since 2.0.8
498 - */
499 - public function __construct() {
500 - $this->start_hrtime = hrtime( true );
501 - $this->start_time = time();
502 - global $table_prefix;
503 - $this->cache_group_types();
415 + /**
416 + * Monitoring options for the SQLite cache.
417 + *
418 + * Options in array [
419 + * 'capture' => (bool)
420 + * 'resolution' => how often in seconds (float)
421 + * 'lifetime' => how long until entries expire in seconds (int)
422 + * 'verbose' => (bool) capture extra stuff.
423 + * ]
424 + *
425 + * @var array $options Option list.
426 + */
427 + private $monitoring_options;
504 428
505 - /* The environment. */
506 - $apc = defined( 'WP_SQLITE_OBJECT_CACHE_APCU' ) && WP_SQLITE_OBJECT_CACHE_APCU;
507 - $cli = defined( 'WP_CLI' ) && WP_CLI;
508 - $this->apcu_active = $apc && function_exists( 'apcu_enabled' ) && apcu_enabled() && ! $cli;
509 - $this->apcu_supported = $apc && $cli;
429 + /**
430 + * Recursion count.
431 + *
432 + * @var int Recursion in the get command.
433 + */
434 + private $get_depth = 31;
435 + /**
436 + * Database object.
437 + * @var SQLite3 instance.
438 + */
439 + private $sqlite;
440 + /**
441 + * @var int The max number of digits in optimized integer cache keys.
442 + *
443 + * Longer integers than this are treated as text.
444 + */
445 + private $intkey_length;
446 + /**
447 + * @var int The maximum value of integer keys before we handle them as strings.
448 + *
449 + * Longer integers than this are treated as text.
450 + */
451 + private $intkey_max;
452 + /**
453 + * @var int Erode gaps in consecutive runs of integers by this amount.
454 + *
455 + * This makes for fewer SQL queries at the cost of some extra retrieved items.
456 + */
457 + private $erode_gaps;
510 458
511 - $force_serialize = defined( 'WP_SQLITE_OBJECT_CACHE_SERIALIZE' ) && WP_SQLITE_OBJECT_CACHE_SERIALIZE;
512 - $this->has_igbinary = function_exists( 'igbinary_serialize' ) && ! $force_serialize;
513 - $this->salt = defined( 'WP_CACHE_KEY_SALT' )
514 - ? preg_replace( '/[^-_A-Za-z0-9]/', '_', WP_CACHE_KEY_SALT )
515 - : '';
516 - if ( $this->apcu_active ) {
517 - /* As unique as possible to avoid collisions with other instances on the same server. */
518 - $this->apcusalt = ( ( '' !== $this->salt )
519 - ? $this->salt
520 - : substr( base64_encode( md5( $this->salt . $table_prefix . DB_HOST . DB_USER . DB_NAME . AUTH_KEY . AUTH_SALT ) ),
521 - 0, 12 ) ) . '|';
459 + /**
460 + * @var int mmap_size setting for SQLite. Zero to disable.
461 + */
462 + private $mmap_size = 0;
463 + /**
464 + * The APCu cache is active in this request
465 + * @var bool
466 + */
467 + private $apcu_active = false;
468 + /**
469 + * The APCu cache is active in this site, but not in this request.
470 + *
471 + * This happens for wp-cli programs.
472 + * @var bool
473 + */
474 + private $apcu_supported = false;
475 + private $salt;
476 + /**
477 + * @var string
478 + */
479 + public $apcusalt;
522 480
523 - }
524 - $this->sqlite_path = $this->create_database_path();
481 + /**
482 + * Constructor for SQLite Object Cache.
483 + *
484 + * @since 2.0.8
485 + */
486 + public function __construct() {
487 + $this->start_time = hrtime( true );
488 + global $table_prefix;
489 + $this->cache_group_types();
525 490
526 - $this->checkpoint_freq = 4 * ( defined( 'WP_SQLITE_OBJECT_CACHE_CHECKPOINT_FREQ' )
527 - ? (int) WP_SQLITE_OBJECT_CACHE_CHECKPOINT_FREQ
528 - : self::CHECKPOINT_FREQ );
491 + /* The environment. */
492 + $apc = defined( 'WP_SQLITE_OBJECT_CACHE_APCU' ) && WP_SQLITE_OBJECT_CACHE_APCU;
493 + $cli = defined( 'WP_CLI' ) && WP_CLI;
494 + $this->apcu_active = $apc && function_exists( 'apcu_enabled' ) && apcu_enabled() && ! $cli;
495 + $this->apcu_supported = $apc && $cli;
529 496
530 - $this->sqlite_timeout = defined( 'WP_SQLITE_OBJECT_CACHE_TIMEOUT' )
531 - ? WP_SQLITE_OBJECT_CACHE_TIMEOUT
532 - : self::SQLITE_TIMEOUT;
497 + $force_serialize = defined( 'WP_SQLITE_OBJECT_CACHE_SERIALIZE' ) && WP_SQLITE_OBJECT_CACHE_SERIALIZE;
498 + $this->has_igbinary = function_exists( 'igbinary_serialize' ) && ! $force_serialize;
499 + $this->salt = defined( 'WP_CACHE_KEY_SALT' )
500 + ? preg_replace( '/[^-_A-Za-z0-9]/', '_', WP_CACHE_KEY_SALT )
501 + : '';
502 + if ( $this->apcu_active ) {
503 + /* As unique as possible to avoid collisions with other instances on the same server. */
504 + $this->apcusalt = ( ( '' !== $this->salt )
505 + ? $this->salt
506 + : substr( base64_encode( md5( $this->salt . $table_prefix . DB_HOST . DB_USER . DB_NAME . AUTH_KEY . AUTH_SALT ) ),
507 + 0, 12 ) ) . '|';
533 508
534 - $this->sqlite_journal_mode = defined( 'WP_SQLITE_OBJECT_CACHE_JOURNAL_MODE' )
535 - ? WP_SQLITE_OBJECT_CACHE_JOURNAL_MODE
536 - : self::JOURNAL_MODE;
509 + }
510 + $this->sqlite_path = $this->create_database_path();
537 511
538 - $this->erode_gaps = defined( 'WP_SQLITE_OBJECT_CACHE_INTKEY_ERODE_GAPS' )
539 - ? (int) WP_SQLITE_OBJECT_CACHE_INTKEY_ERODE_GAPS
540 - : self::INTKEY_ERODE_GAPS;
512 + $this->sqlite_timeout = defined( 'WP_SQLITE_OBJECT_CACHE_TIMEOUT' )
513 + ? WP_SQLITE_OBJECT_CACHE_TIMEOUT
514 + : self::SQLITE_TIMEOUT;
541 515
542 - $this->intkey_length = defined( 'WP_SQLITE_OBJECT_CACHE_INTKEY_LENGTH' )
543 - ? (int) WP_SQLITE_OBJECT_CACHE_INTKEY_LENGTH
544 - : self::INTKEY_LENGTH;
516 + $this->sqlite_journal_mode = defined( 'WP_SQLITE_OBJECT_CACHE_JOURNAL_MODE' )
517 + ? WP_SQLITE_OBJECT_CACHE_JOURNAL_MODE
518 + : self::JOURNAL_MODE;
545 519
546 - $this->intkey_max = - 1 + (int) str_pad( '1', 1 + $this->intkey_length, 0, STR_PAD_RIGHT );
520 + $this->erode_gaps = defined( 'WP_SQLITE_OBJECT_CACHE_INTKEY_ERODE_GAPS' )
521 + ? (int) WP_SQLITE_OBJECT_CACHE_INTKEY_ERODE_GAPS
522 + : self::INTKEY_ERODE_GAPS;
547 523
548 - $this->mmap_size = defined( 'WP_SQLITE_OBJECT_CACHE_MMAP_SIZE' )
549 - ? (int) WP_SQLITE_OBJECT_CACHE_MMAP_SIZE
550 - : self::MMAP_SIZE;
551 - $this->mmap_size = (int) $this->mmap_size * 1024 * 1024;
524 + $this->intkey_length = defined( 'WP_SQLITE_OBJECT_CACHE_INTKEY_LENGTH' )
525 + ? (int) WP_SQLITE_OBJECT_CACHE_INTKEY_LENGTH
526 + : self::INTKEY_LENGTH;
552 527
553 - $this->multisite = is_multisite();
554 - $this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : '';
555 - $this->cache_table_name = self::OBJECT_CACHE_TABLE;
556 - $this->flags_table_name = self::OBJECT_FLAGS_TABLE;
557 - $this->noexpire_timestamp_offset = self::NOEXPIRE_TIMESTAMP_OFFSET;
558 - $this->open_connection();
528 + $this->intkey_max = - 1 + (int) str_pad( '1', 1 + $this->intkey_length, 0, STR_PAD_RIGHT );
559 529
560 - /* If wp-cli code cached something into SQLite, clear the APCu cache because it's stale. */
561 - if ( $this->apcu_active && $this->clear_flag() ) {
562 - $this->apcu_clear_cache();
530 + $this->mmap_size = defined( 'WP_SQLITE_OBJECT_CACHE_MMAP_SIZE' )
531 + ? (int) WP_SQLITE_OBJECT_CACHE_MMAP_SIZE
532 + : self::MMAP_SIZE;
533 + $this->mmap_size = (int) $this->mmap_size * 1024 * 1024;
534 +
535 + $this->multisite = is_multisite();
536 + $this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : '';
537 + $this->cache_table_name = self::OBJECT_CACHE_TABLE;
538 + $this->flags_table_name = self::OBJECT_FLAGS_TABLE;
539 + $this->noexpire_timestamp_offset = self::NOEXPIRE_TIMESTAMP_OFFSET;
540 + $this->open_connection();
541 +
542 + /* If wp-cli code cached something into SQLite, clear the APCu cache because it's stale. */
543 + if ( $this->apcu_active && $this->clear_flag() ) {
544 + $this->apcu_clear_cache();
545 + }
563 546 }
564 - }
565 547
566 - /**
567 - * Defer the closing of the SQLite connection to another destructor.
568 - *
569 - * This is needed because some plugins set transients in their object destructors.
570 - */
571 - public function __destruct() {
572 - global $sqlite_object_cache_reaper;
573 - $sqlite_object_cache_reaper = new SQLite_Object_Cache_Reaper( $this->sqlite );
574 - }
548 + /**
549 + * Make sure connections are always closed at end of request
550 + */
551 + public function __destruct() {
552 + if ( $this->sqlite ) {
553 + $this->sqlite->close();
554 + unset( $this->sqlite );
555 + }
556 + }
575 557
576 - /**
577 - * Convert a list of integers into a list of runs: consecutive integers.
578 - *
579 - * Runs expand to include up to $erode_gaps extra integers, to make
580 - * fewer, longer runs. (Each run turns into a single database query,
581 - * so fewer of them is better.)
582 - *
583 - * @param int[] $intkeys List of integers. This can contain duplicate values.
584 - * @param int $erode_gaps Combine runs separated by this or fewer integers.
585 - *
586 - * @return array Associative array with elements start => end
587 - */
588 - private function runs( &$intkeys, $erode_gaps = 2 ) {
589 - if ( 0 === count( $intkeys ) ) {
590 - return array();
591 - }
592 - sort( $intkeys, SORT_NUMERIC );
593 - $previous = $intkeys[0];
594 - $runstart = $previous;
595 - $runs = array();
596 - foreach ( $intkeys as $intkey ) {
597 - if ( $intkey > $previous + 1 + $erode_gaps ) {
558 + /**
559 + * Convert a list of integers into a list of runs: consecutive integers.
560 + *
561 + * Runs expand to include up to $erode_gaps extra integers, to make
562 + * fewer, longer runs. (Each run turns into a single database query,
563 + * so fewer of them is better.)
564 + *
565 + * @param int[] $intkeys List of integers. This can contain duplicate values.
566 + * @param int $erode_gaps Combine runs separated by this or fewer integers.
567 + *
568 + * @return array Associative array with elements start => end
569 + */
570 + private function runs( &$intkeys, $erode_gaps = 2 ) {
571 + if ( 0 === count( $intkeys ) ) {
572 + return array();
573 + }
574 + sort( $intkeys, SORT_NUMERIC );
575 + $previous = $intkeys[0];
576 + $runstart = $previous;
577 + $runs = array();
578 + foreach ( $intkeys as $intkey ) {
579 + if ( $intkey > $previous + 1 + $erode_gaps ) {
580 + $runs[ $runstart ] = $previous;
581 + $runstart = $intkey;
582 + }
583 + $previous = $intkey;
584 + }
585 + if ( null !== $runstart ) {
598 586 $runs[ $runstart ] = $previous;
599 - $runstart = $intkey;
600 587 }
601 - $previous = $intkey;
588 +
589 + return $runs;
602 590 }
603 - if ( null !== $runstart ) {
604 - $runs[ $runstart ] = $previous;
605 - }
606 591
607 - return $runs;
608 - }
592 + /**
593 + * Create the pathname for the sqlite database.
594 + *
595 + * This is based on WP_SQLITE_OBJECT_CACHE_DB_FILE, WP_CACHE_KEY_SALT,
596 + * and whether igbinary is available.
597 + * It may have -wal and -shm appended to it by the SQLite engine.
598 + *
599 + * @return string Full filesystem pathname for SQLite database.
600 + */
601 + private function create_database_path() {
609 602
610 - /**
611 - * Create the pathname for the sqlite database.
612 - *
613 - * This is based on WP_SQLITE_OBJECT_CACHE_DB_FILE, WP_CACHE_KEY_SALT,
614 - * and whether igbinary is available.
615 - * It may have -wal and -shm appended to it by the SQLite engine.
616 - *
617 - * @return string Full filesystem pathname for SQLite database.
618 - */
619 - private function create_database_path() {
603 + $result = defined( 'WP_SQLITE_OBJECT_CACHE_DB_FILE' )
604 + ? WP_SQLITE_OBJECT_CACHE_DB_FILE
605 + : WP_CONTENT_DIR . '/' . self::SQLITE_FILENAME;
620 606
621 - $result = defined( 'WP_SQLITE_OBJECT_CACHE_DB_FILE' )
622 - ? WP_SQLITE_OBJECT_CACHE_DB_FILE
623 - : WP_CONTENT_DIR . '/' . self::SQLITE_FILENAME;
607 + $salt = $this->salt;
608 + $salt .= $this->has_igbinary ? '' : '-a';
624 609
625 - $salt = $this->salt;
626 - $salt .= $this->has_igbinary ? '' : '-a';
610 + if ( strlen( $salt ) > 0 ) {
611 + $splits = explode( '.', $result );
612 + if ( count( $splits ) >= 2 && 'sqlite' === $splits [ count( $splits ) - 1 ] ) {
613 + $splits[ count( $splits ) - 1 ] = $salt;
614 + $splits [] = 'sqlite';
615 + $result = implode( '.', $splits );
616 + } else {
617 + $result .= '.' . $salt . '.sqlite';
618 + }
619 + }
627 620
628 - if ( strlen( $salt ) > 0 ) {
629 - $splits = explode( '.', $result );
630 - if ( count( $splits ) >= 2 && 'sqlite' === $splits [ count( $splits ) - 1 ] ) {
631 - $splits[ count( $splits ) - 1 ] = $salt;
632 - $splits [] = 'sqlite';
633 - $result = implode( '.', $splits );
634 - } else {
635 - $result .= '.' . $salt . '.sqlite';
636 - }
621 + return $result;
637 622 }
638 623
639 - $directory = dirname( $result );
640 - if ( ! wp_is_writable( $directory ) ) {
641 - $message = sprintf( 'The SQLite Object Cache cannot be activated because the %s directory is not writable.', $directory );
642 - WP_Object_Cache::drop_dead( $message );
624 + /**
625 + * @param string|null $msg
626 + *
627 + * @return void
628 + */
629 + public static function drop_dead( $msg = null ) {
630 + wp_die( $msg ?: 'The SQLite Object Cache temporarily failed. Please try again now.' );
643 631 }
644 632
645 - return $result;
646 - }
647 -
648 - /**
649 - * @param string|null $msg
650 - *
651 - * @return void
652 - */
653 - public static function drop_dead( $msg = null ) {
654 - wp_die( esc_html( $msg ?: 'The SQLite Object Cache temporarily failed. Please try again now.' ) );
655 - }
656 -
657 - /**
658 - * Log an error.
659 - *
660 - * @param string $msg
661 - * @param Exception $exception
662 - *
663 - * @return void
664 - */
665 - private function error_log( $msg, $exception = null ) {
666 - $log_exception = ! ! $exception;
667 - $server_software = 'unk';
668 - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
669 - if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && is_string( $_SERVER['SERVER_SOFTWARE'] ) ) {
670 - $server_software = $_SERVER['SERVER_SOFTWARE'];
671 - }
672 - // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
673 - $msgs = array();
674 - $msgs [] = 'SQLite Object Cache:';
675 - $msgs [] = $this->dropin_version;
676 - $msgs [] = 'SQLite:';
677 - $msgs [] = $this->sqlite_get_version();
678 - $msgs [] = $this->has_igbinary ? 'igbinary' : 'no igbinary';
679 - $msgs [] = $this->apcu_active ? 'APCu active' : 'APCu inactive';
680 - $msgs [] = 'php:';
681 - $msgs [] = PHP_VERSION;
682 - $msgs [] = 'server:';
683 - $msgs [] = esc_html( $server_software );
684 - $msgs [] = $msg;
685 - if ( $this->sqlite ) {
686 - if ( $this->sqlite->lastErrorMsg() ) {
687 - $msgs [] = $this->sqlite->lastErrorMsg();
688 - $msgs [] = '(' . $this->sqlite->lastErrorCode() . ')';
689 - $log_exception = $log_exception && $this->sqlite->lastErrorMsg() !== $exception->getMessage();
633 + /**
634 + * Log an error.
635 + *
636 + * @param string $msg
637 + * @param Exception $exception
638 + *
639 + * @return void
640 + */
641 + private function error_log( $msg, $exception = null ) {
642 + $log_exception = ! ! $exception;
643 + $msgs = array();
644 + $msgs [] = 'SQLite Object Cache:';
645 + $msgs [] = $this->dropin_version;
646 + $msgs [] = 'SQLite:';
647 + $msgs [] = $this->sqlite_get_version();
648 + $msgs [] = $this->has_igbinary ? 'igbinary' : 'no igbinary';
649 + $msgs [] = $this->apcu_active ? 'APCu active' : 'APCu inactive';
650 + $msgs [] = 'php:';
651 + $msgs [] = PHP_VERSION;
652 + $msgs [] = 'server:';
653 + $msgs [] = $_SERVER['SERVER_SOFTWARE'];
654 + $msgs [] = $msg;
655 + if ( $this->sqlite ) {
656 + if ( $this->sqlite->lastErrorMsg() ) {
657 + $msgs [] = $this->sqlite->lastErrorMsg();
658 + $msgs [] = '(' . $this->sqlite->lastErrorCode() . ')';
659 + $log_exception = $log_exception && $this->sqlite->lastErrorMsg() !== $exception->getMessage();
660 + }
690 661 }
662 + if ( $log_exception ) {
663 + $msgs[] = $exception->getMessage();
664 + $msgs [] = '(' . $exception->getCode() . ')';
665 + $msgs [] = $exception->getTraceAsString();
666 + }
667 + error_log( implode( ' ', $msgs ) );
691 668 }
692 - if ( $log_exception ) {
693 - $msgs[] = $exception->getMessage();
694 - $msgs [] = '(' . $exception->getCode() . ')';
695 - $msgs [] = $exception->getTraceAsString();
696 - }
697 - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
698 - error_log( implode( ' ', $msgs ) );
699 - }
700 669
701 - /**
702 - * Open SQLite3 connection.
703 - * @return void
704 - */
705 - private function open_connection() {
706 - if ( $this->sqlite ) {
707 - return;
708 - }
709 - $retries = 3;
710 - while ( $retries -- > 0 ) {
711 - try {
712 - $this->actual_open_connection();
713 -
670 + /**
671 + * Open SQLite3 connection.
672 + * @return void
673 + */
674 + private function open_connection() {
675 + if ( $this->sqlite ) {
714 676 return;
715 - } catch ( Exception $ex ) {
716 - /* something went wrong opening */
717 - $this->error_log( 'open_connection failure', $ex );
718 - $this->delete_offending_files( $retries );
719 677 }
720 - }
721 - }
678 + $retries = 3;
679 + while ( $retries -- > 0 ) {
680 + try {
681 + $this->actual_open_connection();
722 682
723 - /**
724 - * Open SQLite3 connection.
725 - *
726 - * @return void
727 - * @throws Exception Announce SQLite failure.
728 - */
729 - private function actual_open_connection() {
730 - $start = hrtime( true );
731 -
732 - if ( defined ( 'PHP_OS') || 0 !== stripos(PHP_OS, 'WIN')) {
733 - /* Not Windows: Create file manually to set correct file permissions and make the DB group writable, for WP-CLI's sake */
734 - if ( ! file_exists( $this->sqlite_path ) ) {
735 - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_touch
736 - touch( $this->sqlite_path );
737 - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod
738 - chmod( $this->sqlite_path, 0664 );
683 + return;
684 + } catch ( Exception $ex ) {
685 + /* something went wrong opening */
686 + $this->error_log( 'open_connection failure', $ex );
687 + $this->delete_offending_files( $retries );
688 + }
739 689 }
740 690 }
741 691
742 - $this->sqlite = new SQLite3( $this->sqlite_path, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, '' );
743 - $this->sqlite->enableExceptions( true );
744 - $this->sqlite->busyTimeout( $this->sqlite_timeout );
745 -
746 - /* Set some initial pragma stuff.
747 - * Notice we sometimes use a journal mode (MEMORY) that risks database corruption.
748 - * That's OK, because it's faster, and because we have an error
749 - * recovery procedure that deletes and recreates a corrupt database file.
692 + /**
693 + * Open SQLite3 connection.
694 + *
695 + * @return void
696 + * @throws Exception Announce SQLite failure.
750 697 */
751 - $this->sqlite->exec( 'PRAGMA page_size = 4096' );
752 - if ( $this->mmap_size ) {
753 - $this->sqlite->exec( 'PRAGMA mmap_size = ' . $this->mmap_size );
754 - }
755 - $this->sqlite->exec( 'PRAGMA synchronous = OFF' );
756 - $this->sqlite->exec( "PRAGMA journal_mode = $this->sqlite_journal_mode" );
757 - $this->sqlite->exec( "PRAGMA encoding = 'UTF-8'" );
758 - $this->sqlite->exec( 'PRAGMA case_sensitive_like = true' );
759 - $this->create_object_cache_tables( );
760 - $this->prepare_statements( $this->cache_table_name );
698 + private function actual_open_connection() {
699 + $start = hrtime( true );
700 + $this->sqlite = new SQLite3( $this->sqlite_path, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, '' );
701 + $this->sqlite->enableExceptions( true );
702 + $this->sqlite->busyTimeout( $this->sqlite_timeout );
761 703
762 - $this->open_time = hrtime( true ) - $start;
763 - }
704 + /* Set some initial pragma stuff.
705 + * Notice we sometimes use a journal mode (MEMORY) that risks database corruption.
706 + * That's OK, because it's faster, and because we have an error
707 + * recovery procedure that deletes and recreates a corrupt database file.
708 + */
709 + $this->sqlite->exec( 'PRAGMA page_size = 4096' );
710 + if ( $this->mmap_size ) {
711 + $this->sqlite->exec( 'PRAGMA mmap_size = ' . $this->mmap_size );
712 + }
713 + $this->sqlite->exec( 'PRAGMA synchronous = OFF' );
714 + $this->sqlite->exec( "PRAGMA journal_mode = $this->sqlite_journal_mode" );
715 + $this->sqlite->exec( "PRAGMA encoding = 'UTF-8'" );
716 + $this->sqlite->exec( 'PRAGMA case_sensitive_like = true' );
717 + $this->create_object_cache_tables();
718 + $this->prepare_statements( $this->cache_table_name );
764 719
765 - /**
766 - * Set group type array
767 - *
768 - * @return void
769 - */
770 - protected function cache_group_types() {
771 - foreach ( $this->global_groups as $group ) {
772 - $this->group_type[ $group ] = 'global';
720 + $this->open_time = hrtime( true ) - $start;
773 721 }
774 722
775 - foreach ( $this->unflushable_groups as $group ) {
776 - $this->group_type[ $group ] = 'unflushable';
777 - }
723 + /**
724 + * Set group type array
725 + *
726 + * @return void
727 + */
728 + protected function cache_group_types() {
729 + foreach ( $this->global_groups as $group ) {
730 + $this->group_type[ $group ] = 'global';
731 + }
778 732
779 - foreach ( $this->ignored_groups as $group ) {
780 - $this->group_type[ $group ] = 'ignored';
733 + foreach ( $this->unflushable_groups as $group ) {
734 + $this->group_type[ $group ] = 'unflushable';
735 + }
736 +
737 + foreach ( $this->ignored_groups as $group ) {
738 + $this->group_type[ $group ] = 'ignored';
739 + }
781 740 }
782 - }
783 741
784 - /**
785 - * Do the necessary Data Definition Language work, for the cache table and flags table
786 - *
787 - * We use a single name column comprising group|key in one text string.
788 - * Why?
789 - * In recent versions of SQLite, it can serve as a clustered-index simple primary key.
790 - * SQLite's ANALYZE facilty only builds query - planner stats for the first column of composite keys .
791 - *
792 - * "groups" are all text .
793 - *
794 - * "keys" are sometimes alphanumeric text and sometimes integers . So, they are all treated as text
795 - * in the name column of the database .
796 - *
797 - * Now, range scanning( BETWEEN ) is a hassle in get_multiple, especially when using
798 - * get_multiple to retrieve a range of keys from a group.
799 - *
800 - * @return void
801 - * @throws Exception If something fails .
802 - * @noinspection SqlResolve
803 - */
804 - private function create_object_cache_tables() {
805 - $this->sqlite->exec( 'BEGIN' );
806 - /* does our table exist? */
807 - $query = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = '$this->cache_table_name';";
808 - if ( 0 === $this->sqlite->querySingle( $query ) ) {
809 - /* later versions of SQLite3 have clustered primary keys, "WITHOUT ROWID" */
810 - $uses_rowid = version_compare( $this->sqlite_get_version(), '3.8.2' ) < 0;
811 - if ( $uses_rowid ) {
812 - /* @noinspection SqlIdentifier */
813 - $t = "
742 + /**
743 + * Do the necessary Data Definition Language work, for the cache table and flags table
744 + *
745 + * We use a single name column comprising group|key in one text string.
746 + * Why?
747 + * In recent versions of SQLite, it can serve as a clustered-index simple primary key.
748 + * SQLite's ANALYZE facilty only builds query - planner stats for the first column of composite keys .
749 + *
750 + * "groups" are all text .
751 + *
752 + * "keys" are sometimes alphanumeric text and sometimes integers . So, they are all treated as text
753 + * in the name column of the database .
754 + *
755 + * Now, range scanning( BETWEEN ) is a hassle in get_multiple, especially when using
756 + * get_multiple to retrieve a range of keys from a group .
757 + *
758 + * @return void
759 + * @throws Exception If something fails .
760 + * @noinspection SqlResolve
761 + */
762 + private function create_object_cache_tables() {
763 + $this->sqlite->exec( 'BEGIN' );
764 + /* does our table exist? */
765 + $q = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = '$this->cache_table_name';";
766 + $r = $this->sqlite->querySingle( $q );
767 + if ( 0 === $r ) {
768 + /* later versions of SQLite3 have clustered primary keys, "WITHOUT ROWID" */
769 + $uses_rowid = version_compare( $this->sqlite_get_version(), '3.8.2' ) < 0;
770 + if ( $uses_rowid ) {
771 + /* @noinspection SqlIdentifier */
772 + $t = "
814 773 CREATE TABLE IF NOT EXISTS $this->cache_table_name (
815 774 name TEXT NOT NULL COLLATE BINARY,
816 775 expires INT,
817 776 value BLOB
@@ -817,11 +776,11 @@
817 776 value BLOB
818 777 );
819 778 CREATE UNIQUE INDEX IF NOT EXISTS cache_name ON $this->cache_table_name (name);
820 779 CREATE INDEX IF NOT EXISTS expires ON $this->cache_table_name (expires);";
821 - } else {
822 - /* @noinspection SqlIdentifier */
823 - $t = "
780 + } else {
781 + /* @noinspection SqlIdentifier */
782 + $t = "
824 783 CREATE TABLE IF NOT EXISTS $this->cache_table_name (
825 784 name TEXT NOT NULL PRIMARY KEY COLLATE BINARY,
826 785 expires INT,
827 786 value BLOB
@@ -826,1176 +785,1114 @@
826 785 expires INT,
827 786 value BLOB
828 787 ) WITHOUT ROWID;
829 788 CREATE INDEX IF NOT EXISTS expires ON $this->cache_table_name (expires);";
830 - }
831 - $this->sqlite->exec( $t );
789 + }
790 + $this->sqlite->exec( $t );
832 791
833 - if ( $uses_rowid ) {
834 - /* @noinspection SqlIdentifier */
835 - $t = "
792 + if ( $uses_rowid ) {
793 + /* @noinspection SqlIdentifier */
794 + $t = "
836 795 CREATE TABLE IF NOT EXISTS $this->flags_table_name (
837 796 name TEXT NOT NULL COLLATE BINARY
838 797 );
839 798 CREATE UNIQUE INDEX IF NOT EXISTS flags_name ON $this->flags_table_name (name);";
840 - } else {
841 - /* @noinspection SqlIdentifier */
842 - $t = "
799 + } else {
800 + /* @noinspection SqlIdentifier */
801 + $t = "
843 802 CREATE TABLE IF NOT EXISTS $this->flags_table_name (
844 803 name TEXT NOT NULL PRIMARY KEY COLLATE BINARY
845 804 ) WITHOUT ROWID;";
846 - }
847 - $this->sqlite->exec( $t );
805 + }
806 + $this->sqlite->exec( $t );
848 807
849 - /* Put the drop-in's version number in the SQLite file, for troubleshooting. */
850 - $version = str_replace( '.', '0', $this->dropin_version );
851 - if ( is_numeric( $version ) ) {
852 - $this->sqlite->exec( "PRAGMA user_version=" . ( (int) $version ) . ";" );
808 + /* Put the drop-in's version number in the SQLite file, for troubleshooting. */
809 + $version = str_replace( '.', '0', $this->dropin_version );
810 + if ( is_numeric( $version ) ) {
811 + $this->sqlite->exec( "PRAGMA user_version=" . ( (int) $version ) . ";" );
812 + }
813 + /* Creating SQLite tables; clear APCu at the same time. */
814 + $this->apcu_clear_cache();
853 815 }
854 - /* Creating SQLite tables; clear APCu at the same time. */
855 - $this->apcu_clear_cache();
816 + $this->sqlite->exec( 'COMMIT' );
856 817 }
857 - $this->sqlite->exec( 'COMMIT' );
858 - }
859 818
860 - /**
861 - * Do the necessary Data Definition Language work.
862 - *
863 - * @param string $tbl The name of the table.
864 - *
865 - * @return void
866 - * @throws Exception If something fails.
867 - * @noinspection SqlResolve
868 - */
869 - private function maybe_create_stats_table( $tbl ) {
870 - $this->sqlite->exec( 'BEGIN' );
871 - /* Does our table exist? */
872 - $q = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = '$tbl';";
873 - $r = $this->sqlite->querySingle( $q );
874 - if ( 0 === $r ) {
875 - /* @noinspection SqlIdentifier */
876 - $t = "
819 + /**
820 + * Do the necessary Data Definition Language work.
821 + *
822 + * @param string $tbl The name of the table.
823 + *
824 + * @return void
825 + * @throws Exception If something fails.
826 + * @noinspection SqlResolve
827 + */
828 + private function maybe_create_stats_table( $tbl ) {
829 + $this->sqlite->exec( 'BEGIN' );
830 + /* Does our table exist? */
831 + $q = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = '$tbl';";
832 + $r = $this->sqlite->querySingle( $q );
833 + if ( 0 === $r ) {
834 + /* @noinspection SqlIdentifier */
835 + $t = "
877 836 CREATE TABLE IF NOT EXISTS $tbl (
878 837 value BLOB,
879 838 timestamp INT
880 839 );
881 840 CREATE INDEX IF NOT EXISTS expires ON $tbl (timestamp);";
882 - $this->sqlite->exec( $t );
841 + $this->sqlite->exec( $t );
842 + }
843 + $this->sqlite->exec( 'COMMIT' );
883 844 }
884 - $this->sqlite->exec( 'COMMIT' );
885 - }
886 845
887 - /**
888 - * Create the prepared statements to use.
889 - *
890 - * @param string $tbl Table name.
891 - *
892 - * @return void
893 - * @throws Exception Announce failure.
894 - * @noinspection SqlResolve
895 - */
896 - private function prepare_statements( $tbl ) {
897 - $now = $this->start_time;
898 - $this->getone_stmt =
899 - $this->sqlite->prepare( "SELECT value, expires FROM $tbl WHERE name = :name AND expires >= $now;" );
900 - $this->getrange_stmt =
901 - $this->sqlite->prepare( "SELECT name, value, expires FROM $tbl WHERE name BETWEEN :first AND :last AND expires >= $now;" );
902 - $this->deleteone_stmt = $this->sqlite->prepare( "DELETE FROM $tbl WHERE name = :name;" );
903 - $this->deletegroup_stmt = $this->sqlite->prepare( "DELETE FROM $tbl WHERE name LIKE :group || '%';" );
904 - /*
905 - * Some versions of SQLite3 built into php predate the 3.38 advent of unixepoch() (2022-02-22).
906 - * And, others predate the 3.24 advent of UPSERT (that is, ON CONFLICT) syntax.
907 - * In that case we have to do attempt-update then insert to get updates to work. Sigh.
846 + /**
847 + * Create the prepared statements to use.
848 + *
849 + * @param string $tbl Table name.
850 + *
851 + * @return void
852 + * @throws Exception Announce failure.
853 + * @noinspection SqlResolve
908 854 */
909 - $has_upsert = version_compare( $this->sqlite_get_version(), '3.24', 'ge' );
910 - if ( $has_upsert ) {
911 - $this->upsertone_stmt =
912 - $this->sqlite->prepare( "INSERT INTO $tbl (name, value, expires) VALUES (:name, :value, $now + :expires) ON CONFLICT(name) DO UPDATE SET value=excluded.value, expires=excluded.expires;" );
913 - } else {
914 - $this->insertone_stmt =
915 - $this->sqlite->prepare( "INSERT INTO $tbl (name, value, expires) VALUES (:name, :value, $now + :expires);" );
916 - $this->updateone_stmt =
917 - $this->sqlite->prepare( "UPDATE $tbl SET value = :value, expires = $now + :expires WHERE name = :name;" );
855 + private function prepare_statements( $tbl ) {
856 + $now = time();
857 + $this->getone_stmt =
858 + $this->sqlite->prepare( "SELECT value, expires FROM $tbl WHERE name = :name AND expires >= $now;" );
859 + $this->getrange_stmt =
860 + $this->sqlite->prepare( "SELECT name, value, expires FROM $tbl WHERE name BETWEEN :first AND :last AND expires >= $now;" );
861 + $this->deleteone_stmt = $this->sqlite->prepare( "DELETE FROM $tbl WHERE name = :name;" );
862 + $this->deletegroup_stmt = $this->sqlite->prepare( "DELETE FROM $tbl WHERE name LIKE :group || '%';" );
863 + /*
864 + * Some versions of SQLite3 built into php predate the 3.38 advent of unixepoch() (2022-02-22).
865 + * And, others predate the 3.24 advent of UPSERT (that is, ON CONFLICT) syntax.
866 + * In that case we have to do attempt-update then insert to get updates to work. Sigh.
867 + */
868 + $has_upsert = version_compare( $this->sqlite_get_version(), '3.24', 'ge' );
869 + if ( $has_upsert ) {
870 + $this->upsertone_stmt =
871 + $this->sqlite->prepare( "INSERT INTO $tbl (name, value, expires) VALUES (:name, :value, $now + :expires) ON CONFLICT(name) DO UPDATE SET value=excluded.value, expires=excluded.expires;" );
872 + } else {
873 + $this->insertone_stmt =
874 + $this->sqlite->prepare( "INSERT INTO $tbl (name, value, expires) VALUES (:name, :value, $now + :expires);" );
875 + $this->updateone_stmt =
876 + $this->sqlite->prepare( "UPDATE $tbl SET value = :value, expires = $now + :expires WHERE name = :name;" );
877 + }
918 878 }
919 - }
920 879
921 - /**
922 - * Serialize data for persistence if need be. Use igbinary if available.
923 - *
924 - * @param mixed $data To be serialized.
925 - *
926 - * @return string|mixed Data ready for dbms insertion.
927 - */
928 - private function encode( $data ) {
929 - return $this->has_igbinary
930 - ? igbinary_serialize( $data )
931 - : maybe_serialize( $data );
932 - }
880 + /**
881 + * Serialize data for persistence if need be. Use igbinary if available.
882 + *
883 + * @param mixed $data To be serialized.
884 + *
885 + * @return string|mixed Data ready for dbms insertion.
886 + */
887 + private function encode( $data ) {
888 + return $this->has_igbinary
889 + ? igbinary_serialize( $data )
890 + : maybe_serialize( $data );
891 + }
933 892
934 893
935 - /**
936 - * Unserialize persistend data. Use igbinary if available.
937 - *
938 - * @param mixed $data To be unserialized.
939 - *
940 - * @return string|mixed Data ready for use.
941 - */
942 - private function decode( $data ) {
943 - return $this->has_igbinary
944 - ? igbinary_unserialize( $data )
945 - : maybe_unserialize( $data );
946 - }
894 + /**
895 + * Unserialize persistend data. Use igbinary if available.
896 + *
897 + * @param mixed $data To be unserialized.
898 + *
899 + * @return string|mixed Data ready for use.
900 + */
901 + private function decode( $data ) {
902 + return $this->has_igbinary
903 + ? igbinary_unserialize( $data )
904 + : maybe_unserialize( $data );
905 + }
947 906
948 - /**
949 - * @param mixed $data The serialized data to reconsititute
950 - *
951 - * @return mixed|string The data, cloned if an object.
952 - */
953 - private function reconstitute( $data ) {
954 - $ret = $this->decode( $data );
955 - return is_object( $ret ) ? clone $ret : $ret;
956 - }
907 + /**
908 + * @param mixed $data The serialized data to reconsititute
909 + *
910 + * @return mixed|string The data, cloned if an object.
911 + */
912 + private function reconstitute( $data ) {
913 + $ret = $this->decode( $data );
914 + return is_object( $ret ) ? clone $ret : $ret;
915 + }
957 916
958 - /**
959 - * Determine whether we can use SQLite3.
960 - *
961 - * @return bool|string true, or an error message.
962 - */
963 - public static function has_sqlite() {
917 + /**
918 + * Determine whether we can use SQLite3.
919 + *
920 + * @param string $directory The directory to hold the .sqlite file. Default WP_CONTENT_DIR.
921 + *
922 + * @return bool|string true, or an error message.
923 + */
924 + public static function has_sqlite( $directory = WP_CONTENT_DIR ) {
925 + if ( ! wp_is_writable( $directory ) ) {
926 + return sprintf( 'The SQLite Object Cache cannot be activated because the %s directory is not writable.', $directory );
927 + }
964 928
965 - if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
966 - return 'The SQLite Object Cache cannot be activated because the SQLite3 extension is not loaded.';
929 + if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
930 + return 'The SQLite Object Cache cannot be activated because the SQLite3 extension is not loaded.';
931 + }
932 +
933 + return true;
967 934 }
968 935
969 - return true;
970 - }
936 + /**
937 + * Set the monitoring options for the SQLite cache.
938 + *
939 + * Options in array [
940 + * 'capture' => (bool)
941 + * 'resolution' => how often in seconds (float)
942 + * 'lifetime' => how long until entries expire in seconds (int)
943 + * 'verbose' => (bool) capture extra stuff.
944 + * ]
945 + *
946 + * @param array $options Option list.
947 + *
948 + * @return void
949 + */
950 + public function set_sqlite_monitoring_options( $options ) {
951 + $this->monitoring_options = $options;
952 + }
971 953
972 - /**
973 - * Set the monitoring options for the SQLite cache.
974 - *
975 - * Options in array [
976 - * 'capture' => (bool)
977 - * 'resolution' => how often in seconds (float)
978 - * 'lifetime' => how long until entries expire in seconds (int)
979 - * 'verbose' => (bool) capture extra stuff.
980 - * ]
981 - *
982 - * @param array $options Option list.
983 - *
984 - * @return void
985 - */
986 - public function set_sqlite_monitoring_options( $options ) {
987 - $this->monitoring_options = $options;
988 - }
954 + /**
955 + * Is recording this performance sample appropriate.
956 + *
957 + * We decide to take a performance sample based upon:
958 + * -- the sqlite_object_cache_settings option existing.
959 + * -- $option.capture having the 'on' value.
960 + * -- $option.samplerate >= 100 or samplerate greater than a random number.
961 + *
962 + * @return bool True if this sample should be recorded.
963 + */
964 + private function is_sample() {
965 + $options = get_option( 'sqlite_object_cache_settings', 'missing_option' );
966 + if ( 'missing_option' === $options ) {
967 + /* set an absent option to the empty array, so we don't repeatedly hammer the cache looking for a missing option */
968 + update_option( 'sqlite_object_cache_settings', array(), true );
989 969
990 - /**
991 - * Is recording this performance sample appropriate.
992 - *
993 - * We decide to take a performance sample based upon:
994 - * -- the sqlite_object_cache_settings option existing.
995 - * -- $option.capture having the 'on' value.
996 - * -- $option.samplerate >= 100 or samplerate greater than a random number.
997 - *
998 - * @return bool True if this sample should be recorded.
999 - */
1000 - private function is_sample() {
1001 - $options = get_option( 'sqlite_object_cache_settings', 'missing_option' );
1002 - if ( 'missing_option' === $options ) {
1003 - /* set an absent option to the empty array, so we don't repeatedly hammer the cache looking for a missing option */
1004 - update_option( 'sqlite_object_cache_settings', array(), true );
970 + return false;
971 + }
972 + if ( is_array( $options ) && array_key_exists( 'capture', $options ) && 'on' === $options['capture'] ) {
973 + if ( array_key_exists( 'samplerate', $options ) && is_numeric( $options['samplerate'] ) ) {
974 + /* samplerate is a percentage likelihood in the option setting */
975 + $samplerate = $options['samplerate'];
976 + if ( $samplerate > 0 ) {
977 + /* a random sample at $samplerate */
978 + if ( $samplerate >= 100 ) {
979 + return true;
980 + }
981 + return ( $samplerate * 10000 ) > rand( 1, 1000000 );
982 + }
983 + }
984 + }
1005 985
1006 986 return false;
1007 987 }
1008 - if ( is_array( $options ) && array_key_exists( 'capture', $options ) && 'on' === $options['capture'] ) {
1009 - if ( array_key_exists( 'samplerate', $options ) && is_numeric( $options['samplerate'] ) ) {
1010 - /* samplerate is a percentage likelihood in the option setting */
1011 - $samplerate = $options['samplerate'];
1012 - if ( $samplerate > 0 ) {
1013 - /* a random sample at $samplerate */
1014 - if ( $samplerate >= 100 ) {
1015 - return true;
1016 - }
1017 - // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand
1018 - return ( $samplerate * 10000 ) > rand( 1, 1000000 );
988 +
989 + /**
990 + * Capture statistics if need be. Leave the connection open for late-arriving cache operations.
991 + *
992 + * @return bool
993 + */
994 + public function close() {
995 + if ( $this->sqlite ) {
996 + if ( $this->is_sample() ) {
997 + $this->capture( $this->monitoring_options );
1019 998 }
999 + /* Once in a while checkpoint the whole WAL log, so it doesn't grow without bound on a busy site. */
1000 + if ( 1 === rand( 1, 5000 ) ) {
1001 + $this->checkpoint();
1002 + }
1020 1003 }
1021 - }
1022 1004
1023 - return false;
1024 - }
1025 -
1026 - /**
1027 - * Capture statistics if need be. Leave the connection open for late-arriving cache operations.
1028 - *
1029 - * @return bool
1030 - */
1031 - public function close() {
1032 - if ( $this->sqlite ) {
1033 - if ( $this->is_sample() ) {
1034 - $this->capture( $this->monitoring_options );
1035 - }
1036 - /*
1037 - * Once in a while checkpoint the whole WAL log, so it doesn't grow without bound on a busy site.
1038 - * One in four times we do this, truncate (zero the length of) the WAL log file */
1039 - // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand
1040 - $r = rand( 1, $this->checkpoint_freq );
1041 - if ( 1 === $r ) {
1042 - $this->checkpoint( 'TRUNCATE' );
1043 - } else if ( $r <= 4 ) {
1044 - $this->checkpoint();
1045 - }
1005 + return true;
1046 1006 }
1047 - return true;
1048 - }
1049 1007
1050 - /**
1051 - * Remove statistics entries from the cache
1052 - *
1053 - * @param int|null $age Number of seconds' worth to retain. Default: retain none.
1054 - *
1055 - * @return void
1056 - */
1057 - public function sqlite_reset_statistics( $age = null ) {
1008 + /**
1009 + * Remove statistics entries from the cache
1010 + *
1011 + * @param int|null $age Number of seconds' worth to retain. Default: retain none.
1012 + *
1013 + * @return void
1014 + */
1015 + public function sqlite_reset_statistics( $age = null ) {
1058 1016
1059 - try {
1060 - $object_stats = self::OBJECT_STATS_TABLE;
1061 - $this->maybe_create_stats_table( $object_stats );
1062 - if ( ! is_numeric( $age ) ) {
1063 - /* @noinspection SqlWithoutWhere */
1064 - $sql = "DELETE FROM $object_stats;";
1065 - $this->sqlite->exec( $sql );
1066 - } else {
1067 - $expires = (int) ( $this->start_time - $age );
1068 - $limit = self::TRANSACTION_SIZE_LIMIT;
1069 - $hits = $limit;
1070 - while ( $hits >= $limit ) {
1071 - /* @noinspection SqlResolve */
1072 - $sql = "DELETE FROM $object_stats WHERE timestamp IN (SELECT timestamp FROM $object_stats WHERE timestamp < $expires LIMIT $limit);";
1017 + try {
1018 + $object_stats = self::OBJECT_STATS_TABLE;
1019 + $this->maybe_create_stats_table( $object_stats );
1020 + if ( ! is_numeric( $age ) ) {
1021 + /* @noinspection SqlWithoutWhere */
1022 + $sql = "DELETE FROM $object_stats;";
1073 1023 $this->sqlite->exec( $sql );
1074 - $hits = $this->sqlite->changes();
1024 + } else {
1025 + $expires = (int) ( time() - $age );
1026 + $limit = self::TRANSACTION_SIZE_LIMIT;
1027 + $hits = $limit;
1028 + while ( $hits >= $limit ) {
1029 + /* @noinspection SqlResolve */
1030 + $sql = "DELETE FROM $object_stats WHERE timestamp IN (SELECT timestamp FROM $object_stats WHERE timestamp < $expires LIMIT $limit);";
1031 + $this->sqlite->exec( $sql );
1032 + $hits = $this->sqlite->changes();
1033 + }
1075 1034 }
1035 + } catch ( Exception $ex ) {
1036 + $this->error_log( 'SQLite Object Cache exception resetting statistics. ', $ex );
1076 1037 }
1077 - } catch ( Exception $ex ) {
1078 - $this->error_log( 'SQLite Object Cache exception resetting statistics. ', $ex );
1079 1038 }
1080 - }
1081 1039
1082 - /**
1083 - * Remove old entries.
1084 - *
1085 - * @return boolean True if any items were removed.
1086 - * @noinspection SqlResolve
1087 - */
1088 - public function sqlite_remove_expired() {
1089 - $items_removed = 0;
1090 - try {
1091 - $limit = self::TRANSACTION_SIZE_LIMIT;
1092 - $hit = $limit;
1040 + /**
1041 + * Remove old entries.
1042 + *
1043 + * @return boolean True if any items were removed.
1044 + * @noinspection SqlResolve
1045 + */
1046 + public function sqlite_remove_expired() {
1047 + $items_removed = 0;
1048 + try {
1049 + $this->checkpoint();
1050 + $limit = self::TRANSACTION_SIZE_LIMIT;
1051 + $hit = $limit;
1093 1052
1094 - /* Remove items with definite expirations, like transients */
1095 - $sql = 'DELETE FROM ' . $this->cache_table_name . ' WHERE name IN (SELECT name FROM ' . $this->cache_table_name . ' WHERE expires <= ' . $this->start_time . ' LIMIT ' . $limit . ')';
1053 + /* Remove items with definite expirations, like transients */
1054 + $sql = 'DELETE FROM ' . $this->cache_table_name . ' WHERE name IN (SELECT name FROM ' . $this->cache_table_name . ' WHERE expires <= ' . time() . ' LIMIT ' . $limit . ')';
1096 1055
1097 - while ( $hit >= $limit ) {
1098 - $this->sqlite->exec( $sql );
1099 - $hit = $this->sqlite->changes();
1100 - $items_removed += $hit;
1056 + while ( $hit >= $limit ) {
1057 + $this->sqlite->exec( $sql );
1058 + $hit = $this->sqlite->changes();
1059 + $items_removed += $hit;
1060 + }
1061 + } catch ( Exception $ex ) {
1062 + $this->error_log( 'sqlite_remove_expired', $ex );
1101 1063 }
1102 - } catch ( Exception $ex ) {
1103 - $this->error_log( 'sqlite_remove_expired', $ex );
1064 +
1065 + return $items_removed > 0;
1104 1066 }
1105 - $this->checkpoint( 'TRUNCATE' );
1106 1067
1107 - return $items_removed > 0;
1108 - }
1068 + /**
1069 + * Get the size of the cache database.
1070 + *
1071 + * @return int Size of current cache database in bytes.
1072 + */
1073 + public function sqlite_get_size() {
1074 + $object_cache = self::OBJECT_CACHE_TABLE;
1075 + $sql = "SELECT SUM(LENGTH(value) + LENGTH(name)) length FROM $object_cache";
1076 + $stmt = $this->sqlite->prepare( $sql );
1077 + $resultset = $stmt->execute();
1078 + $row = $resultset->fetchArray( SQLITE3_NUM );
1079 + $result = $row[0];
1080 + $resultset->finalize();
1109 1081
1110 - /**
1111 - * Get the size of the cache database.
1112 - *
1113 - * @return int Size of current cache database in bytes.
1114 - */
1115 - public function sqlite_get_size() {
1116 - $object_cache = self::OBJECT_CACHE_TABLE;
1117 - $sql = "SELECT SUM(LENGTH(value) + LENGTH(name)) length FROM $object_cache";
1118 - $stmt = $this->sqlite->prepare( $sql );
1119 - $resultset = $stmt->execute();
1120 - $row = $resultset->fetchArray( SQLITE3_NUM );
1121 - $result = $row[0];
1122 - $resultset->finalize();
1082 + return (int) $result;
1083 + }
1123 1084
1124 - return (int) $result;
1125 - }
1126 -
1127 - /**
1128 - * Read object names, sizes, expirations from cache, ordered by expiration time oldest first.
1129 - *
1130 - * @param $timestamps true If the timestamps returned should be expirations, false means raw
1131 - *
1132 - * @return Generator of name/length/timestamp rows.
1133 - * @throws Exception Announce SQLite failure.
1134 - * @noinspection SqlResolve
1135 - */
1136 - public function &sqlite_load_usages( $timestamps = true ) {
1137 - $object_cache = self::OBJECT_CACHE_TABLE;
1138 - $offset = $this->noexpire_timestamp_offset;
1139 - $sql = "SELECT name, LENGTH(value) + LENGTH(name) length, expires FROM $object_cache";
1140 - $stmt = $this->sqlite->prepare( $sql );
1141 - try {
1142 - $resultset = $stmt->execute();
1143 - while ( true ) {
1144 - $row = $resultset->fetchArray( SQLITE3_ASSOC );
1145 - if ( ! $row ) {
1146 - break;
1147 - }
1148 - $row = (object) $row;
1149 - if ( $timestamps ) {
1150 - $expires = $row->expires;
1151 - if ( $expires >= self::NOEXPIRE_TIMESTAMP_OFFSET ) {
1152 - $expires -= self::NOEXPIRE_TIMESTAMP_OFFSET;
1085 + /**
1086 + * Read object names, sizes, expirations from cache, ordered by expiration time oldest first.
1087 + *
1088 + * @param $timestamps true If the timestamps returned should be expirations, false means raw
1089 + *
1090 + * @return Generator of name/length/timestamp rows.
1091 + * @throws Exception Announce SQLite failure.
1092 + * @noinspection SqlResolve
1093 + */
1094 + public function &sqlite_load_usages( $timestamps = true ) {
1095 + $object_cache = self::OBJECT_CACHE_TABLE;
1096 + $offset = $this->noexpire_timestamp_offset;
1097 + $sql = "SELECT name, LENGTH(value) + LENGTH(name) length, expires FROM $object_cache";
1098 + $stmt = $this->sqlite->prepare( $sql );
1099 + try {
1100 + $resultset = $stmt->execute();
1101 + while ( true ) {
1102 + $row = $resultset->fetchArray( SQLITE3_ASSOC );
1103 + if ( ! $row ) {
1104 + break;
1153 1105 }
1154 - $row->expires = $expires;
1106 + $row = (object) $row;
1107 + if ( $timestamps ) {
1108 + $expires = $row->expires;
1109 + if ( $expires >= self::NOEXPIRE_TIMESTAMP_OFFSET ) {
1110 + $expires -= self::NOEXPIRE_TIMESTAMP_OFFSET;
1111 + }
1112 + $row->expires = $expires;
1113 + }
1114 + yield $row;
1155 1115 }
1156 - yield $row;
1116 + } finally {
1117 + $resultset->finalize();
1157 1118 }
1158 - } finally {
1159 - $resultset->finalize();
1160 1119 }
1161 - }
1162 1120
1163 - public function sqlite_sizes() {
1164 - $object_stats = self::OBJECT_STATS_TABLE;
1165 - $this->maybe_create_stats_table( $object_stats );
1121 + public function sqlite_sizes() {
1122 + $object_stats = self::OBJECT_STATS_TABLE;
1123 + $this->maybe_create_stats_table( $object_stats );
1166 1124
1167 - $items = array(
1168 - 'page_size' => 'PRAGMA page_size;',
1169 - 'free_pages' => 'PRAGMA freelist_count;',
1170 - 'total_pages' => 'PRAGMA page_count;',
1171 - 'stats_items' => "SELECT COUNT(value) FROM $object_stats;",
1172 - 'stats_size' => "SELECT SUM(LENGTH(value)+ 4) FROM $object_stats;",
1173 - 'mmap_size' => "PRAGMA mmap_size;",
1174 - );
1125 + $items = array(
1126 + 'page_size' => 'PRAGMA page_size;',
1127 + 'free_pages' => 'PRAGMA freelist_count;',
1128 + 'total_pages' => 'PRAGMA page_count;',
1129 + 'stats_items' => "SELECT COUNT(value) FROM $object_stats;",
1130 + 'stats_size' => "SELECT SUM(LENGTH(value)+ 4) FROM $object_stats;",
1131 + 'mmap_size' => "PRAGMA mmap_size;",
1132 + );
1175 1133
1176 - $result = array();
1177 - foreach ( $items as $item => $query ) {
1178 - $stmt = $this->sqlite->prepare( $query );
1179 - $resultset = $stmt->execute();
1180 - $row = $resultset->fetchArray( SQLITE3_NUM );
1181 - $val = (int) $row[0];
1182 - $resultset->finalize();
1183 - $result [ $item ] = $val;
1134 + $result = array();
1135 + foreach ( $items as $item => $query ) {
1136 + $stmt = $this->sqlite->prepare( $query );
1137 + $resultset = $stmt->execute();
1138 + $row = $resultset->fetchArray( SQLITE3_NUM );
1139 + $val = (int) $row[0];
1140 + $resultset->finalize();
1141 + $result [ $item ] = $val;
1142 + }
1143 +
1144 + return $result;
1184 1145 }
1185 1146
1186 - return $result;
1187 - }
1147 + /**
1148 + * Read timestamps and object sizes of non-expiring items, oldest first, in buckets of 16 seconds.
1149 + *
1150 + * Object sizes are the summed lengths of name, value, and timestamp, and ignore index overhead.
1151 + *
1152 + * @return SQLite3Result Resultset containing length/timestamp rows.
1153 + * @throws Exception Announce SQLite failure.
1154 + * @noinspection SqlResolve
1155 + */
1156 + private function sqlite_load_sizes() {
1157 + $object_cache = self::OBJECT_CACHE_TABLE;
1158 + $offset = $this->noexpire_timestamp_offset;
1159 + $sql =
1160 + "SELECT SUM(LENGTH(value) + LENGTH(name) + 6) length, ((expires+15)/16)*16 expires FROM $object_cache WHERE expires >= $offset GROUP BY ((expires+15)/16)*16 ORDER BY ((expires+15)/16)*16";
1161 + $stmt = $this->sqlite->prepare( $sql );
1188 1162
1189 - /**
1190 - * Read timestamps and object sizes of non-expiring items, oldest first, in buckets of 16 seconds.
1191 - *
1192 - * Object sizes are the summed lengths of name, value, and timestamp, and ignore index overhead.
1193 - *
1194 - * @return SQLite3Result Resultset containing length/timestamp rows.
1195 - * @throws Exception Announce SQLite failure.
1196 - * @noinspection SqlResolve
1197 - */
1198 - private function sqlite_load_sizes() {
1199 - $object_cache = self::OBJECT_CACHE_TABLE;
1200 - $offset = $this->noexpire_timestamp_offset;
1201 - $sql =
1202 - "SELECT SUM(LENGTH(value) + LENGTH(name) + 6) length, ((expires+15)/16)*16 expires FROM $object_cache WHERE expires >= $offset GROUP BY ((expires+15)/16)*16 ORDER BY ((expires+15)/16)*16";
1203 - $stmt = $this->sqlite->prepare( $sql );
1163 + return $stmt->execute();
1164 + }
1204 1165
1205 - return $stmt->execute();
1206 - }
1207 -
1208 - /**
1209 - * Read rows from the stored statistics.
1210 - *
1211 - * @return Generator
1212 - * @throws Exception Announce SQLite failure.
1213 - * @noinspection SqlResolve
1214 - */
1215 - public function sqlite_load_statistics() {
1216 - $object_stats = self::OBJECT_STATS_TABLE;
1217 - $this->maybe_create_stats_table( $object_stats );
1218 - $sql = "SELECT value FROM $object_stats;";
1219 - $stmt = $this->sqlite->prepare( $sql );
1220 - try {
1221 - $resultset = $stmt->execute();
1222 - while ( true ) {
1223 - $row = $resultset->fetchArray( SQLITE3_NUM );
1224 - if ( ! $row ) {
1225 - break;
1166 + /**
1167 + * Read rows from the stored statistics.
1168 + *
1169 + * @return Generator
1170 + * @throws Exception Announce SQLite failure.
1171 + * @noinspection SqlResolve
1172 + */
1173 + public function sqlite_load_statistics() {
1174 + $object_stats = self::OBJECT_STATS_TABLE;
1175 + $this->maybe_create_stats_table( $object_stats );
1176 + $sql = "SELECT value FROM $object_stats;";
1177 + $stmt = $this->sqlite->prepare( $sql );
1178 + try {
1179 + $resultset = $stmt->execute();
1180 + while ( true ) {
1181 + $row = $resultset->fetchArray( SQLITE3_NUM );
1182 + if ( ! $row ) {
1183 + break;
1184 + }
1185 + $value = $this->decode( $row[0] );
1186 + yield (object) $value;
1226 1187 }
1227 - $value = $this->decode( $row[0] );
1228 - yield (object) $value;
1188 + } finally {
1189 + $resultset->finalize();
1229 1190 }
1230 - } finally {
1231 - $resultset->finalize();
1232 1191 }
1233 - }
1234 1192
1235 - /**
1236 - * Do the performance-capture operation.
1237 - *
1238 - * Put a row named sqlite_object_cache.mon.123456 into sqlite containing the raw data.
1239 - *
1240 - * @param array $options Contents of $this->monitoring_options.
1241 - *
1242 - * @return void
1243 - * @noinspection SqlResolve
1244 - */
1245 - private function capture( $options ) {
1246 - $now = microtime( true );
1247 - global $wpdb;
1248 - $record = array(
1249 - 'time' => $now,
1250 - 'elapsed' => hrtime( true ) - $this->start_hrtime,
1251 - 'RAMhits' => $this->cache_hits,
1252 - 'RAMmisses' => $this->cache_misses,
1253 - 'DISKhits' => $this->persistent_hits,
1254 - 'DISKmisses' => $this->persistent_misses,
1255 - 'open' => $this->open_time,
1256 - 'selects' => $this->select_times,
1257 - 'gets' => $this->get_times,
1258 - 'get_multiples' => $this->get_multiple_times,
1259 - 'get_multiple_keys' => $this->get_multiple_keys,
1260 - 'inserts' => $this->insert_times,
1261 - 'deletes' => $this->delete_times,
1262 - 'checkpoints' => $this->checkpoint_times,
1263 - 'DBMSqueries' => $wpdb->num_queries,
1264 - 'RAM' => memory_get_peak_usage( true ),
1265 - 'APCuhits' => $this->apcu_hits,
1266 - 'APCumisses' => $this->apcu_misses,
1267 - 'APCufetchhit' => $this->apcu_fetch_hit_times,
1268 - 'APCufetchmiss' => $this->apcu_fetch_miss_times,
1269 - 'APCustore' => $this->apcu_store_times,
1193 + /**
1194 + * Do the performance-capture operation.
1195 + *
1196 + * Put a row named sqlite_object_cache.mon.123456 into sqlite containing the raw data.
1197 + *
1198 + * @param array $options Contents of $this->monitoring_options.
1199 + *
1200 + * @return void
1201 + * @noinspection SqlResolve
1202 + */
1203 + private function capture( $options ) {
1204 + $now = microtime( true );
1205 + global $wpdb;
1206 + $record = array(
1207 + 'time' => $now,
1208 + 'elapsed' => hrtime( true ) - $this->start_time,
1209 + 'RAMhits' => $this->cache_hits,
1210 + 'RAMmisses' => $this->cache_misses,
1211 + 'DISKhits' => $this->persistent_hits,
1212 + 'DISKmisses' => $this->persistent_misses,
1213 + 'open' => $this->open_time,
1214 + 'selects' => $this->select_times,
1215 + 'gets' => $this->get_times,
1216 + 'get_multiples' => $this->get_multiple_times,
1217 + 'get_multiple_keys' => $this->get_multiple_keys,
1218 + 'inserts' => $this->insert_times,
1219 + 'deletes' => $this->delete_times,
1220 + 'checkpoints' => $this->checkpoint_times,
1221 + 'DBMSqueries' => $wpdb->num_queries,
1222 + 'RAM' => memory_get_peak_usage( true ),
1223 + 'APCuhits' => $this->apcu_hits,
1224 + 'APCumisses' => $this->apcu_misses,
1225 + 'APCufetchhit' => $this->apcu_fetch_hit_times,
1226 + 'APCufetchmiss' => $this->apcu_fetch_miss_times,
1227 + 'APCustore' => $this->apcu_store_times,
1270 1228
1271 - );
1272 - $object_stats = self::OBJECT_STATS_TABLE;
1273 - try {
1274 - $this->maybe_create_stats_table( $object_stats );
1275 - $sql =
1276 - "INSERT INTO $object_stats (value, timestamp) VALUES (:value, :timestamp);";
1277 - $stmt = $this->sqlite->prepare( $sql );
1278 - $stmt->bindValue( ':value', $this->encode( $record ), SQLITE3_BLOB );
1279 - $stmt->bindValue( ':timestamp', $this->start_time, SQLITE3_INTEGER );
1280 - $result = $stmt->execute();
1281 - $result->finalize();
1282 - } catch ( Exception $ex ) {
1283 - $this->error_log( 'error capturing performance stats, skipping.', $ex );
1229 + );
1230 + $object_stats = self::OBJECT_STATS_TABLE;
1231 + try {
1232 + $this->maybe_create_stats_table( $object_stats );
1233 + $sql =
1234 + "INSERT INTO $object_stats (value, timestamp) VALUES (:value, :timestamp);";
1235 + $stmt = $this->sqlite->prepare( $sql );
1236 + $stmt->bindValue( ':value', $this->encode( $record ), SQLITE3_BLOB );
1237 + $stmt->bindValue( ':timestamp', time(), SQLITE3_INTEGER );
1238 + $result = $stmt->execute();
1239 + $result->finalize();
1240 + } catch ( Exception $ex ) {
1241 + $this->error_log( 'error capturing performance stats, skipping.', $ex );
1242 + }
1243 + unset( $record, $stmt );
1284 1244 }
1285 - unset( $record, $stmt );
1286 - }
1287 1245
1288 - /** Get the version of the drop-in.
1289 - *
1290 - * @return string drop-in version.
1291 - */
1292 - public function dropin_get_version() {
1293 - return $this->dropin_version;
1294 - }
1246 + /** Get the version of the drop-in.
1247 + *
1248 + * @return string drop-in version.
1249 + */
1250 + public function dropin_get_version() {
1251 + return $this->dropin_version;
1252 + }
1295 1253
1296 - /**
1297 - * Get the version of SQLite in use.
1298 - *
1299 - * @return string
1300 - */
1301 - public function sqlite_get_version() {
1302 - if ( $this->sqlite_version ) {
1254 + /**
1255 + * Get the version of SQLite in use.
1256 + *
1257 + * @return string
1258 + */
1259 + public function sqlite_get_version() {
1260 + if ( $this->sqlite_version ) {
1261 + return $this->sqlite_version;
1262 + }
1263 + $v = SQLite3::version();
1264 + $this->sqlite_version = $v['versionString'];
1265 +
1303 1266 return $this->sqlite_version;
1304 1267 }
1305 - $v = SQLite3::version();
1306 - $this->sqlite_version = $v['versionString'];
1307 1268
1308 - return $this->sqlite_version;
1309 - }
1269 + /**
1270 + * Sets the list of groups not to be cached by Redis.
1271 + *
1272 + * @param array $groups List of groups that are to be ignored.
1273 + */
1274 + public function add_non_persistent_groups( $groups ) {
1275 + /**
1276 + * Filters list of groups to be added to {@see self::$ignored_groups}
1277 + *
1278 + * @param string[] $groups List of groups to be ignored.
1279 + *
1280 + * @since 2.1.7
1281 + */
1282 + $groups = apply_filters( 'sqlite_object_cache_add_non_persistent_groups', (array) $groups );
1310 1283
1311 - /**
1312 - * Sets the list of groups not to be cached by Redis.
1313 - *
1314 - * @param array $groups List of groups that are to be ignored.
1315 - */
1316 - public function add_non_persistent_groups( $groups ) {
1284 + $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
1285 + $this->cache_group_types();
1286 + }
1287 +
1317 1288 /**
1318 - * Filters list of groups to be added to {@see self::$ignored_groups}
1289 + * Makes private properties readable for backward compatibility.
1319 1290 *
1320 - * @param string[] $groups List of groups to be ignored.
1291 + * @param string $name Property to get.
1321 1292 *
1322 - * @since 2.1.7
1293 + * @return mixed Property.
1294 + * @since 4.0.0
1323 1295 */
1324 - $groups = apply_filters( 'sqlite_object_cache_add_non_persistent_groups', (array) $groups );
1296 + public function __get( $name ) {
1297 + return $this->$name;
1298 + }
1325 1299
1326 - $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
1327 - $this->cache_group_types();
1328 - }
1300 + /**
1301 + * Makes private properties settable for backward compatibility.
1302 + *
1303 + * @param string $name Property to set.
1304 + * @param mixed $value Property value.
1305 + *
1306 + * @return mixed Newly-set property.
1307 + * @since 4.0.0
1308 + */
1309 + public function __set( $name, $value ) {
1310 + return $this->$name = $value;
1311 + }
1329 1312
1330 - /**
1331 - * Makes private properties readable for backward compatibility.
1332 - *
1333 - * @param string $name Property to get.
1334 - *
1335 - * @return mixed Property.
1336 - * @since 4.0.0
1337 - */
1338 - public function __get( $name ) {
1339 - return $this->$name;
1340 - }
1313 + /**
1314 + * Makes private properties checkable for backward compatibility.
1315 + *
1316 + * @param string $name Property to check if set.
1317 + *
1318 + * @return bool Whether the property is set.
1319 + * @since 4.0.0
1320 + */
1321 + public function __isset( $name ) {
1322 + return isset( $this->$name );
1323 + }
1341 1324
1342 - /**
1343 - * Makes private properties settable for backward compatibility.
1344 - *
1345 - * @param string $name Property to set.
1346 - * @param mixed $value Property value.
1347 - *
1348 - * @return mixed Newly-set property.
1349 - * @since 4.0.0
1350 - */
1351 - public function __set( $name, $value ) {
1352 - return $this->$name = $value;
1353 - }
1325 + /**
1326 + * Makes private properties un-settable for backward compatibility.
1327 + *
1328 + * @param string $name Property to unset.
1329 + *
1330 + * @since 4.0.0
1331 + */
1332 + public function __unset( $name ) {
1333 + unset( $this->$name );
1334 + }
1354 1335
1355 - /**
1356 - * Makes private properties checkable for backward compatibility.
1357 - *
1358 - * @param string $name Property to check if set.
1359 - *
1360 - * @return bool Whether the property is set.
1361 - * @since 4.0.0
1362 - */
1363 - public function __isset( $name ) {
1364 - return isset( $this->$name );
1365 - }
1366 -
1367 - /**
1368 - * Makes private properties un-settable for backward compatibility.
1369 - *
1370 - * @param string $name Property to unset.
1371 - *
1372 - * @since 4.0.0
1373 - */
1374 - public function __unset( $name ) {
1375 - unset( $this->$name );
1376 - }
1377 -
1378 - /**
1379 - * Adds multiple values to the cache in one call.
1380 - *
1381 - * @param array $data Array of keys and values to be added.
1382 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
1383 - * @param int $expire Optional. When to expire the cache contents, in seconds.
1384 - * Default 0 (no expiration).
1385 - *
1386 - * @return bool[] Array of return values, grouped by key. Each value is either
1387 - * true on success, or false if cache key and group already exist.
1388 - * @since 6.0.0
1389 - */
1390 - public function add_multiple( array &$data, $group = '', $expire = 0 ) {
1391 - if ( 0 === count( $data ) ) {
1392 - return array();
1393 - }
1394 - $values = array();
1395 - /* sort the array to reduce index page fragmentation */
1396 - ksort( $data, SORT_NUMERIC );
1397 - try {
1398 - /* use a transaction to accelerate add_multiple */
1399 - $this->transaction_active = true;
1400 - $this->sqlite->exec( 'BEGIN' );
1401 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1402 - foreach ( $data as $key => $value ) {
1403 - $values[ $key ] = $this->add( $key, $value, $group, $expire );
1404 - /* limit the size of the transaction, hopefully preventing timeouts in other clients */
1405 - if ( -- $transaction_size <= 0 ) {
1406 - $this->sqlite->exec( 'COMMIT' );
1407 - $this->sqlite->exec( 'BEGIN' );
1408 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1336 + /**
1337 + * Adds multiple values to the cache in one call.
1338 + *
1339 + * @param array $data Array of keys and values to be added.
1340 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
1341 + * @param int $expire Optional. When to expire the cache contents, in seconds.
1342 + * Default 0 (no expiration).
1343 + *
1344 + * @return bool[] Array of return values, grouped by key. Each value is either
1345 + * true on success, or false if cache key and group already exist.
1346 + * @since 6.0.0
1347 + */
1348 + public function add_multiple( array &$data, $group = '', $expire = 0 ) {
1349 + if ( 0 === count( $data ) ) {
1350 + return array();
1351 + }
1352 + $values = array();
1353 + /* sort the array to reduce index page fragmentation */
1354 + ksort( $data, SORT_NUMERIC );
1355 + try {
1356 + /* use a transaction to accelerate add_multiple */
1357 + $this->transaction_active = true;
1358 + $this->sqlite->exec( 'BEGIN' );
1359 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1360 + foreach ( $data as $key => $value ) {
1361 + $values[ $key ] = $this->add( $key, $value, $group, $expire );
1362 + /* limit the size of the transaction, hopefully preventing timeouts in other clients */
1363 + if ( -- $transaction_size <= 0 ) {
1364 + $this->sqlite->exec( 'COMMIT' );
1365 + $this->sqlite->exec( 'BEGIN' );
1366 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1367 + }
1409 1368 }
1369 + $this->sqlite->exec( 'COMMIT' );
1370 + $this->transaction_active = false;
1371 + } catch ( Exception $ex ) {
1372 + $this->error_log( 'add_multiple', $ex );
1373 + $this->delete_offending_files();
1374 + self::drop_dead();
1410 1375 }
1411 - $this->sqlite->exec( 'COMMIT' );
1412 - $this->transaction_active = false;
1413 - } catch ( Exception $ex ) {
1414 - $this->error_log( 'add_multiple', $ex );
1415 - $this->delete_offending_files();
1416 - self::drop_dead();
1417 - }
1418 1376
1419 - return $values;
1420 - }
1421 -
1422 - /**
1423 - * Adds data to the cache if it doesn't already exist.
1424 - *
1425 - * @param int|string $key What to call the contents in the cache.
1426 - * @param mixed $data The contents to store in the cache.
1427 - * @param string $group Optional. Where to group the cache contents. Default 'default'.
1428 - * @param int $expire Optional. When to expire the cache contents, in seconds.
1429 - * Default 0 (no expiration).
1430 - *
1431 - * @return bool True on success, false if cache key and group already exist.
1432 - * @throws Exception Announce database failure.
1433 - * @since 2.0.0
1434 - *
1435 - * @uses WP_Object_Cache::cache_item_exists() Checks to see if the cache already has data.
1436 - * @uses WP_Object_Cache::set() Sets the data after the checking the cache
1437 - * contents existence.
1438 - */
1439 - public function add( $key, $data, $group = 'default', $expire = 0 ) {
1440 - if ( wp_suspend_cache_addition() ) {
1441 - return false;
1377 + return $values;
1442 1378 }
1443 1379
1444 - if ( ! $this->is_valid_key( $key ) ) {
1445 - return false;
1446 - }
1380 + /**
1381 + * Adds data to the cache if it doesn't already exist.
1382 + *
1383 + * @param int|string $key What to call the contents in the cache.
1384 + * @param mixed $data The contents to store in the cache.
1385 + * @param string $group Optional. Where to group the cache contents. Default 'default'.
1386 + * @param int $expire Optional. When to expire the cache contents, in seconds.
1387 + * Default 0 (no expiration).
1388 + *
1389 + * @return bool True on success, false if cache key and group already exist.
1390 + * @throws Exception Announce database failure.
1391 + * @since 2.0.0
1392 + *
1393 + * @uses WP_Object_Cache::cache_item_exists() Checks to see if the cache already has data.
1394 + * @uses WP_Object_Cache::set() Sets the data after the checking the cache
1395 + * contents existence.
1396 + */
1397 + public function add( $key, $data, $group = 'default', $expire = 0 ) {
1398 + if ( wp_suspend_cache_addition() ) {
1399 + return false;
1400 + }
1447 1401
1448 - $name = $this->normalize_name( $key, $group );
1402 + if ( ! $this->is_valid_key( $key ) ) {
1403 + return false;
1404 + }
1449 1405
1450 - if ( $this->cache_item_not_exists( $name ) ) {
1451 - return $this->set( $key, $data, $group, (int) $expire );
1452 - }
1406 + $name = $this->normalize_name( $key, $group );
1453 1407
1454 - return false;
1455 - }
1408 + if ( $this->cache_item_not_exists( $name ) ) {
1409 + return $this->set( $key, $data, $group, (int) $expire );
1410 + }
1456 1411
1457 - /**
1458 - * Serves as a utility function to determine whether a key is valid.
1459 - *
1460 - * @param int|string $key Cache key to check for validity.
1461 - *
1462 - * @return bool Whether the key is valid.
1463 - * @since 6.1.0
1464 - */
1465 - protected function is_valid_key( $key ) {
1466 - if ( is_int( $key ) ) {
1467 - return true;
1412 + return false;
1468 1413 }
1469 1414
1470 - if ( is_string( $key ) && trim( $key ) !== '' ) {
1471 - return true;
1472 - }
1415 + /**
1416 + * Serves as a utility function to determine whether a key is valid.
1417 + *
1418 + * @param int|string $key Cache key to check for validity.
1419 + *
1420 + * @return bool Whether the key is valid.
1421 + * @since 6.1.0
1422 + */
1423 + protected function is_valid_key( $key ) {
1424 + if ( is_int( $key ) ) {
1425 + return true;
1426 + }
1473 1427
1474 - $type = gettype( $key );
1428 + if ( is_string( $key ) && trim( $key ) !== '' ) {
1429 + return true;
1430 + }
1475 1431
1476 - if ( ! function_exists( '__' ) ) {
1477 - wp_load_translations_early();
1478 - }
1432 + $type = gettype( $key );
1479 1433
1480 - // phpcs:disable WordPress.WP.I18n.MissingArgDomain,WordPress.Security.EscapeOutput.OutputNotEscaped,WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
1481 - $message =
1482 - // Core I18n Phrases
1483 - is_string( $key ) ? __( 'Cache key must not be an empty string.' )
1484 - /* translators: This i18n string is from core. */
1485 - : sprintf( __( 'Cache key must be integer or non-empty string, %s given.' ), $type );
1486 - _doing_it_wrong( sprintf( '%s::%s', __CLASS__, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['function'] ), $message, '6.1.0' );
1487 - //phpcs:enable WordPress.WP.I18n.MissingArgDomain,WordPress.Security.EscapeOutput.OutputNotEscaped,,WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
1434 + if ( ! function_exists( '__' ) ) {
1435 + wp_load_translations_early();
1436 + }
1488 1437
1489 - return false;
1490 - }
1438 + $message =
1439 + is_string( $key ) ? __( 'Cache key must not be an empty string.' )
1440 + /* translators: %s: The type of the given cache key. */
1441 + : sprintf( __( 'Cache key must be integer or non-empty string, %s given.' ), $type );
1442 + // phpcs:ignore
1443 + _doing_it_wrong( sprintf( '%s::%s', __CLASS__, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['function'] ), $message, '6.1.0' );
1491 1444
1492 - /**
1493 - * Determine whether a key exists in the cache.
1494 - *
1495 - * As a side-effect and optimization, copy the value from the SQLite store
1496 - * to RAM if it exists in the SQLite store.
1497 - *
1498 - * @param int|string $name Cache key to check for existence.
1499 - *
1500 - * @return bool Whether the key exists in the cache for the given group.
1501 - * @throws Exception Announce database failure.
1502 - * @since 3.4.0
1503 - */
1504 - protected function cache_item_exists( $name ) {
1505 - $exists = false;
1506 -
1507 - if ( array_key_exists( $name, $this->not_in_persistent_cache ) ) {
1508 1445 return false;
1509 1446 }
1510 - $val = $this->get_by_name( $name, $fetchsuccess );
1511 - if ( $fetchsuccess ) {
1512 - $this->cache[ $name ] = $val;
1513 - $exists = true;
1514 - $this->persistent_hits ++;
1515 - unset( $this->not_in_persistent_cache[ $name ] );
1516 - } else {
1517 - $this->persistent_misses ++;
1518 - $this->not_in_persistent_cache[ $name ] = true;
1519 - }
1520 1447
1521 - return $exists;
1522 - }
1448 + /**
1449 + * Determine whether a key exists in the cache.
1450 + *
1451 + * As a side-effect and optimization, copy the value from the SQLite store
1452 + * to RAM if it exists in the SQLite store.
1453 + *
1454 + * @param int|string $name Cache key to check for existence.
1455 + *
1456 + * @return bool Whether the key exists in the cache for the given group.
1457 + * @throws Exception Announce database failure.
1458 + * @since 3.4.0
1459 + */
1460 + protected function cache_item_exists( $name ) {
1461 + $exists = false;
1523 1462
1524 - /**
1525 - * Determine whether a key does not exist in the cache. either local or SQLite
1526 - *
1527 - * @param int|string $name Cache key to check for existence.
1528 - *
1529 - * @return bool Whether the key does not exists in the cache.
1530 - * @throws Exception Announce database failure.
1531 - * @since 3.4.0
1532 - */
1533 - protected function cache_item_not_exists( $name ) {
1463 + if ( array_key_exists( $name, $this->not_in_persistent_cache ) ) {
1464 + return false;
1465 + }
1466 + $val = $this->get_by_name( $name, $fetchsuccess );
1467 + if ( $fetchsuccess ) {
1468 + $this->cache[ $name ] = $val;
1469 + $exists = true;
1470 + $this->persistent_hits ++;
1471 + unset( $this->not_in_persistent_cache[ $name ] );
1472 + } else {
1473 + $this->persistent_misses ++;
1474 + $this->not_in_persistent_cache[ $name ] = true;
1475 + }
1534 1476
1535 - if ( array_key_exists( $name, $this->cache ) ) {
1536 - return false;
1477 + return $exists;
1537 1478 }
1538 - if ( array_key_exists( $name, $this->not_in_persistent_cache ) ) {
1539 - return true;
1540 - }
1541 1479
1542 - return ! $this->cache_item_exists( $name );
1543 - }
1480 + /**
1481 + * Determine whether a key does not exist in the cache. either local or SQLite
1482 + *
1483 + * @param int|string $name Cache key to check for existence.
1484 + *
1485 + * @return bool Whether the key does not exists in the cache.
1486 + * @throws Exception Announce database failure.
1487 + * @since 3.4.0
1488 + */
1489 + protected function cache_item_not_exists( $name ) {
1544 1490
1545 - /**
1546 - * Get one item from external cache.
1547 - *
1548 - * @param string $name Cache key.
1549 - * @param bool|null $success Set to true if the item was found.
1550 - *
1551 - * @return mixed|null Cached item, cloned if an object. Null if not found. (Cached item can be false.)
1552 - */
1553 - private function get_by_name( $name, &$success ) {
1554 - if ( $this->apcu_active ) {
1555 - $astart = hrtime( true );
1556 - $data = apcu_fetch( $this->apcusalt . $name, $fetchsuccess );
1557 - if ( $fetchsuccess ) {
1558 - if ( is_object( $data ) ) {
1559 - $data = clone $data;
1560 - }
1561 - ++ $this->apcu_hits;
1562 - $this->apcu_fetch_hit_times[] = hrtime( true ) - $astart;
1563 - $success = true;
1564 - return $data;
1565 - } else {
1566 - ++ $this->apcu_misses;
1567 - $this->apcu_fetch_miss_times[] = hrtime( true ) - $astart;
1491 + if ( array_key_exists( $name, $this->cache ) ) {
1492 + return false;
1568 1493 }
1494 + if ( array_key_exists( $name, $this->not_in_persistent_cache ) ) {
1495 + return true;
1496 + }
1497 +
1498 + return ! $this->cache_item_exists( $name );
1569 1499 }
1570 - $data = null;
1571 - $fetchsuccess = false;
1572 - $expires = 0;
1573 - $start = hrtime( true );
1574 - try {
1575 - $stmt = $this->getone_stmt;
1576 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1577 - $result = $stmt->execute();
1578 - $row = $result->fetchArray( SQLITE3_NUM );
1579 - $result->finalize();
1580 - if ( false !== $row ) {
1581 - $fetchsuccess = true;
1582 - $expires = $row[1];
1583 - if ( $expires < self::NOEXPIRE_TIMESTAMP_OFFSET ) {
1584 - /* Item has explicit expiration time. */
1585 - $expires -= $this->start_time;
1586 - if ( $expires <= 0 ) {
1587 - /* Item has expired */
1588 - unset ( $this->cache[ $name ] );
1589 - $this->not_in_persistent_cache [ $name ] = true;
1590 - $success = false;
1591 - return null;
1500 +
1501 + /**
1502 + * Get one item from external cache.
1503 + *
1504 + * @param string $name Cache key.
1505 + * @param bool|null $success Set to true if the item was found.
1506 + *
1507 + * @return mixed|null Cached item, cloned if an object. Null if not found. (Cached item can be false.)
1508 + */
1509 + private function get_by_name( $name, &$success ) {
1510 + if ( $this->apcu_active ) {
1511 + $astart = hrtime( true );
1512 + $data = apcu_fetch( $this->apcusalt . $name, $fetchsuccess );
1513 + if ( $fetchsuccess ) {
1514 + if ( is_object( $data ) ) {
1515 + $data = clone $data;
1592 1516 }
1517 + ++ $this->apcu_hits;
1518 + $this->apcu_fetch_hit_times[] = hrtime( true ) - $astart;
1519 + $success = true;
1520 + return $data;
1593 1521 } else {
1594 - $expires = DAY_IN_SECONDS;
1522 + ++ $this->apcu_misses;
1523 + $this->apcu_fetch_miss_times[] = hrtime( true ) - $astart;
1595 1524 }
1596 - $data = $this->reconstitute( $row[0] );
1597 1525 }
1598 - if ( $fetchsuccess ) {
1599 - /* Pull item into APCu */
1600 - if ( $this->apcu_active ) {
1601 - $astart = hrtime( true );
1602 - apcu_store( $this->apcusalt . $name, $data, $expires );
1603 - $this->apcu_store_times[] = hrtime( true ) - $astart;
1526 + $data = null;
1527 + $fetchsuccess = false;
1528 + $expires = 0;
1529 + $start = hrtime( true );
1530 + try {
1531 + $stmt = $this->getone_stmt;
1532 + $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1533 + $result = $stmt->execute();
1534 + $row = $result->fetchArray( SQLITE3_NUM );
1535 + if ( false !== $row ) {
1536 + $fetchsuccess = true;
1537 + $expires = $row[1];
1538 + $expires = ( $expires < self::NOEXPIRE_TIMESTAMP_OFFSET ) ? $expires : $expires - self::NOEXPIRE_TIMESTAMP_OFFSET;
1539 + $expires = $expires - time();
1540 + $expires = $expires > 0 ? $expires : DAY_IN_SECONDS;
1541 + $data = $this->reconstitute( $row[0] );
1604 1542 }
1543 + if ( $fetchsuccess ) {
1544 + /* Pull item into APCu */
1545 + if ( $this->apcu_active ) {
1546 + $astart = hrtime( true );
1547 + apcu_store( $this->apcusalt . $name, $data, $expires );
1548 + $this->apcu_store_times[] = hrtime( true ) - $astart;
1549 + }
1605 1550
1606 - unset ( $this->not_in_persistent_cache[ $name ] );
1607 - } else {
1608 - $this->not_in_persistent_cache [ $name ] = true;
1551 + unset ( $this->not_in_persistent_cache[ $name ] );
1552 + } else {
1553 + $this->not_in_persistent_cache [ $name ] = true;
1554 + }
1555 + $result->finalize();
1556 + } catch ( Exception $ex ) {
1557 + unset( $this->not_in_persistent_cache [ $name ] );
1558 + $this->error_log( 'get_by_name', $ex );
1559 + $this->delete_offending_files();
1560 + self::drop_dead();
1609 1561 }
1610 - } catch ( Exception $ex ) {
1611 - unset( $this->not_in_persistent_cache [ $name ] );
1612 - $this->error_log( 'get_by_name', $ex );
1613 - $this->delete_offending_files();
1614 - self::drop_dead();
1562 + $this->select_times[] = hrtime( true ) - $start;
1563 +
1564 + $success = $fetchsuccess;
1565 + return $data;
1615 1566 }
1616 - $this->select_times[] = hrtime( true ) - $start;
1617 1567
1618 - $success = $fetchsuccess;
1619 - return $data;
1620 - }
1568 + /**
1569 + * Sets the data contents into the cache.
1570 + *
1571 + * The cache contents are grouped by the $group parameter followed by the
1572 + * $key. This allows for duplicate IDs in unique groups. Therefore, naming of
1573 + * the group should be used with care and should follow normal function
1574 + * naming guidelines outside of core WordPress usage.
1575 + *
1576 + * The $expire parameter is not used, because the cache will automatically
1577 + * expire for each time a page is accessed and PHP finishes. The method is
1578 + * more for cache plugins which use files.
1579 + *
1580 + * @param int|string $key What to call the contents in the cache.
1581 + * @param mixed $data The contents to store in the cache. Objects are cloned before being stored.
1582 + * @param string $group Optional. Where to group the cache contents. Default 'default'.
1583 + * @param int $expire Optional. Not used.
1584 + *
1585 + * @return bool True if contents were set, false if key is invalid.
1586 + * @since 2.0.0
1587 + * @since 6.1.0 Returns false if cache key is invalid.
1588 + *
1589 + */
1590 + public function set( $key, $data, $group = 'default', $expire = 0 ) {
1621 1591
1622 - /**
1623 - * Get the expiration time of an item
1624 - *
1625 - * @param string $name Cache key.
1626 - *
1627 - * @return boolean | integer False if no such item, the expiration ttl (from now)), or zero if no expireation.
1628 - */
1629 - private function get_expiration_by_name( $name ) {
1630 - try {
1631 - $stmt = $this->getone_stmt;
1632 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1633 - $result = $stmt->execute();
1634 - $row = $result->fetchArray( SQLITE3_NUM );
1635 - $result->finalize();
1636 - if ( false !== $row ) {
1637 - $expires = $row[1];
1638 - if ( $expires < self::NOEXPIRE_TIMESTAMP_OFFSET ) {
1639 - /* Item has explicit expiration time. */
1640 - $expires -= $this->start_time;
1641 - if ( $expires <= 0 ) {
1642 - if ( $this->apcu_active ) {
1643 - apcu_delete( $this->apcusalt . $name );
1644 - }
1645 - return false;
1646 - } else {
1647 - return $expires;
1648 - }
1649 - } else {
1650 - return 0;
1651 - }
1652 - } else {
1592 + if ( ! $this->is_valid_key( $key ) ) {
1653 1593 return false;
1654 1594 }
1655 - } catch ( Exception $ex ) {
1656 - unset( $this->not_in_persistent_cache [ $name ] );
1657 - $this->error_log( 'get_by_name', $ex );
1658 - $this->delete_offending_files();
1659 - self::drop_dead();
1660 - }
1661 - return false;
1662 - }
1663 1595
1664 - /**
1665 - * Sets the data contents into the cache.
1666 - *
1667 - * The cache contents are grouped by the $group parameter followed by the
1668 - * $key. This allows for duplicate IDs in unique groups. Therefore, naming of
1669 - * the group should be used with care and should follow normal function
1670 - * naming guidelines outside of core WordPress usage.
1671 - *
1672 - * The $expire parameter is not used, because the cache will automatically
1673 - * expire for each time a page is accessed and PHP finishes. The method is
1674 - * more for cache plugins which use files.
1675 - *
1676 - * @param int|string $key What to call the contents in the cache.
1677 - * @param mixed $data The contents to store in the cache. Objects are cloned before being stored.
1678 - * @param string $group Optional. Where to group the cache contents. Default 'default'.
1679 - * @param int $expire Optional. Not used.
1680 - *
1681 - * @return bool True if contents were set, false if key is invalid.
1682 - * @since 2.0.0
1683 - * @since 6.1.0 Returns false if cache key is invalid.
1684 - *
1685 - */
1686 - public function set( $key, $data, $group = 'default', $expire = 0 ) {
1596 + $name = $this->normalize_name( $key, $group );
1687 1597
1688 - if ( ! $this->is_valid_key( $key ) ) {
1689 - return false;
1690 - }
1598 + if ( is_object( $data ) ) {
1599 + $data = clone $data;
1600 + }
1691 1601
1692 - $name = $this->normalize_name( $key, $group );
1602 + $this->cache[ $name ] = $data;
1693 1603
1694 - if ( is_object( $data ) ) {
1695 - $data = clone $data;
1696 - }
1604 + if ( $this->is_ignored_group( $group ) ) {
1605 + return true;
1606 + }
1697 1607
1698 - $this->cache[ $name ] = $data;
1608 + $start = hrtime( true );
1609 + $this->put_by_name( $name, $data, $expire );
1610 + $this->insert_times[] = hrtime( true ) - $start;
1699 1611
1700 - if ( $this->is_ignored_group( $group ) ) {
1701 1612 return true;
1702 1613 }
1703 1614
1704 - $start = hrtime( true );
1705 - $this->put_by_name( $name, $data, $expire );
1706 - $this->insert_times[] = hrtime( true ) - $start;
1707 -
1708 - return true;
1709 - }
1710 -
1711 - /**
1712 - * Write to the persistent cache, with timeout retry.
1713 - *
1714 - * @param string $name What to call the contents in the cache.
1715 - * @param mixed $data The contents to store in the cache.
1716 - * @param int $expire Optional.
1717 - *
1718 - * @return void
1719 - */
1720 - private function put_by_name( $name, $data, $expire ) {
1721 - $exception = null;
1722 - $expires = $expire ?: $this->noexpire_timestamp_offset;
1723 - $retries = 3;
1724 - while ( $retries -- > 0 ) {
1725 - try {
1726 - $this->actual_put_by_name( $name, $this->encode( $data ), $expires );
1727 - unset( $this->not_in_persistent_cache[ $name ] );
1728 - if ( $this->apcu_active ) {
1729 - $astart = hrtime( true );
1730 - apcu_store( $this->apcusalt . $name, $data, $expire ?: DAY_IN_SECONDS );
1731 - $this->apcu_store_times[] = hrtime( true ) - $astart;
1615 + /**
1616 + * Write to the persistent cache, with timeout retry.
1617 + *
1618 + * @param string $name What to call the contents in the cache.
1619 + * @param mixed $data The contents to store in the cache.
1620 + * @param int $expire Optional.
1621 + *
1622 + * @return void
1623 + */
1624 + private function put_by_name( $name, $data, $expire ) {
1625 + $exception = null;
1626 + $expires = $expire ?: $this->noexpire_timestamp_offset;
1627 + $retries = 3;
1628 + while ( $retries -- > 0 ) {
1629 + try {
1630 + $this->actual_put_by_name( $name, $this->encode( $data ), $expires );
1631 + unset( $this->not_in_persistent_cache[ $name ] );
1632 + if ( $this->apcu_active ) {
1633 + $astart = hrtime( true );
1634 + apcu_store( $this->apcusalt . $name, $data, $expire ?: DAY_IN_SECONDS );
1635 + $this->apcu_store_times[] = hrtime( true ) - $astart;
1636 + }
1637 + return;
1638 + } catch ( Exception $ex ) {
1639 + $exception = $ex;
1640 + if ( ! str_contains( $ex->getMessage(), 'database is locked' ) || 5 !== $this->sqlite->lastErrorCode() ) {
1641 + break;
1642 + }
1732 1643 }
1733 - return;
1734 - } catch ( Exception $ex ) {
1735 - $exception = $ex;
1736 - if ( ! str_contains( $ex->getMessage(), 'database is locked' ) || 5 !== $this->sqlite->lastErrorCode() ) {
1737 - break;
1738 - }
1644 + sleep( 1 );
1739 1645 }
1740 - sleep( 1 );
1646 + if ( $exception ) {
1647 + $this->error_log( 'put_by_name', $exception );
1648 + $this->delete_offending_files();
1649 + self::drop_dead();
1650 + }
1741 1651 }
1742 - if ( $exception ) {
1743 - $this->error_log( 'put_by_name', $exception );
1744 - $this->delete_offending_files();
1745 - self::drop_dead();
1746 - }
1747 - }
1748 1652
1749 - /**
1750 - * Actually write to the cache.
1751 - *
1752 - * @param string $name What to call the contents in the cache.
1753 - * @param string $value The seriolized value.
1754 - * @param int $expires Expiration time.
1755 - *
1756 - * @return void
1757 - */
1758 - private function actual_put_by_name( $name, $value, $expires ) {
1759 - if ( $this->apcu_supported ) {
1760 - $this->set_flag();
1761 - }
1762 - if ( $this->upsertone_stmt ) {
1763 - $stmt = $this->upsertone_stmt;
1764 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1765 - $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1766 - $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1767 - $result = $stmt->execute();
1768 - $result->finalize();
1769 - } else {
1770 - /* Pre-upsert version (pre- 3.24) of SQLite,
1771 - * Need to try update, then do insert if need be.
1772 - * Race conditions are possible, hence BEGIN / COMMIT
1773 - */
1774 - if ( ! $this->transaction_active ) {
1775 - $this->sqlite->exec( 'BEGIN' );
1653 + /**
1654 + * Actually write to the cache.
1655 + *
1656 + * @param string $name What to call the contents in the cache.
1657 + * @param string $value The seriolized value.
1658 + * @param int $expires Expiration time.
1659 + *
1660 + * @return void
1661 + */
1662 + private function actual_put_by_name( $name, $value, $expires ) {
1663 + if ( $this->apcu_supported ) {
1664 + $this->set_flag();
1776 1665 }
1777 - $stmt = $this->updateone_stmt;
1778 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1779 - $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1780 - $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1781 - $result = $stmt->execute();
1782 - $result->finalize();
1783 - if ( 0 === $this->sqlite->changes() ) {
1784 - /* Updated zero rows, so we need an insert. */
1785 - $stmt = $this->insertone_stmt;
1666 + if ( $this->upsertone_stmt ) {
1667 + $stmt = $this->upsertone_stmt;
1786 1668 $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1787 1669 $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1788 1670 $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1789 1671 $result = $stmt->execute();
1790 1672 $result->finalize();
1673 + } else {
1674 + /* Pre-upsert version (pre- 3.24) of SQLite,
1675 + * Need to try update, then do insert if need be.
1676 + * Race conditions are possible, hence BEGIN / COMMIT
1677 + */
1678 + if ( ! $this->transaction_active ) {
1679 + $this->sqlite->exec( 'BEGIN' );
1680 + }
1681 + $stmt = $this->updateone_stmt;
1682 + $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1683 + $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1684 + $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1685 + $result = $stmt->execute();
1686 + $result->finalize();
1687 + if ( 0 === $this->sqlite->changes() ) {
1688 + /* Updated zero rows, so we need an insert. */
1689 + $stmt = $this->insertone_stmt;
1690 + $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1691 + $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1692 + $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1693 + $result = $stmt->execute();
1694 + $result->finalize();
1791 1695
1696 + }
1697 + if ( ! $this->transaction_active ) {
1698 + $this->sqlite->exec( 'COMMIT' );
1699 + }
1792 1700 }
1793 - if ( ! $this->transaction_active ) {
1794 - $this->sqlite->exec( 'COMMIT' );
1701 + }
1702 +
1703 + /**
1704 + * Replaces the contents in the cache, if contents already exist.
1705 + *
1706 + * @param int|string $key What to call the contents in the cache.
1707 + * @param mixed $data The contents to store in the cache.
1708 + * @param string $group Optional. Where to group the cache contents. Default 'default'.
1709 + * @param int $expire Optional. When to expire the cache contents, in seconds.
1710 + * Default 0 (no expiration).
1711 + *
1712 + * @return bool True if contents were replaced, false if original value does not exist.
1713 + * @see WP_Object_Cache::set()
1714 + *
1715 + * @since 2.0.0
1716 + *
1717 + */
1718 + public function replace( $key, $data, $group = 'default', $expire = 0 ) {
1719 + if ( ! $this->is_valid_key( $key ) ) {
1720 + return false;
1795 1721 }
1796 - }
1797 - }
1798 1722
1799 - /**
1800 - * Replaces the contents in the cache, if contents already exist.
1801 - *
1802 - * @param int|string $key What to call the contents in the cache.
1803 - * @param mixed $data The contents to store in the cache.
1804 - * @param string $group Optional. Where to group the cache contents. Default 'default'.
1805 - * @param int $expire Optional. When to expire the cache contents, in seconds.
1806 - * Default 0 (no expiration).
1807 - *
1808 - * @return bool True if contents were replaced, false if original value does not exist.
1809 - * @see WP_Object_Cache::set()
1810 - *
1811 - * @since 2.0.0
1812 - *
1813 - */
1814 - public function replace( $key, $data, $group = 'default', $expire = 0 ) {
1815 - if ( ! $this->is_valid_key( $key ) ) {
1816 - return false;
1817 - }
1723 + $name = $this->normalize_name( $key, $data );
1818 1724
1819 - $name = $this->normalize_name( $key, $data );
1725 + if ( $this->cache_item_not_exists( $name ) ) {
1726 + return false;
1727 + }
1820 1728
1821 - if ( $this->cache_item_not_exists( $name ) ) {
1822 - return false;
1729 + return $this->set( $key, $data, $group, (int) $expire );
1823 1730 }
1824 1731
1825 - return $this->set( $key, $data, $group, (int) $expire );
1826 - }
1732 + /**
1733 + * Sets multiple values to the cache in one call.
1734 + *
1735 + * @param array $data Array of key and value to be set.
1736 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
1737 + * @param int $expire Optional. When to expire the cache contents, in seconds.
1738 + * Default 0 (no expiration).
1739 + *
1740 + * @return bool[] Array of return values, grouped by key. Each value is always true.
1741 + * @since 6.0.0
1742 + */
1743 + public function set_multiple( array &$data, $group = '', $expire = 0 ) {
1744 + if ( 0 === count( $data ) ) {
1745 + return array();
1746 + }
1747 + $values = array();
1748 + /* Sort the array to reduce index page fragmentation */
1749 + ksort( $data, SORT_NUMERIC );
1750 + try {
1751 + /* use a transaction to accelerate set_multiple */
1752 + $this->transaction_active = true;
1753 + $this->sqlite->exec( 'BEGIN' );
1754 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1827 1755
1828 - /**
1829 - * Sets multiple values to the cache in one call.
1830 - *
1831 - * @param array $data Array of key and value to be set.
1832 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
1833 - * @param int $expire Optional. When to expire the cache contents, in seconds.
1834 - * Default 0 (no expiration).
1835 - *
1836 - * @return bool[] Array of return values, grouped by key. Each value is always true.
1837 - * @since 6.0.0
1838 - */
1839 - public function set_multiple( array &$data, $group = '', $expire = 0 ) {
1840 - if ( 0 === count( $data ) ) {
1841 - return array();
1756 + foreach ( $data as $key => $value ) {
1757 + $values[ $key ] = $this->set( $key, $value, $group, $expire );
1758 + /* limit the size of the transaction, hopefully preventing timeouts in other clients */
1759 + if ( -- $transaction_size <= 0 ) {
1760 + $this->sqlite->exec( 'COMMIT' );
1761 + $this->sqlite->exec( 'BEGIN' );
1762 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1763 + }
1764 + }
1765 + $this->sqlite->exec( 'COMMIT' );
1766 + $this->transaction_active = false;
1767 + } catch ( Exception $ex ) {
1768 + $this->error_log( 'set_multiple', $ex );
1769 + $this->delete_offending_files();
1770 + self::drop_dead();
1771 + }
1772 +
1773 + return $values;
1842 1774 }
1843 - $values = array();
1844 - /* Sort the array to reduce index page fragmentation */
1845 - ksort( $data, SORT_NUMERIC );
1846 - try {
1847 - /* use a transaction to accelerate set_multiple */
1848 - $this->transaction_active = true;
1849 - $this->sqlite->exec( 'BEGIN' );
1850 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1851 1775
1852 - foreach ( $data as $key => $value ) {
1853 - $values[ $key ] = $this->set( $key, $value, $group, $expire );
1854 - /* limit the size of the transaction, hopefully preventing timeouts in other clients */
1855 - if ( -- $transaction_size <= 0 ) {
1856 - $this->sqlite->exec( 'COMMIT' );
1857 - $this->sqlite->exec( 'BEGIN' );
1858 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1776 + /**
1777 + * Retrieves multiple values from the cache in one call.
1778 + *
1779 + * @param string[]|int[] $input_keys
1780 + * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
1781 + * @param bool $force Optional. Whether to force an update of the local cache
1782 + * from the persistent cache. Default false.
1783 + *
1784 + * @return array Array of return values, grouped by key. Each value is either
1785 + * the cache contents on success, or false on failure. Objects are cloned
1786 + * before putting them in the array.
1787 + * @since 5.5.5
1788 + */
1789 + public function get_multiple( $input_keys, $group = 'default', $force = false ) {
1790 + $values = array();
1791 + if ( count( $input_keys ) <= 1 || $force ) {
1792 + /* Send the degenerate get_multiple calls, and forced calls, to plain old get. That logic is simpler. */
1793 + foreach ( $input_keys as $key ) {
1794 + $values[ $key ] = $this->get( $key, $group, $force );
1859 1795 }
1796 +
1797 + return $values;
1860 1798 }
1861 - $this->sqlite->exec( 'COMMIT' );
1862 - $this->transaction_active = false;
1863 - } catch ( Exception $ex ) {
1864 - $this->error_log( 'set_multiple', $ex );
1865 - $this->delete_offending_files();
1866 - self::drop_dead();
1867 - }
1799 + $start = hrtime( true );
1868 1800
1869 - return $values;
1870 - }
1871 -
1872 - /**
1873 - * Retrieves multiple values from the cache in one call.
1874 - *
1875 - * @param string[]|int[] $input_keys
1876 - * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
1877 - * @param bool $force Optional. Whether to force an update of the local cache
1878 - * from the persistent cache. Default false.
1879 - *
1880 - * @return array Array of return values, grouped by key. Each value is either
1881 - * the cache contents on success, or false on failure. Objects are cloned
1882 - * before putting them in the array.
1883 - * @since 5.5.5
1884 - */
1885 - public function get_multiple( $input_keys, $group = 'default', $force = false ) {
1886 - $values = array();
1887 - if ( count( $input_keys ) <= 1 || $force ) {
1888 - /* Send the degenerate get_multiple calls, and forced calls, to plain old get. That logic is simpler. */
1801 + $normalized = array();
1802 + $keys_not_found = array();
1803 + /* Find already-cached keys, pruning down the list of keys to fetch. */
1889 1804 foreach ( $input_keys as $key ) {
1890 - $values[ $key ] = $this->get( $key, $group, $force );
1805 + $name = $this->normalize_name( $key, $group );
1806 + $normalized [ $key ] = $name;
1807 + if ( array_key_exists( $name, $this->cache ) ) {
1808 + $values [ $key ] = is_object( $this->cache[ $name ] )
1809 + ? clone $this->cache[ $name ]
1810 + : $this->cache[ $name ];
1811 + ++ $this->cache_hits;
1812 + } else {
1813 + $keys_not_found[ $key ] = $name;
1814 + $values[ $key ] = false;
1815 + }
1891 1816 }
1892 1817
1893 - return $values;
1894 - }
1895 - $start = hrtime( true );
1818 + /* Examine APCu cache for stashed items. */
1819 + if ( $this->apcu_active && count( $keys_not_found ) > 0 ) {
1820 + $keys_not_found_apcu = array();
1821 + foreach ( $keys_not_found as $key => $name ) {
1822 + //TODO this can get an array form of the fetch operation.
1823 + $astart = hrtime( true );
1824 + $val = apcu_fetch( $this->apcusalt . $name, $success );
1825 + if ( $success ) {
1826 + if ( is_object( $val ) ) {
1827 + $val = clone $val;
1828 + }
1829 + ++ $this->apcu_hits;
1830 + $this->apcu_fetch_hit_times[] = hrtime( true ) - $astart;
1896 1831
1897 - $normalized = array();
1898 - $keys_not_found = array();
1899 - /* Find already-cached keys, pruning down the list of keys to fetch. */
1900 - foreach ( $input_keys as $key ) {
1901 - $name = $this->normalize_name( $key, $group );
1902 - $normalized [ $key ] = $name;
1903 - if ( array_key_exists( $name, $this->cache ) ) {
1904 - $values [ $key ] = is_object( $this->cache[ $name ] )
1905 - ? clone $this->cache[ $name ]
1906 - : $this->cache[ $name ];
1907 - ++ $this->cache_hits;
1908 - } else {
1909 - $keys_not_found[ $key ] = $name;
1910 - $values[ $key ] = false;
1832 + $values [ $key ] = $val;
1833 + } else {
1834 + ++ $this->apcu_misses;
1835 + $this->apcu_fetch_miss_times[] = hrtime( true ) - $astart;
1836 + $keys_not_found_apcu[ $key ] = $name;
1837 + }
1838 + }
1839 + $keys_not_found = $keys_not_found_apcu;
1911 1840 }
1912 - }
1913 1841
1914 - /* Examine APCu cache for stashed items. */
1915 - if ( $this->apcu_active && count( $keys_not_found ) > 0 ) {
1916 - $keys_not_found_apcu = array();
1917 - foreach ( $keys_not_found as $key => $name ) {
1918 - //TODO this can get an array form of the fetch operation.
1919 - $astart = hrtime( true );
1920 - $val = apcu_fetch( $this->apcusalt . $name, $success );
1921 - if ( $success ) {
1922 - if ( is_object( $val ) ) {
1923 - $val = clone $val;
1842 + if ( count( $keys_not_found ) <= 1 ) {
1843 + /* Degenerate case after fulfilment from RAM: handle as simple get */
1844 + foreach ( $keys_not_found as $key => $name ) {
1845 + $success = false;
1846 + $data = $this->get_by_normalized_name( $name, $success );
1847 + if ( $success ) {
1848 + $values[ $key ] = $data;
1924 1849 }
1925 - ++ $this->apcu_hits;
1926 - $this->apcu_fetch_hit_times[] = hrtime( true ) - $astart;
1850 + }
1927 1851
1928 - $values [ $key ] = $val;
1929 - } else {
1930 - ++ $this->apcu_misses;
1931 - $this->apcu_fetch_miss_times[] = hrtime( true ) - $astart;
1932 - $keys_not_found_apcu[ $key ] = $name;
1933 - }
1852 + $this->get_multiple_times[] = hrtime( true ) - $start;
1853 + $this->get_multiple_keys [] = count( $input_keys );
1854 + return $values;
1934 1855 }
1935 - $keys_not_found = $keys_not_found_apcu;
1936 - }
1937 -
1938 - if ( count( $keys_not_found ) <= 1 ) {
1939 - /* Degenerate case after fulfilment from RAM: handle as simple get */
1856 + /* split into alpha and numeric keys */
1857 + $alphakeys = array();
1858 + $intkeys = array();
1940 1859 foreach ( $keys_not_found as $key => $name ) {
1941 - $success = false;
1942 - $data = $this->get_by_normalized_name( $name, $success );
1943 - if ( $success ) {
1944 - $values[ $key ] = $data;
1860 + if ( is_numeric( $key ) && (int) $key == $key && (int) $key > 0 && (int) $key <= $this->intkey_max ) {
1861 + $intkeys [] = (int) $key;
1862 + } else {
1863 + $alphakeys [ $key ] = $name;
1945 1864 }
1946 1865 }
1866 + try {
1867 + /* Get the consecutive integer key runs */
1868 + $runs = $this->runs( $intkeys, $this->erode_gaps );
1947 1869
1948 - $this->get_multiple_times[] = hrtime( true ) - $start;
1949 - $this->get_multiple_keys [] = count( $input_keys );
1950 - return $values;
1951 - }
1952 - /* split into alpha and numeric keys */
1953 - $alphakeys = array();
1954 - $intkeys = array();
1955 - foreach ( $keys_not_found as $key => $name ) {
1956 - if ( is_numeric( $key ) && (int) $key == $key && (int) $key > 0 && (int) $key <= $this->intkey_max ) {
1957 - $intkeys [] = (int) $key;
1958 - } else {
1959 - $alphakeys [ $key ] = $name;
1960 - }
1961 - }
1962 - try {
1963 - /* Get the consecutive integer key runs */
1964 - $runs = $this->runs( $intkeys, $this->erode_gaps );
1870 + /* use a transaction to accelerate get_multiple */
1871 + $this->transaction_active = true;
1872 + $this->sqlite->exec( 'BEGIN' );
1873 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1965 1874
1966 - /* use a transaction to accelerate get_multiple */
1967 - $this->transaction_active = true;
1968 - $this->sqlite->exec( 'BEGIN' );
1969 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1970 -
1971 - /* Start by loading the consecutive runs of int keys */
1972 - foreach ( $runs as $first => $last ) {
1973 - $stmt = $this->getrange_stmt;
1974 - $stmt->bindValue( ':first', $normalized[ $first ], SQLITE3_TEXT );
1975 - $stmt->bindValue( ':last', $normalized[ $last ], SQLITE3_TEXT );
1976 - $resultset = $stmt->execute();
1977 - while ( true ) {
1978 - $row = $resultset->fetchArray( SQLITE3_NUM );
1979 - if ( ! $row ) {
1980 - break;
1981 - }
1982 - ++ $this->persistent_hits;
1983 - $name = $row[0];
1984 - $this->cache[ $name ] = $this->reconstitute( $row[1] );
1985 -
1986 - $expires = $row[2];
1987 - if ( $expires < self::NOEXPIRE_TIMESTAMP_OFFSET ) {
1988 - $expires -= $this->start_time;
1989 - } else {
1990 - $expires = DAY_IN_SECONDS;
1991 - }
1992 - if ( $expires > 0 ) {
1993 - /* Item has not expired */
1875 + /* Start by loading the consecutive runs of int keys */
1876 + foreach ( $runs as $first => $last ) {
1877 + $stmt = $this->getrange_stmt;
1878 + $stmt->bindValue( ':first', $normalized[ $first ], SQLITE3_TEXT );
1879 + $stmt->bindValue( ':last', $normalized[ $last ], SQLITE3_TEXT );
1880 + $resultset = $stmt->execute();
1881 + while ( true ) {
1882 + $row = $resultset->fetchArray( SQLITE3_NUM );
1883 + if ( ! $row ) {
1884 + break;
1885 + }
1994 1886 ++ $this->persistent_hits;
1887 + $name = $row[0];
1995 1888 $this->cache[ $name ] = $this->reconstitute( $row[1] );
1996 - unset( $this->not_in_persistent_cache[ $name ] );
1997 1889
1890 + $expires = $row[2];
1891 + $expires = ( $expires < self::NOEXPIRE_TIMESTAMP_OFFSET ) ? $expires : $expires - self::NOEXPIRE_TIMESTAMP_OFFSET;
1892 + $expires = $expires - time();
1893 + $expires = $expires > 0 ? $expires : DAY_IN_SECONDS;
1894 +
1998 1895 if ( $this->apcu_active ) {
1999 1896 $astart = hrtime( true );
2000 1897 apcu_store( $this->apcusalt . $name, $this->cache[ $name ], $expires );
2001 1898 $this->apcu_store_times[] = hrtime( true ) - $astart;
@@ -2000,26 +1897,11 @@
2000 1897 apcu_store( $this->apcusalt . $name, $this->cache[ $name ], $expires );
2001 1898 $this->apcu_store_times[] = hrtime( true ) - $astart;
2002 1899
2003 1900 }
1901 + unset( $this->not_in_persistent_cache[ $name ] );
2004 1902 }
2005 - }
2006 - $resultset->finalize();
2007 - /* limit the size of the transaction, hopefully preventing timeouts in other clients */
2008 - if ( -- $transaction_size <= 0 ) {
2009 - $this->sqlite->exec( 'COMMIT' );
2010 - $this->sqlite->exec( 'BEGIN' );
2011 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
2012 - }
2013 - }
2014 - /* Do the alpha keys, if any */
2015 - foreach ( $alphakeys as $key => $name ) {
2016 - if ( false === $values[ $key ] ) {
2017 - $success = false;
2018 - $data = $this->get_by_normalized_name( $name, $success );
2019 - if ( $success ) {
2020 - $values[ $key ] = $data;
2021 - }
1903 + $resultset->finalize();
2022 1904 /* limit the size of the transaction, hopefully preventing timeouts in other clients */
2023 1905 if ( -- $transaction_size <= 0 ) {
2024 1906 $this->sqlite->exec( 'COMMIT' );
2025 1907 $this->sqlite->exec( 'BEGIN' );
@@ -2025,1251 +1907,1211 @@
2025 1907 $this->sqlite->exec( 'BEGIN' );
2026 1908 $transaction_size = self::TRANSACTION_SIZE_LIMIT;
2027 1909 }
2028 1910 }
2029 - }
2030 - foreach ( $intkeys as $key ) {
2031 - if ( false === $values[ $key ] ) {
2032 - $success = false;
2033 - $data = $this->get_by_normalized_name( $normalized[ $key ], $success );
2034 - if ( $success ) {
2035 - $values[ $key ] = $data;
1911 + /* Do the alpha keys, if any */
1912 + foreach ( $alphakeys as $key => $name ) {
1913 + if ( false === $values[ $key ] ) {
1914 + $success = false;
1915 + $data = $this->get_by_normalized_name( $name, $success );
1916 + if ( $success ) {
1917 + $values[ $key ] = $data;
1918 + }
1919 + /* limit the size of the transaction, hopefully preventing timeouts in other clients */
1920 + if ( -- $transaction_size <= 0 ) {
1921 + $this->sqlite->exec( 'COMMIT' );
1922 + $this->sqlite->exec( 'BEGIN' );
1923 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1924 + }
2036 1925 }
2037 - /* limit the size of the transaction, hopefully preventing timeouts in other clients */
2038 - if ( -- $transaction_size <= 0 ) {
2039 - $this->sqlite->exec( 'COMMIT' );
2040 - $this->sqlite->exec( 'BEGIN' );
2041 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1926 + }
1927 + foreach ( $intkeys as $key ) {
1928 + if ( false === $values[ $key ] ) {
1929 + $success = false;
1930 + $data = $this->get_by_normalized_name( $normalized[ $key ], $success );
1931 + if ( $success ) {
1932 + $values[ $key ] = $data;
1933 + }
1934 + /* limit the size of the transaction, hopefully preventing timeouts in other clients */
1935 + if ( -- $transaction_size <= 0 ) {
1936 + $this->sqlite->exec( 'COMMIT' );
1937 + $this->sqlite->exec( 'BEGIN' );
1938 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
1939 + }
2042 1940 }
2043 1941 }
1942 + $this->sqlite->exec( 'COMMIT' );
1943 + $this->transaction_active = false;
1944 + } catch ( Exception $ex ) {
1945 + $this->error_log( 'get_multiple', $ex );
1946 + $this->delete_offending_files();
1947 + self::drop_dead();
2044 1948 }
2045 - $this->sqlite->exec( 'COMMIT' );
2046 - $this->transaction_active = false;
2047 - } catch ( Exception $ex ) {
2048 - $this->error_log( 'get_multiple', $ex );
2049 - $this->delete_offending_files();
2050 - self::drop_dead();
1949 + $this->get_multiple_keys [] = count( $input_keys );
1950 + $this->get_multiple_times [] = hrtime( true ) - $start;
1951 +
1952 + return $values;
2051 1953 }
2052 - $this->get_multiple_keys [] = count( $input_keys );
2053 - $this->get_multiple_times [] = hrtime( true ) - $start;
2054 1954
2055 - return $values;
2056 - }
1955 + /**
1956 + * Get the cache row name for a key and group.
1957 + *
1958 + * Notice that numeric keys have leading zeros applied so we can do range queries.
1959 + *
1960 + * @param int|string $key Key name.
1961 + * @param string $group Group name, default = 'default'.
1962 + *
1963 + * @return string
1964 + */
1965 + private function normalize_name( $key, $group ) {
1966 + if ( is_numeric( $key ) && (int) $key == $key && (int) $key >= 0 && (int) $key <= $this->intkey_max ) {
1967 + $key = self::INTKEY_SENTINEL . str_pad( $key, 1 + $this->intkey_length, '0', STR_PAD_LEFT );
1968 + }
2057 1969
2058 - /**
2059 - * Get the cache row name for a key and group.
2060 - *
2061 - * Notice that numeric keys have leading zeros applied so we can do range queries.
2062 - *
2063 - * @param int|string $key Key name.
2064 - * @param string $group Group name, default = 'default'.
2065 - *
2066 - * @return string
2067 - */
2068 - private function normalize_name( $key, $group ) {
2069 - if ( is_numeric( $key ) && (int) $key == $key && (int) $key >= 0 && (int) $key <= $this->intkey_max ) {
2070 - $key = self::INTKEY_SENTINEL . str_pad( $key, 1 + $this->intkey_length, '0', STR_PAD_LEFT );
1970 + if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1971 + $key = $this->blog_prefix . $key;
1972 + }
1973 + if ( empty( $group ) ) {
1974 + $group = 'default';
1975 + }
1976 +
1977 + return $group . '|' . $key;
2071 1978 }
2072 1979
2073 - if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
2074 - $key = $this->blog_prefix . $key;
2075 - }
2076 - if ( empty( $group ) ) {
2077 - $group = 'default';
2078 - }
1980 + /**
1981 + * Retrieves the cache contents, if it exists.
1982 + *
1983 + * The contents will be first attempted to be retrieved by searching by the
1984 + * key in the cache group. If the cache is hit (success) then the contents
1985 + * are returned.
1986 + *
1987 + * On failure, the number of cache misses will be incremented.
1988 + *
1989 + * @param int|string $key The key under which the cache contents are stored.
1990 + * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
1991 + * @param bool $force Optional. Whether to force an update of the local cache
1992 + * from the persistent cache. Default false.
1993 + * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
1994 + * Disambiguates a return of false, a storable value. Default null.
1995 + *
1996 + * @return mixed|false The cache contents on success, false on failure to retrieve contents.
1997 + * @since 2.0.0
1998 + */
1999 + public function get( $key, $group = 'default', $force = false, &$found = null ) {
2000 + if ( -- $this->get_depth <= 0 ) {
2001 + return false;
2002 + }
2079 2003
2080 - return $group . '|' . $key;
2081 - }
2004 + if ( ! $this->is_valid_key( $key ) ) {
2005 + ++ $this->get_depth;
2082 2006
2083 - /**
2084 - * Retrieves the cache contents, if it exists.
2085 - *
2086 - * The contents will be first attempted to be retrieved by searching by the
2087 - * key in the cache group. If the cache is hit (success) then the contents
2088 - * are returned.
2089 - *
2090 - * On failure, the number of cache misses will be incremented.
2091 - *
2092 - * @param int|string $key The key under which the cache contents are stored.
2093 - * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
2094 - * @param bool $force Optional. Whether to force an update of the local cache
2095 - * from the persistent cache. Default false.
2096 - * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
2097 - * Disambiguates a return of false, a storable value. Default null.
2098 - *
2099 - * @return mixed|false The cache contents on success, false on failure to retrieve contents.
2100 - * @since 2.0.0
2101 - */
2102 - public function get( $key, $group = 'default', $force = false, &$found = null ) {
2103 - if ( -- $this->get_depth <= 0 ) {
2104 - return false;
2105 - }
2007 + return false;
2008 + }
2106 2009
2107 - if ( ! $this->is_valid_key( $key ) ) {
2108 - ++ $this->get_depth;
2010 + $start = hrtime( true );
2011 + $name = $this->normalize_name( $key, $group );
2109 2012
2110 - return false;
2111 - }
2013 + if ( $force ) {
2014 + unset( $this->cache[ $name ] );
2015 + unset ( $this->not_in_persistent_cache[ $name ] );
2016 + }
2112 2017
2113 - $start = hrtime( true );
2114 - $name = $this->normalize_name( $key, $group );
2018 + try {
2019 + if ( array_key_exists( $name, $this->cache ) ) {
2020 + $found = true;
2021 + ++ $this->cache_hits;
2022 + ++ $this->get_depth;
2115 2023
2116 - if ( $force ) {
2117 - unset( $this->cache[ $name ] );
2118 - unset ( $this->not_in_persistent_cache[ $name ] );
2119 - }
2024 + return is_object( $this->cache[ $name ] ) ? clone( $this->cache[ $name ] ) : $this->cache[ $name ];
2025 + }
2026 + if ( $this->cache_item_exists( $name ) ) {
2027 + $found = true;
2028 + ++ $this->cache_hits;
2029 + ++ $this->get_depth;
2120 2030
2121 - try {
2122 - if ( array_key_exists( $name, $this->cache ) ) {
2123 - $found = true;
2124 - ++ $this->cache_hits;
2031 + $this->get_times[] = hrtime( true ) - $start;
2032 +
2033 + return is_object( $this->cache[ $name ] ) ? clone( $this->cache[ $name ] ) : $this->cache[ $name ];
2034 + }
2035 + } catch ( Exception $ex ) {
2036 + $this->delete_offending_files();
2037 +
2125 2038 ++ $this->get_depth;
2126 2039
2127 - return is_object( $this->cache[ $name ] ) ? clone( $this->cache[ $name ] ) : $this->cache[ $name ];
2040 + return false;
2128 2041 }
2129 - if ( $this->cache_item_exists( $name ) ) {
2130 - $found = true;
2131 - ++ $this->cache_hits;
2132 - ++ $this->get_depth;
2133 2042
2134 - $this->get_times[] = hrtime( true ) - $start;
2043 + $found = false;
2044 + $this->cache_misses ++;
2135 2045
2136 - return is_object( $this->cache[ $name ] ) ? clone( $this->cache[ $name ] ) : $this->cache[ $name ];
2137 - }
2138 - } catch ( Exception $ex ) {
2139 - $this->delete_offending_files();
2140 -
2141 2046 ++ $this->get_depth;
2142 2047
2143 2048 return false;
2144 2049 }
2145 2050
2146 - $found = false;
2147 - $this->cache_misses ++;
2051 + /**
2052 + * Retrieves the cache contents, if it exists.
2053 + *
2054 + * The contents will be first attempted to be retrieved by searching by the
2055 + * key in the cache group. If the cache is hit (success) then the contents
2056 + * are returned.
2057 + *
2058 + * On failure, the number of cache misses will be incremented.
2059 + *
2060 + * @param string $name Normalized name.
2061 + * @param bool $found Whether the key was found in the cache (passed by reference). Disambiguates a return of false, a storable value.
2062 + *
2063 + * @return mixed|false The cache contents -- clones of objects -- on success, false on failure to retrieve contents.
2064 + * @since 2.0.0
2065 + */
2066 + private function get_by_normalized_name( $name, &$found ) {
2067 + if ( -- $this->get_depth <= 0 ) {
2068 + $found = false;
2069 + return false;
2070 + }
2071 + try {
2072 + if ( array_key_exists( $name, $this->cache ) || $this->cache_item_exists( $name ) ) {
2073 + ++ $this->cache_hits;
2074 + ++ $this->get_depth;
2075 + $found = true;
2148 2076
2149 - ++ $this->get_depth;
2077 + return is_object( $this->cache[ $name ] ) ? clone( $this->cache[ $name ] ) : $this->cache[ $name ];
2078 + }
2079 + } catch ( Exception $ex ) {
2080 + $this->delete_offending_files();
2150 2081
2151 - return false;
2152 - }
2082 + ++ $this->get_depth;
2153 2083
2154 - /**
2155 - * Retrieves the cache contents, if it exists.
2156 - *
2157 - * The contents will be first attempted to be retrieved by searching by the
2158 - * key in the cache group. If the cache is hit (success) then the contents
2159 - * are returned.
2160 - *
2161 - * On failure, the number of cache misses will be incremented.
2162 - *
2163 - * @param string $name Normalized name.
2164 - * @param bool $found Whether the key was found in the cache (passed by reference). Disambiguates a return of false, a storable value.
2165 - *
2166 - * @return mixed|false The cache contents -- clones of objects -- on success, false on failure to retrieve contents.
2167 - * @since 2.0.0
2168 - */
2169 - private function get_by_normalized_name( $name, &$found ) {
2170 - if ( -- $this->get_depth <= 0 ) {
2084 + $found = false;
2085 + return false;
2086 + }
2087 + $this->cache_misses ++;
2088 + ++ $this->get_depth;
2089 +
2171 2090 $found = false;
2172 2091 return false;
2173 2092 }
2174 - try {
2175 - if ( array_key_exists( $name, $this->cache ) || $this->cache_item_exists( $name ) ) {
2176 - ++ $this->cache_hits;
2177 - ++ $this->get_depth;
2178 - $found = true;
2179 2093
2180 - return is_object( $this->cache[ $name ] ) ? clone( $this->cache[ $name ] ) : $this->cache[ $name ];
2094 + /**
2095 + * Deletes multiple values from the cache in one call.
2096 + *
2097 + * @param array $keys Array of keys to be deleted.
2098 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2099 + *
2100 + * @return bool[] Array of return values, grouped by key. Each value is either
2101 + * true on success, or false if the contents were not deleted.
2102 + * @since 6.0.0
2103 + */
2104 + public function delete_multiple( array $keys, $group = '' ) {
2105 + if ( 0 === count( $keys ) ) {
2106 + return array();
2181 2107 }
2182 - } catch ( Exception $ex ) {
2183 - $this->delete_offending_files();
2108 + $values = array();
2184 2109
2185 - ++ $this->get_depth;
2110 + /* use a transaction to accelerate delete_multiple */
2111 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
2112 + $this->transaction_active = true;
2113 + $this->sqlite->exec( 'BEGIN' );
2186 2114
2187 - $found = false;
2188 - return false;
2115 + foreach ( $keys as $key ) {
2116 + $values[ $key ] = $this->delete( $key, $group );
2117 + /* limit the size of the transaction, hopefully preventing timeouts in other clients */
2118 + if ( -- $transaction_size <= 0 ) {
2119 + $this->sqlite->exec( 'COMMIT' );
2120 + $this->sqlite->exec( 'BEGIN' );
2121 + $transaction_size = self::TRANSACTION_SIZE_LIMIT;
2122 + }
2123 + }
2124 + $this->sqlite->exec( 'COMMIT' );
2125 + $this->transaction_active = false;
2126 +
2127 + return $values;
2189 2128 }
2190 - $this->cache_misses ++;
2191 - ++ $this->get_depth;
2192 2129
2193 - $found = false;
2194 - return false;
2195 - }
2130 + /**
2131 + * Removes the contents of the cache key in the group.
2132 + *
2133 + * If the cache key does not exist in the group, then nothing will happen.
2134 + *
2135 + * @param int|string $key What the contents in the cache are called.
2136 + * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
2137 + * @param bool $deprecated Optional. Unused. Default false.
2138 + *
2139 + * @return bool True on success, false if the contents were not deleted.
2140 + * @since 2.0.0
2141 + *
2142 + */
2143 + public function delete( $key, $group = 'default', $deprecated = false ) {
2144 + if ( ! $this->is_valid_key( $key ) ) {
2145 + return false;
2146 + }
2196 2147
2197 - /**
2198 - * Deletes multiple values from the cache in one call.
2199 - *
2200 - * @param array $keys Array of keys to be deleted.
2201 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
2202 - *
2203 - * @return bool[] Array of return values, grouped by key. Each value is either
2204 - * true on success, or false if the contents were not deleted.
2205 - * @since 6.0.0
2206 - */
2207 - public function delete_multiple( array $keys, $group = '' ) {
2208 - if ( 0 === count( $keys ) ) {
2209 - return array();
2148 + $name = $this->normalize_name( $key, $group );
2149 + unset ( $this->cache[ $name ] );
2150 + $this->delete_by_name( $name );
2151 +
2152 + return true;
2210 2153 }
2211 - $values = array();
2212 2154
2213 - /* use a transaction to accelerate delete_multiple */
2214 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
2215 - $this->transaction_active = true;
2216 - $this->sqlite->exec( 'BEGIN' );
2155 + /**
2156 + * Clear the APCu cache.
2157 + * @return void
2158 + */
2159 + public function apcu_clear_cache() {
2160 + /* Immediate cache clear. */
2161 + if ( $this->apcu_active ) {
2162 + foreach ( new APCUIterator( '/^' . $this->apcusalt . '/', APC_ITER_KEY ) as $item ) {
2163 + apcu_delete( $item['key'] );
2164 + }
2165 + }
2166 + /* Deferred cache clear if we're in CLI context. */
2167 + if ( $this->apcu_supported ) {
2168 + $this->set_flag();
2169 + }
2170 + }
2217 2171
2218 - foreach ( $keys as $key ) {
2219 - $values[ $key ] = $this->delete( $key, $group );
2220 - /* limit the size of the transaction, hopefully preventing timeouts in other clients */
2221 - if ( -- $transaction_size <= 0 ) {
2222 - $this->sqlite->exec( 'COMMIT' );
2223 - $this->sqlite->exec( 'BEGIN' );
2224 - $transaction_size = self::TRANSACTION_SIZE_LIMIT;
2172 + /**
2173 + * Delete the oldest elements until the size falls below the target size.
2174 + *
2175 + * This uses a least-recently-UPDATED approach to aging the elements. A least-recently-USED
2176 + * approach requires writing the time of use to the cache with every access, and that
2177 + * is too expensive.
2178 + *
2179 + * @param int $target_size Desired size in bytes.
2180 + * @param int $current_size Current size in bytes.
2181 + *
2182 + * @return void
2183 + */
2184 + public function sqlite_delete_old( $target_size, $current_size ) {
2185 + $horizon = null;
2186 + if ( ! $this->sqlite ) {
2187 + return;
2225 2188 }
2189 + try {
2190 + if ( $target_size < $current_size ) {
2191 + $resultset = $this->sqlite_load_sizes();
2192 + if ( ! $resultset ) {
2193 + return;
2194 + }
2195 + while ( true ) {
2196 + $row = $resultset->fetchArray( SQLITE3_NUM );
2197 + if ( ! $row ) {
2198 + break;
2199 + }
2200 + /* Find the time horizon that will delete enough entries */
2201 + $horizon = $row[1];
2202 + $current_size -= $row[0];
2203 + if ( $current_size <= $target_size ) {
2204 + break;
2205 + }
2206 + }
2207 + $resultset->finalize();
2208 + if ( ! $horizon ) {
2209 + return;
2210 + }
2211 + $object_cache = self::OBJECT_CACHE_TABLE;
2212 + $offset = $this->noexpire_timestamp_offset;
2213 + $limit = self::TRANSACTION_SIZE_LIMIT;
2214 + $hit = $limit;
2215 + $cleared = false;
2216 +
2217 + while ( $hit >= $limit ) {
2218 + if ( ! $cleared ) {
2219 + /* Clear the APCu cache when we bulk-delete entries from SQLite. */
2220 + $this->apcu_clear_cache();
2221 + $cleared = true;
2222 + }
2223 + $sql = "DELETE FROM $object_cache WHERE name IN (SELECT name FROM $object_cache WHERE expires >= $offset AND expires <= $horizon LIMIT $limit)";
2224 + $this->sqlite->exec( $sql );
2225 + $hit = $this->sqlite->changes();
2226 + }
2227 + if ( $cleared ) {
2228 + /* Clear the APCu cache again after bulk delete to avoid a race condition. */
2229 + $this->apcu_clear_cache();
2230 + }
2231 + $this->sqlite->exec( 'PRAGMA optimize;' );
2232 + }
2233 + $this->checkpoint();
2234 + } catch ( Exception $ex ) {
2235 + /* Empty, intentionally. */
2236 + }
2226 2237 }
2227 - $this->sqlite->exec( 'COMMIT' );
2228 - $this->transaction_active = false;
2229 2238
2230 - return $values;
2231 - }
2239 + /**
2240 + * Delete from the persistent cache.
2241 + *
2242 + * @param string $name What to call the contents in the cache.
2243 + *
2244 + * @return void
2245 + */
2246 + private function delete_by_name( $name ) {
2247 + $exception = null;
2248 + $retries = 3;
2249 + $stmt = $this->deleteone_stmt;
2250 + $this->not_in_persistent_cache[ $name ] = true;
2251 + $start = hrtime( true );
2252 + if ( $this->apcu_active ) {
2253 + apcu_delete( $this->apcusalt . $name );
2254 + }
2255 + while ( $retries -- > 0 ) {
2256 + try {
2257 + $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
2258 + $result = $stmt->execute();
2259 + $result->finalize();
2260 + $this->delete_times[] = hrtime( true ) - $start;
2232 2261
2233 - /**
2234 - * Removes the contents of the cache key in the group.
2235 - *
2236 - * If the cache key does not exist in the group, then nothing will happen.
2237 - *
2238 - * @param int|string $key What the contents in the cache are called.
2239 - * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
2240 - * @param bool $deprecated Optional. Unused. Default false.
2241 - *
2242 - * @return bool True on success, false if the contents were not deleted.
2243 - * @since 2.0.0
2244 - *
2245 - */
2246 - public function delete( $key, $group = 'default', $deprecated = false ) {
2247 - if ( ! $this->is_valid_key( $key ) ) {
2248 - return false;
2262 + return;
2263 + } catch ( Exception $ex ) {
2264 + $exception = $ex;
2265 + if ( ! str_contains( $ex->getMessage(), 'database is locked' ) || 5 !== $this->sqlite->lastErrorCode() ) {
2266 + break;
2267 + }
2268 + }
2269 + sleep( 1 );
2270 + }
2271 + if ( $exception ) {
2272 + $this->error_log( 'delete_by_name', $exception );
2273 + $this->delete_offending_files();
2274 + self::drop_dead();
2275 + }
2249 2276 }
2250 2277
2251 - $name = $this->normalize_name( $key, $group );
2252 - unset ( $this->cache[ $name ] );
2253 - $this->delete_by_name( $name );
2278 + /**
2279 + * Increments numeric cache item's value.
2280 + *
2281 + * @param int|string $key The cache key to increment.
2282 + * @param int $offset Optional. The amount by which to increment the item's value.
2283 + * Default 1.
2284 + * @param string $group Optional. The group the key is in. Default 'default'.
2285 + *
2286 + * @return int|false The item's new value on success, false on failure.
2287 + * @since 3.3.0
2288 + */
2289 + public function incr( $key, $offset = 1, $group = 'default' ) {
2290 + if ( ! $this->is_valid_key( $key ) ) {
2291 + return false;
2292 + }
2254 2293
2255 - return true;
2256 - }
2294 + $name = $this->normalize_name( $key, $group );
2257 2295
2258 - /**
2259 - * Flush a group from the APCu cache.
2260 - *
2261 - * @param string $group to clear from APCu cache.
2262 - *
2263 - * @return void
2264 - */
2265 - public function apcu_clear_cache_group( $group ) {
2266 - /* Immediate cache group purge. */
2267 - if ( $this->apcu_active ) {
2268 - foreach ( new APCUIterator( '/^' . preg_quote( $this->apcusalt . $group . '|', '/' ) . '/', APC_ITER_KEY ) as $item ) {
2269 - apcu_delete( $item['key'] );
2296 + if ( $this->cache_item_not_exists( $name ) ) {
2297 + return false;
2270 2298 }
2271 - }
2272 - /* Deferred cache clear if we're in CLI context. */
2273 - if ( $this->apcu_supported ) {
2274 - $this->set_flag();
2275 - }
2276 - }
2277 2299
2278 - /**
2279 - * Clear the APCu cache.
2280 - * @return void
2281 - */
2282 - public function apcu_clear_cache() {
2283 - /* Immediate cache clear. */
2284 - if ( $this->apcu_active ) {
2285 - foreach ( new APCUIterator( '/^' . preg_quote( $this->apcusalt, '/' ) . '/', APC_ITER_KEY ) as $item ) {
2286 - apcu_delete( $item['key'] );
2300 + if ( ! is_numeric( $this->cache[ $name ] ) ) {
2301 + $this->cache[ $name ] = 0;
2287 2302 }
2303 +
2304 + $offset = (int) $offset;
2305 +
2306 + $this->cache[ $name ] += $offset;
2307 +
2308 + if ( $this->cache[ $name ] < 0 ) {
2309 + $this->cache[ $name ] = 0;
2310 + }
2311 + $this->put_by_name( $name, $this->cache[ $name ], 0 );
2312 +
2313 + return $this->cache[ $name ];
2288 2314 }
2289 - /* Deferred cache clear if we're in CLI context. */
2290 - if ( $this->apcu_supported ) {
2291 - $this->set_flag();
2315 +
2316 + /**
2317 + * Decrements numeric cache item's value.
2318 + *
2319 + * @param int|string $key The cache key to decrement.
2320 + * @param int $offset Optional. The amount by which to decrement the item's value.
2321 + * Default 1.
2322 + * @param string $group Optional. The group the key is in. Default 'default'.
2323 + *
2324 + * @return int|false The item's new value on success, false on failure.
2325 + * @since 3.3.0
2326 + *
2327 + */
2328 + public function decr( $key, $offset = 1, $group = 'default' ) {
2329 + return $this->incr( $key, - $offset, $group );
2292 2330 }
2293 - }
2294 2331
2295 - /**
2296 - * Delete the oldest elements until the size falls below the target size.
2297 - *
2298 - * This uses a least-recently-UPDATED approach to aging the elements. A least-recently-USED
2299 - * approach requires writing the time of use to the cache with every access, and that
2300 - * is too expensive.
2301 - *
2302 - * @param int $target_size Desired size in bytes.
2303 - * @param int $current_size Current size in bytes.
2304 - *
2305 - * @return void
2306 - */
2307 - public function sqlite_delete_old( $target_size, $current_size ) {
2308 - $horizon = null;
2309 - if ( ! $this->sqlite ) {
2310 - return;
2332 + /**
2333 + * Checkpoint and immediately vacuum.
2334 + *
2335 + * Notice that
2336 + * @return void
2337 + */
2338 + public function vacuum() {
2339 + $this->checkpoint();
2340 + $this->sqlite->exec( 'VACUUM;' );
2341 +
2311 2342 }
2312 - try {
2313 - if ( $target_size < $current_size ) {
2314 - $resultset = $this->sqlite_load_sizes();
2315 - if ( ! $resultset ) {
2316 - return;
2317 - }
2318 - while ( true ) {
2319 - $row = $resultset->fetchArray( SQLITE3_NUM );
2320 - if ( ! $row ) {
2321 - break;
2343 +
2344 + /**
2345 + * Clears the object cache of all data.
2346 + *
2347 + * @param bool $vacuum True to do a VACUUM operation.
2348 + *
2349 + * @return bool Always returns true.
2350 + * @since 2.0.0
2351 + */
2352 + public function flush( $vacuum = false ) {
2353 + try {
2354 + $this->apcu_clear_cache();
2355 + $this->cache = array();
2356 + $this->not_in_persistent_cache = array();
2357 +
2358 + $selective =
2359 + defined( 'WP_SQLITE_OBJECT_CACHE_SELECTIVE_FLUSH' ) ? WP_SQLITE_OBJECT_CACHE_SELECTIVE_FLUSH : null;
2360 +
2361 + if ( $selective && is_array( $this->unflushable_groups ) && count( $this->unflushable_groups ) > 0 ) {
2362 + $clauses = array();
2363 + foreach ( $this->unflushable_groups as $unflushable_group ) {
2364 + $unflushable_group = sanitize_key( $unflushable_group );
2365 + $clauses [] = "(name NOT LIKE '$unflushable_group|%')";
2322 2366 }
2323 - /* Find the time horizon that will delete enough entries */
2324 - $horizon = $row[1];
2325 - $current_size -= $row[0];
2326 - if ( $current_size <= $target_size ) {
2327 - break;
2367 + /* @noinspection SqlConstantCondition, SqlConstantExpression */
2368 + $limit = self::TRANSACTION_SIZE_LIMIT;
2369 + $hit = $limit;
2370 + $this->checkpoint();
2371 + $sql = 'DELETE FROM ' . $this->cache_table_name . ' WHERE name IN (SELECT name FROM ' . $this->cache_table_name . ' WHERE ' . implode( ' AND ', $clauses ) . ' LIMIT $limit);';
2372 + while ( $hit >= $limit ) {
2373 + $this->sqlite->exec( $sql );
2374 + $hit = $this->sqlite->changes();
2328 2375 }
2376 + } else {
2377 + /* SQLite's TRUNCATE TABLE equivalent */
2378 + $sql = 'DELETE FROM ' . $this->cache_table_name . ';';
2379 + $this->sqlite->exec( $sql );
2329 2380 }
2330 - $resultset->finalize();
2331 - if ( ! $horizon ) {
2332 - return;
2381 +
2382 + if ( $vacuum ) {
2383 + $this->vacuum();
2333 2384 }
2334 - $object_cache = self::OBJECT_CACHE_TABLE;
2335 - $offset = $this->noexpire_timestamp_offset;
2336 - $limit = self::TRANSACTION_SIZE_LIMIT;
2337 - $hit = $limit;
2338 - $cleared = false;
2385 + } catch ( Exception $ex ) {
2386 + $this->error_log( 'flush failure, recreate cache.', $ex );
2387 + $this->delete_offending_files();
2388 + }
2339 2389
2340 - while ( $hit >= $limit ) {
2341 - if ( ! $cleared ) {
2342 - /* Clear the APCu cache when we bulk-delete entries from SQLite. */
2343 - $this->apcu_clear_cache();
2344 - $cleared = true;
2390 + return true;
2391 + }
2392 +
2393 + /**
2394 + * Clears the in-memory cache of all data leaving the external cache untouched.
2395 + *
2396 + * @return bool Always returns true.
2397 + * @since 2.0.0
2398 + */
2399 + public function flush_runtime() {
2400 + $this->cache = array();
2401 + $this->not_in_persistent_cache = array();
2402 +
2403 + return true;
2404 + }
2405 +
2406 + /**
2407 + * Removes all cache items in a group.
2408 + *
2409 + * @param string $group Name of group to remove from cache.
2410 + *
2411 + * @return true Always returns true.
2412 + * @since 6.1.0
2413 + */
2414 + public function flush_group( $group ) {
2415 + $this->apcu_clear_cache();
2416 +
2417 + try {
2418 + $names_to_flush = array();
2419 + $prefix = $group . '|';
2420 + foreach ( $this->cache as $name => $data ) {
2421 + if ( str_starts_with( $name, $prefix ) ) {
2422 + $names_to_flush [] = $name;
2345 2423 }
2346 - $sql = "DELETE FROM $object_cache WHERE name IN (SELECT name FROM $object_cache WHERE expires >= $offset AND expires <= $horizon LIMIT $limit)";
2347 - $this->sqlite->exec( $sql );
2348 - $hit = $this->sqlite->changes();
2349 2424 }
2350 - if ( $cleared ) {
2351 - /* Clear the APCu cache again after bulk delete to avoid a race condition. */
2352 - $this->apcu_clear_cache();
2425 + foreach ( $names_to_flush as $name ) {
2426 + unset ( $this->cache[ $name ] );
2427 + $this->not_in_persistent_cache[ $name ] = true;
2353 2428 }
2354 - $this->sqlite->exec( 'PRAGMA optimize;' );
2429 + unset ( $names_to_flush );
2430 +
2431 + $stmt = $this->deletegroup_stmt;
2432 + $stmt->bindValue( ':group', $prefix, SQLITE3_TEXT );
2433 + $result = $stmt->execute();
2434 + $result->finalize();
2435 + } catch ( Exception $ex ) {
2436 + $this->error_log( 'flush_group', $ex );
2437 + $this->delete_offending_files();
2355 2438 }
2356 - $this->checkpoint( 'TRUNCATE' );
2357 - } catch ( Exception $ex ) {
2358 - /* Empty, intentionally. */
2439 + /* remove hints about what is in the persistent cache */
2440 + $this->not_in_persistent_cache = array();
2441 +
2442 + return true;
2359 2443 }
2360 - }
2361 2444
2362 - /**
2363 - * Delete from the persistent cache.
2364 - *
2365 - * @param string $name What to call the contents in the cache.
2366 - *
2367 - * @return void
2368 - */
2369 - private function delete_by_name( $name ) {
2370 - $exception = null;
2371 - $retries = 3;
2372 - $stmt = $this->deleteone_stmt;
2373 - $this->not_in_persistent_cache[ $name ] = true;
2374 - $start = hrtime( true );
2375 - if ( $this->apcu_active ) {
2376 - apcu_delete( $this->apcusalt . $name );
2445 + /**
2446 + * Sets the list of groups not to flushed cached.
2447 + *
2448 + * @param array $groups List of groups that are unflushable.
2449 + */
2450 + public function add_unflushable_groups( $groups ) {
2451 + $groups = (array) $groups;
2452 +
2453 + $this->unflushable_groups = array_unique( array_merge( $this->unflushable_groups, $groups ) );
2454 + $this->cache_group_types();
2377 2455 }
2378 - while ( $retries -- > 0 ) {
2379 - try {
2380 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
2381 - $result = $stmt->execute();
2382 - $result->finalize();
2383 - $this->delete_times[] = hrtime( true ) - $start;
2384 2456
2385 - return;
2386 - } catch ( Exception $ex ) {
2387 - $exception = $ex;
2388 - if ( ! str_contains( $ex->getMessage(), 'database is locked' ) || 5 !== $this->sqlite->lastErrorCode() ) {
2389 - break;
2457 + /**
2458 + * Sets the list of global cache groups.
2459 + *
2460 + * @param string|string[] $groups List of groups that are global.
2461 + *
2462 + * @since 3.0.0
2463 + */
2464 + public function add_global_groups( $groups ) {
2465 + $groups = (array) $groups;
2466 +
2467 + $groups = array_fill_keys( $groups, true );
2468 + $this->global_groups = array_merge( $this->global_groups, $groups );
2469 +
2470 + $this->cache_group_types();
2471 + }
2472 +
2473 + /**
2474 + * Switches the internal blog ID.
2475 + *
2476 + * This changes the blog ID used to create keys in blog specific groups.
2477 + *
2478 + * @param int $blog_id Blog ID.
2479 + *
2480 + * @since 3.5.0
2481 + *
2482 + */
2483 + public function switch_to_blog( $blog_id ) {
2484 + $blog_id = (int) $blog_id;
2485 + $this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
2486 + }
2487 +
2488 + /**
2489 + * Resets cache keys.
2490 + *
2491 + * @since 3.0.0
2492 + *
2493 + * @deprecated 3.5.0 Use WP_Object_Cache::switch_to_blog()
2494 + * @see switch_to_blog()
2495 + */
2496 + public function reset() {
2497 + _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Object_Cache::switch_to_blog()' );
2498 +
2499 + // Clear out non-global caches since the blog ID has changed.
2500 + $names_to_flush = array();
2501 + foreach ( $this->cache as $name => $data ) {
2502 + $splits = explode( '|', $name, 2 );
2503 + if ( 2 === count( $splits ) ) {
2504 + $group = $splits[0];
2505 + if ( ! isset( $this->global_groups[ $group ] ) ) {
2506 + $names_to_flush[] = $name;
2507 + }
2390 2508 }
2391 2509 }
2392 - sleep( 1 );
2510 + foreach ( $names_to_flush as $name ) {
2511 + unset ( $this->cache[ $name ] );
2512 + $this->not_in_persistent_cache[ $name ] = true;
2513 + }
2393 2514 }
2394 - if ( $exception ) {
2395 - $this->error_log( 'delete_by_name', $exception );
2396 - $this->delete_offending_files();
2397 - self::drop_dead();
2515 +
2516 + /**
2517 + * Echoes the stats of the caching.
2518 + *
2519 + * Gives the cache hits, and cache misses. Also prints every cached group,
2520 + * key and the data.
2521 + *
2522 + * @since 2.0.0
2523 + */
2524 + public function stats() {
2525 + echo '<p><strong>Cache Hits:</strong> ' . esc_html( $this->cache_hits ) . '<br />';
2526 + echo '<p><strong>Cache Misses:</strong> ' . esc_html( $this->cache_misses ) . '<br />';
2527 + echo '<p><strong>APCu Hits:</strong> ' . esc_html( $this->apcu_hits ) . '<br />';
2528 + echo '<strong>APCu Misses:</strong> ' . esc_html( $this->apcu_misses ) . '<br /></p>' . PHP_EOL;
2398 2529 }
2399 - }
2400 2530
2401 - /**
2402 - * Increments numeric cache item's value.
2403 - *
2404 - * @param int|string $key The cache key to increment.
2405 - * @param int $offset Optional. The amount by which to increment the item's value.
2406 - * Default 1.
2407 - * @param string $group Optional. The group the key is in. Default 'default'.
2408 - *
2409 - * @return int|false The item's new value on success, false on failure.
2410 - * @since 3.3.0
2411 - */
2412 - public function incr( $key, $offset = 1, $group = 'default' ) {
2413 - if ( ! $this->is_valid_key( $key ) ) {
2414 - return false;
2531 + /**
2532 + * Return the cache type. For use by "wp-cli cache type" and other display code.
2533 + *
2534 + * @return string The type of cache, "SQLite".
2535 + */
2536 + public function get_cache_type() {
2537 + return $this->apcu_active ? 'APCu|SQLite' : 'SQLite';
2415 2538 }
2416 2539
2417 - $name = $this->normalize_name( $key, $group );
2540 + /**
2541 + * Checks if the given group is part the ignored group array
2542 + *
2543 + * @param string $group Name of the group to check, pre-sanitized.
2544 + *
2545 + * @return bool
2546 + */
2547 + protected function is_ignored_group( $group ) {
2548 + return $this->is_group_of_type( $group, 'ignored' );
2549 + }
2418 2550
2419 - $this->transaction_active = true;
2420 - $this->sqlite->exec( 'BEGIN' );
2421 - if ( $this->cache_item_not_exists( $name ) ) {
2422 - $this->sqlite->exec( 'COMMIT' );
2423 - $this->transaction_active = false;
2424 - return false;
2551 + /**
2552 + * Checks the type of the given group
2553 + *
2554 + * @param string $group Name of the group to check, pre-sanitized.
2555 + * @param string $type Type of the group to check.
2556 + *
2557 + * @return bool
2558 + */
2559 + private function is_group_of_type( $group, $type ) {
2560 + return isset( $this->group_type[ $group ] ) && $this->group_type[ $group ] === $type;
2425 2561 }
2426 2562
2427 - if ( ! is_numeric( $this->cache[ $name ] ) ) {
2428 - $this->cache[ $name ] = 0;
2563 + /**
2564 + * Checks if the given group is part the global group array
2565 + *
2566 + * @param string $group Name of the group to check, pre-sanitized.
2567 + *
2568 + * @return bool
2569 + */
2570 + protected function is_global_group( $group ) {
2571 + return $this->is_group_of_type( $group, 'global' );
2429 2572 }
2430 2573
2431 - $offset = (int) $offset;
2574 + /**
2575 + * Get the names of the SQLite files.
2576 + *
2577 + * Notice there are, possibly, multiple files used to hold sqlite data.
2578 + *
2579 + * @return Generator Name of one of the possible SQLite files.
2580 + */
2581 + public function sqlite_files() {
2582 + foreach ( array( '', '-shm', '-wal', '-wal2' ) as $suffix ) {
2583 + yield $this->sqlite_path . $suffix;
2584 + }
2585 + }
2432 2586
2433 - $this->cache[ $name ] += $offset;
2587 + /**
2588 + * Delete sqlite files in hopes of recovering from trouble.
2589 + *
2590 + * @param int $retries
2591 + *
2592 + * @return void
2593 + */
2594 + private function delete_offending_files( $retries = 0 ) {
2595 + $this->apcu_clear_cache();
2596 + try {
2597 + /* It may be too early to use file.php. */
2598 + if ( false && function_exists( '__' ) ) {
2599 + error_log( "sqlite_object_cache failure, \$wp_filesystem->deleting sqlite files to retry. $retries" );
2600 + ob_start();
2601 + require_once ABSPATH . 'wp-admin/includes/file.php';
2434 2602
2435 - if ( $this->cache[ $name ] < 0 ) {
2436 - $this->cache[ $name ] = 0;
2603 + $credentials = request_filesystem_credentials( '' );
2604 + WP_Filesystem( $credentials );
2605 + global $wp_filesystem;
2606 + foreach ( $this->sqlite_files() as $file ) {
2607 + $wp_filesystem->delete( $file );
2608 + }
2609 + ob_end_clean();
2610 + } else {
2611 + error_log( "sqlite_object_cache failure, unlinking sqlite files to retry. $retries" );
2612 + ob_start();
2613 + foreach ( $this->sqlite_files() as $file ) {
2614 + if ( @file_exists(realpath($file))) {
2615 + @unlink( realpath( $file ) );
2616 + }
2617 + }
2618 + ob_end_clean();
2619 + }
2620 + } catch ( Exception $e ) {
2621 + error_log( "sqlite_object_cache cleanup failure: " . $e->getMessage() );
2622 +
2623 + }
2437 2624 }
2438 2625
2439 - /* Maintain the original TTL on these incremented items. */
2440 - $expires = $this->get_expiration_by_name( $name );
2441 - if ( false !== $expires ) {
2442 - $this->put_by_name( $name, $this->cache[ $name ], $expires );
2626 + /**
2627 + * Checkpoint the WAL log, incorporating it into the database.
2628 + *
2629 + * This should be done infrequently, but frequently enough so that the log doesn't just keep getting
2630 + * bigger in busy sites with lots of concurrency.
2631 + *
2632 + * @return void
2633 + */
2634 + private function checkpoint() {
2635 + $start = hrtime( true );
2636 + $this->sqlite->exec( 'PRAGMA wal_checkpoint(RESTART)' );
2637 + $this->checkpoint_times[] = 0.000001 * ( hrtime( true ) - $start );
2443 2638 }
2444 2639
2445 - $this->sqlite->exec( 'COMMIT' );
2446 - $this->transaction_active = false;
2447 - return $this->cache[ $name ];
2640 + /**
2641 + * Set a named flag.
2642 + *
2643 + * @param $name string The name of the flag. Default: 'insert'.
2644 + *
2645 + * @return bool true if the flag was already set, false if it wasn't.
2646 + */
2647 + public function set_flag( $name = 'insert' ) {
2648 + if ( ! $this->setflag_stmt ) {
2649 + $tbl = $this->flags_table_name;
2650 + $this->setflag_stmt =
2651 + $this->sqlite->prepare( "INSERT OR IGNORE INTO $tbl (name) VALUES (:name );" );
2652 + }
2653 + $stmt = $this->setflag_stmt;
2654 + $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
2655 + $result = $stmt->execute();
2656 + $already = 0 === $this->sqlite->changes();
2657 + $result->finalize();
2658 + return $already;
2659 + }
2660 +
2661 + /**
2662 + * Clear a named flag.
2663 + *
2664 + * @param $name string The name of the flag. Default: 'insert'.
2665 + *
2666 + * @return bool true if the flag was set, false if it wasn't.
2667 + */
2668 + public function clear_flag( $name = 'insert' ) {
2669 + if ( ! $this->clearflag_stmt ) {
2670 + $tbl = $this->flags_table_name;
2671 + $this->clearflag_stmt =
2672 + $this->sqlite->prepare( "DELETE FROM $tbl WHERE name = :name;" );
2673 + }
2674 + $stmt = $this->clearflag_stmt;
2675 + $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
2676 + $result = $stmt->execute();
2677 + $already = 1 === $this->sqlite->changes();
2678 + $result->finalize();
2679 + return $already;
2680 + }
2448 2681 }
2449 2682
2450 2683 /**
2451 - * Decrements numeric cache item's value.
2684 + * Object Cache API
2452 2685 *
2453 - * @param int|string $key The cache key to decrement.
2454 - * @param int $offset Optional. The amount by which to decrement the item's value.
2455 - * Default 1.
2456 - * @param string $group Optional. The group the key is in. Default 'default'.
2686 + * @link https://developer.wordpress.org/reference/classes/wp_object_cache/
2457 2687 *
2458 - * @return int|false The item's new value on success, false on failure.
2459 - * @since 3.3.0
2688 + * @package WordPress
2689 + * @subpackage Cache
2690 + */
2691 +
2692 + /**
2693 + * Sets up Object Cache Global and assigns it.
2460 2694 *
2695 + * @throws RuntimeException If we cannot write the db file into the specified directory.
2696 + * @since 2.0.0
2697 + *
2698 + * @global WP_Object_Cache $wp_object_cache
2461 2699 */
2462 - public function decr( $key, $offset = 1, $group = 'default' ) {
2463 - return $this->incr( $key, - $offset, $group );
2700 + function wp_cache_init() {
2701 + $message = WP_Object_Cache::has_sqlite();
2702 + if ( true === $message ) {
2703 + // We need to override this WordPress global in order to inject our cache.
2704 + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2705 + $GLOBALS['wp_object_cache'] = new WP_Object_Cache();
2706 + } else {
2707 + WP_Object_Cache::drop_dead( $message );
2708 + }
2464 2709 }
2465 2710
2466 2711 /**
2467 - * Checkpoint and immediately vacuum.
2712 + * Adds data to the cache, if the cache key doesn't already exist.
2468 2713 *
2469 - * Notice that
2470 - * @return void
2714 + * @param int|string $key The cache key to use for retrieval later.
2715 + * @param mixed $data The data to add to the cache.
2716 + * @param string $group Optional. The group to add the cache to. Enables the same key
2717 + * to be used across groups. Default empty.
2718 + * @param int $expire Optional. When the cache data should expire, in seconds.
2719 + * Default 0 (no expiration).
2720 + *
2721 + * @return bool True on success, false if cache key and group already exist.
2722 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2723 + *
2724 + * @since 2.0.0
2725 + *
2726 + * @see WP_Object_Cache::add()
2471 2727 */
2472 - public function vacuum() {
2473 - $this->checkpoint( 'TRUNCATE' );
2474 - $this->sqlite->exec( 'VACUUM;' );
2728 + function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
2729 + global $wp_object_cache;
2475 2730
2731 + return $wp_object_cache->add( $key, $data, $group, (int) $expire );
2476 2732 }
2477 2733
2478 2734 /**
2479 - * Clears the object cache of all data.
2735 + * Adds multiple values to the cache in one call.
2480 2736 *
2481 - * @param bool $vacuum True to do a VACUUM operation.
2737 + * @param array $data Array of keys and values to be set.
2738 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2739 + * @param int $expire Optional. When to expire the cache contents, in seconds.
2740 + * Default 0 (no expiration).
2482 2741 *
2483 - * @return bool Always returns true.
2484 - * @since 2.0.0
2742 + * @return bool[] Array of return values, grouped by key. Each value is either
2743 + * true on success, or false if cache key and group already exist.
2744 + * @see WP_Object_Cache::add_multiple()
2745 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2746 + *
2747 + * @since 6.0.0
2485 2748 */
2486 - public function flush( $vacuum = false ) {
2487 - try {
2488 - $this->apcu_clear_cache();
2489 - $this->cache = array();
2490 - $this->not_in_persistent_cache = array();
2749 + function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
2750 + global $wp_object_cache;
2491 2751
2492 - $selective =
2493 - defined( 'WP_SQLITE_OBJECT_CACHE_SELECTIVE_FLUSH' ) ? WP_SQLITE_OBJECT_CACHE_SELECTIVE_FLUSH : null;
2494 -
2495 - if ( $selective && is_array( $this->unflushable_groups ) && count( $this->unflushable_groups ) > 0 ) {
2496 - $clauses = array();
2497 - foreach ( $this->unflushable_groups as $unflushable_group ) {
2498 - $unflushable_group = sanitize_key( $unflushable_group );
2499 - $clauses [] = "(name NOT LIKE '$unflushable_group|%')";
2500 - }
2501 - /* @noinspection SqlConstantCondition, SqlConstantExpression */
2502 - $limit = self::TRANSACTION_SIZE_LIMIT;
2503 - $hit = $limit;
2504 - $this->checkpoint();
2505 - $sql = 'DELETE FROM ' . $this->cache_table_name . ' WHERE name IN (SELECT name FROM ' . $this->cache_table_name . ' WHERE ' . implode( ' AND ', $clauses ) . ' LIMIT $limit);';
2506 - while ( $hit >= $limit ) {
2507 - $this->sqlite->exec( $sql );
2508 - $hit = $this->sqlite->changes();
2509 - }
2510 - } else {
2511 - /* SQLite's TRUNCATE TABLE equivalent */
2512 - $sql = 'DELETE FROM ' . $this->cache_table_name . ';';
2513 - $this->sqlite->exec( $sql );
2514 - }
2515 - $this->checkpoint( 'TRUNCATE' );
2516 - $this->sqlite->exec( 'PRAGMA optimize' );
2517 -
2518 - if ( $vacuum ) {
2519 - $this->vacuum();
2520 - }
2521 - } catch ( Exception $ex ) {
2522 - $this->error_log( 'flush failure, recreate cache.', $ex );
2523 - $this->delete_offending_files();
2524 - }
2525 -
2526 - return true;
2752 + return $wp_object_cache->add_multiple( $data, $group, $expire );
2527 2753 }
2528 2754
2529 2755 /**
2530 - * Clears the in-memory cache of all data leaving the external cache untouched.
2756 + * Replaces the contents of the cache with new data.
2531 2757 *
2532 - * @return bool Always returns true.
2533 - * @throws Exception Announce SQLite failure.
2758 + * @param int|string $key The key for the cache data that should be replaced.
2759 + * @param mixed $data The new data to store in the cache.
2760 + * @param string $group Optional. The group for the cache data that should be replaced.
2761 + * Default empty.
2762 + * @param int $expire Optional. When to expire the cache contents, in seconds.
2763 + * Default 0 (no expiration).
2764 + *
2765 + * @return bool True if contents were replaced, false if original value does not exist.
2766 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2767 + *
2534 2768 * @since 2.0.0
2769 + *
2770 + * @see WP_Object_Cache::replace()
2535 2771 */
2536 - public function flush_runtime() {
2537 - $this->cache = array();
2538 - $this->not_in_persistent_cache = array();
2539 - /* Reset time horizon for subsequent fetches, for more precise expirations. */
2540 - $now = time();
2541 - if ( $now > $this->start_time ) {
2542 - /* The current time is later than the start time. possibly due to sleep or some slow operation. */
2543 - $this->start_time = $now;
2544 - $this->prepare_statements( $this->cache_table_name );
2545 - /* APCu expiration is bound to the pageview start time. So clean up expired items. */
2546 - if ( $this->apcu_active ) {
2547 - foreach ( new APCUIterator( '/^' . preg_quote( $this->apcusalt, '/' ) . '/', APC_ITER_KEY | APC_ITER_CTIME | APC_ITER_TTL ) as $item ) {
2548 - if ( $item['ttl'] !== 0 && is_numeric( $item['creation_time'] ) && $item['creation_time'] + $item['ttl'] <= $now) {
2549 - apcu_delete( $item['key'] );
2550 - }
2551 - }
2552 - }
2772 + function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
2773 + global $wp_object_cache;
2553 2774
2554 - /* Deferred cache clear if we're in CLI context. */
2555 - if ( $this->apcu_supported ) {
2556 - $this->set_flag();
2557 - }
2558 - }
2559 - return true;
2775 + return $wp_object_cache->replace( $key, $data, $group, (int) $expire );
2560 2776 }
2561 2777
2562 2778 /**
2563 - * Removes all cache items in a group.
2779 + * Saves the data to the cache.
2564 2780 *
2565 - * @param string $group Name of group to remove from cache.
2781 + * Differs from wp_cache_add() and wp_cache_replace() in that it will always write data.
2566 2782 *
2567 - * @return true Always returns true.
2568 - * @since 6.1.0
2783 + * @param int|string $key The cache key to use for retrieval later.
2784 + * @param mixed $data The contents to store in the cache.
2785 + * @param string $group Optional. Where to group the cache contents. Enables the same key
2786 + * to be used across groups. Default empty.
2787 + * @param int $expire Optional. When to expire the cache contents, in seconds.
2788 + * Default 0 (no expiration).
2789 + *
2790 + * @return bool True on success, false on failure.
2791 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2792 + *
2793 + * @since 2.0.0
2794 + *
2795 + * @see WP_Object_Cache::set()
2569 2796 */
2570 - public function flush_group( $group ) {
2571 - $this->apcu_clear_cache_group( $group );
2797 + function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
2798 + global $wp_object_cache;
2572 2799
2573 - try {
2574 - $names_to_flush = array();
2575 - $prefix = $group . '|';
2576 - foreach ( $this->cache as $name => $data ) {
2577 - if ( str_starts_with( $name, $prefix ) ) {
2578 - $names_to_flush [] = $name;
2579 - }
2580 - }
2581 - $stmt = $this->deletegroup_stmt;
2582 - $stmt->bindValue( ':group', $prefix, SQLITE3_TEXT );
2583 - $result = $stmt->execute();
2584 - $result->finalize();
2585 - } catch ( Exception $ex ) {
2586 - $this->error_log( 'flush_group', $ex );
2587 - $this->delete_offending_files();
2588 - }
2589 - foreach ( $names_to_flush as $name ) {
2590 - unset ( $this->cache[ $name ] );
2591 - $this->not_in_persistent_cache[ $name ] = true;
2592 - }
2593 - unset ( $names_to_flush );
2594 -
2595 - return true;
2800 + return $wp_object_cache->set( $key, $data, $group, (int) $expire );
2596 2801 }
2597 2802
2598 2803 /**
2599 - * Sets the list of groups not to flushed cached.
2804 + * Sets multiple values to the cache in one call.
2600 2805 *
2601 - * @param array $groups List of groups that are unflushable.
2806 + * @param array $data Array of keys and values to be set.
2807 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2808 + * @param int $expire Optional. When to expire the cache contents, in seconds.
2809 + * Default 0 (no expiration).
2810 + *
2811 + * @return bool[] Array of return values, grouped by key. Each value is either
2812 + * true on success, or false on failure.
2813 + * @see WP_Object_Cache::set_multiple()
2814 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2815 + *
2816 + * @since 6.0.0
2602 2817 */
2603 - public function add_unflushable_groups( $groups ) {
2604 - $groups = (array) $groups;
2818 + function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
2819 + global $wp_object_cache;
2605 2820
2606 - $this->unflushable_groups = array_unique( array_merge( $this->unflushable_groups, $groups ) );
2607 - $this->cache_group_types();
2821 + return $wp_object_cache->set_multiple( $data, $group, $expire );
2608 2822 }
2609 2823
2610 2824 /**
2611 - * Sets the list of global cache groups.
2825 + * Retrieves the cache contents from the cache by key and group.
2612 2826 *
2613 - * @param string|string[] $groups List of groups that are global.
2827 + * @param int|string $key The key under which the cache contents are stored.
2828 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2829 + * @param bool $force Optional. Whether to force an update of the local cache
2830 + * from the persistent cache. Default false.
2831 + * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
2832 + * Disambiguates a return of false, a storable value. Default null.
2614 2833 *
2615 - * @since 3.0.0
2834 + * @return mixed|false The cache contents on success, false on failure to retrieve contents.
2835 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2836 + *
2837 + * @since 2.0.0
2838 + *
2839 + * @see WP_Object_Cache::get()
2616 2840 */
2617 - public function add_global_groups( $groups ) {
2618 - $groups = (array) $groups;
2841 + function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
2842 + global $wp_object_cache;
2619 2843
2620 - $groups = array_fill_keys( $groups, true );
2621 - $this->global_groups = array_merge( $this->global_groups, $groups );
2622 -
2623 - $this->cache_group_types();
2844 + return $wp_object_cache->get( $key, $group, $force, $found );
2624 2845 }
2625 2846
2626 2847 /**
2627 - * Switches the internal blog ID.
2848 + * Retrieves multiple values from the cache in one call.
2628 2849 *
2629 - * This changes the blog ID used to create keys in blog specific groups.
2850 + * @param array $keys Array of keys under which the cache contents are stored.
2851 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2852 + * @param bool $force Optional. Whether to force an update of the local cache
2853 + * from the persistent cache. Default false.
2630 2854 *
2631 - * @param int $blog_id Blog ID.
2855 + * @return array Array of return values, grouped by key. Each value is either
2856 + * the cache contents on success, or false on failure.
2857 + * @see WP_Object_Cache::get_multiple()
2858 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2632 2859 *
2633 - * @since 3.5.0
2634 - *
2860 + * @since 5.5.0
2635 2861 */
2636 - public function switch_to_blog( $blog_id ) {
2637 - $blog_id = (int) $blog_id;
2638 - $this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
2862 + function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
2863 + if ( 0 === count( $keys ) ) {
2864 + return array();
2865 + }
2866 + global $wp_object_cache;
2867 +
2868 + return $wp_object_cache->get_multiple( $keys, $group, $force );
2639 2869 }
2640 2870
2641 2871 /**
2642 - * Resets cache keys.
2872 + * Removes the cache contents matching key and group.
2643 2873 *
2644 - * @since 3.0.0
2874 + * @param int|string $key What the contents in the cache are called.
2875 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2645 2876 *
2646 - * @deprecated 3.5.0 Use WP_Object_Cache::switch_to_blog()
2647 - * @see switch_to_blog()
2877 + * @return bool True on successful removal, false on failure.
2878 + * @since 2.0.0
2879 + *
2880 + * @see WP_Object_Cache::delete()
2881 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2648 2882 */
2649 - public function reset() {
2650 - _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Object_Cache::switch_to_blog()' );
2883 + function wp_cache_delete( $key, $group = '' ) {
2884 + global $wp_object_cache;
2651 2885
2652 - // Clear out non-global caches since the blog ID has changed.
2653 - $names_to_flush = array();
2654 - foreach ( $this->cache as $name => $data ) {
2655 - $splits = explode( '|', $name, 2 );
2656 - if ( 2 === count( $splits ) ) {
2657 - $group = $splits[0];
2658 - if ( ! isset( $this->global_groups[ $group ] ) ) {
2659 - $names_to_flush[] = $name;
2660 - }
2661 - }
2662 - }
2663 - foreach ( $names_to_flush as $name ) {
2664 - unset ( $this->cache[ $name ] );
2665 - $this->not_in_persistent_cache[ $name ] = true;
2666 - }
2886 + return $wp_object_cache->delete( $key, $group );
2667 2887 }
2668 2888
2669 2889 /**
2670 - * Echoes the stats of the caching.
2890 + * Deletes multiple values from the cache in one call.
2671 2891 *
2672 - * Gives the cache hits, and cache misses. Also prints every cached group,
2673 - * key and the data.
2892 + * @param array $keys Array of keys for deletion.
2893 + * @param string $group Optional. Where the cache contents are grouped. Default empty.
2674 2894 *
2675 - * @since 2.0.0
2895 + * @return bool[] Array of return values, grouped by key. Each value is either
2896 + * true on success, or false if the contents were not deleted.
2897 + * @since 6.0.0
2898 + *
2899 + * @see WP_Object_Cache::delete_multiple()
2900 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2676 2901 */
2677 - public function stats() {
2678 - echo '<p><strong>Cache Hits:</strong> ' . esc_html( $this->cache_hits ) . '<br />';
2679 - echo '<p><strong>Cache Misses:</strong> ' . esc_html( $this->cache_misses ) . '<br />';
2680 - echo '<p><strong>APCu Hits:</strong> ' . esc_html( $this->apcu_hits ) . '<br />';
2681 - echo '<strong>APCu Misses:</strong> ' . esc_html( $this->apcu_misses ) . '<br /></p>' . PHP_EOL;
2902 + function wp_cache_delete_multiple( array $keys, $group = '' ) {
2903 + global $wp_object_cache;
2904 +
2905 + return $wp_object_cache->delete_multiple( $keys, $group );
2682 2906 }
2683 2907
2684 2908 /**
2685 - * Return the cache type. For use by "wp-cli cache type" and other display code.
2909 + * Increments numeric cache item's value.
2686 2910 *
2687 - * @return string The type of cache, "SQLite".
2911 + * @param int|string $key The key for the cache contents that should be incremented.
2912 + * @param int $offset Optional. The amount by which to increment the item's value.
2913 + * Default 1.
2914 + * @param string $group Optional. The group the key is in. Default empty.
2915 + *
2916 + * @return int|false The item's new value on success, false on failure.
2917 + * @see WP_Object_Cache::incr()
2918 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2919 + *
2920 + * @since 3.3.0
2688 2921 */
2689 - public function get_cache_type() {
2690 - return $this->apcu_active ? 'APCu|SQLite' : 'SQLite';
2922 + function wp_cache_incr( $key, $offset = 1, $group = '' ) {
2923 + global $wp_object_cache;
2924 +
2925 + return $wp_object_cache->incr( $key, $offset, $group );
2691 2926 }
2692 2927
2693 2928 /**
2694 - * Checks if the given group is part the ignored group array
2929 + * Decrements numeric cache item's value.
2695 2930 *
2696 - * @param string $group Name of the group to check, pre-sanitized.
2931 + * @param int|string $key The cache key to decrement.
2932 + * @param int $offset Optional. The amount by which to decrement the item's value.
2933 + * Default 1.
2934 + * @param string $group Optional. The group the key is in. Default empty.
2697 2935 *
2698 - * @return bool
2936 + * @return int|false The item's new value on success, false on failure.
2937 + * @see WP_Object_Cache::decr()
2938 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2939 + *
2940 + * @since 3.3.0
2699 2941 */
2700 - protected function is_ignored_group( $group ) {
2701 - return $this->is_group_of_type( $group, 'ignored' );
2942 + function wp_cache_decr( $key, $offset = 1, $group = '' ) {
2943 + global $wp_object_cache;
2944 +
2945 + return $wp_object_cache->decr( $key, $offset, $group );
2702 2946 }
2703 2947
2704 2948 /**
2705 - * Checks the type of the given group
2949 + * Removes all cache items.
2706 2950 *
2707 - * @param string $group Name of the group to check, pre-sanitized.
2708 - * @param string $type Type of the group to check.
2951 + * @return bool True on success, false on failure.
2952 + * @see WP_Object_Cache::flush()
2953 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2709 2954 *
2710 - * @return bool
2955 + * @since 2.0.0
2956 + *
2711 2957 */
2712 - private function is_group_of_type( $group, $type ) {
2713 - return isset( $this->group_type[ $group ] ) && $this->group_type[ $group ] === $type;
2958 + function wp_cache_flush() {
2959 + global $wp_object_cache;
2960 +
2961 + return $wp_object_cache->flush();
2714 2962 }
2715 2963
2716 2964 /**
2717 - * Checks if the given group is part the global group array
2965 + * Removes all cache items from the in-memory runtime cache.
2718 2966 *
2719 - * @param string $group Name of the group to check, pre-sanitized.
2967 + * @return bool True on success, false on failure.
2968 + * @see WP_Object_Cache::flush()
2720 2969 *
2721 - * @return bool
2970 + * @since 6.0.0
2971 + *
2722 2972 */
2723 - protected function is_global_group( $group ) {
2724 - return $this->is_group_of_type( $group, 'global' );
2973 + function wp_cache_flush_runtime() {
2974 + global $wp_object_cache;
2975 +
2976 + return $wp_object_cache->flush_runtime();
2725 2977 }
2726 2978
2727 2979 /**
2728 - * Get the names of the SQLite files.
2980 + * Removes all cache items in a group, if the object cache implementation supports it.
2729 2981 *
2730 - * Notice there are, possibly, multiple files used to hold sqlite data.
2982 + * Before calling this function, always check for group flushing support using the
2983 + * `wp_cache_supports( 'flush_group' )` function.
2731 2984 *
2732 - * @return Generator Name of one of the possible SQLite files.
2985 + * @param string $group Name of group to remove from cache.
2986 + *
2987 + * @return bool True if group was flushed, false otherwise.
2988 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2989 + *
2990 + * @since 6.1.0
2991 + *
2992 + * @see WP_Object_Cache::flush_group()
2733 2993 */
2734 - public function sqlite_files() {
2735 - foreach ( array( '', '-shm', '-wal', '-wal2' ) as $suffix ) {
2736 - yield $this->sqlite_path . $suffix;
2737 - }
2994 + function wp_cache_flush_group( $group ) {
2995 + global $wp_object_cache;
2996 +
2997 + return $wp_object_cache->flush_group( $group );
2738 2998 }
2739 2999
2740 3000 /**
2741 - * Delete sqlite files in hopes of recovering from trouble.
3001 + * Determines whether the object cache implementation supports a particular feature.
2742 3002 *
2743 - * @param int $retries
3003 + * @param string $feature Name of the feature to check for. Possible values include:
3004 + * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
3005 + * 'flush_runtime', 'flush_group'.
2744 3006 *
2745 - * @return void
3007 + * @return bool True if the feature is supported, false otherwise.
3008 + * @since 6.1.0
2746 3009 */
2747 - private function delete_offending_files( $retries = 0 ) {
2748 - $this->apcu_clear_cache();
2749 - try {
2750 - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
2751 - error_log( "sqlite_object_cache failure, unlinking sqlite files to retry. $retries" );
2752 - ob_start();
2753 - foreach ( $this->sqlite_files() as $file ) {
2754 - if ( @file_exists( realpath( $file ) ) ) {
2755 - // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
2756 - @unlink( realpath( $file ) );
2757 - }
2758 - }
2759 - ob_end_clean();
2760 - } catch ( Exception $e ) {
2761 - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
2762 - error_log( "sqlite_object_cache cleanup failure: " . $e->getMessage() );
3010 + function wp_cache_supports( $feature ) {
3011 + switch ( $feature ) {
3012 + case 'add_multiple':
3013 + case 'set_multiple':
3014 + case 'get_multiple':
3015 + case 'delete_multiple':
3016 + case 'flush_runtime':
3017 + case 'flush_group':
3018 + return true;
3019 +
3020 + default:
3021 + return false;
2763 3022 }
2764 3023 }
2765 3024
2766 3025 /**
2767 - * Checkpoint the WAL log, incorporating it into the database.
3026 + * Closes the cache.
2768 3027 *
2769 - * This should be done infrequently, but frequently enough so that the log doesn't just keep getting
2770 - * bigger in busy sites with lots of concurrency.
3028 + * This function has ceased to do anything since WordPress 2.5. The
3029 + * functionality was removed along with the rest of the persistent cache.
2771 3030 *
2772 - * @param string $arg 'TRUNCATE' to remove WAL file or, the default, 'RESTART' to re-use it.
3031 + * This does not mean that plugins can't implement this function when they need
3032 + * to make sure that the cache is cleaned up after WordPress no longer needs it.
2773 3033 *
2774 - * @return void
3034 + * @return true Always returns true.
3035 + * @since 2.0.0
2775 3036 */
2776 - private function checkpoint( $arg = 'RESTART' ) {
2777 - $start = hrtime( true );
2778 - $this->sqlite->exec( "PRAGMA wal_checkpoint($arg)" );
2779 - $this->checkpoint_times[] = 0.000001 * ( hrtime( true ) - $start );
3037 + function wp_cache_close() {
3038 + global $wp_object_cache;
3039 +
3040 + return $wp_object_cache->close();
2780 3041 }
2781 3042
2782 3043 /**
2783 - * Set a named flag.
3044 + * Adds a group or set of groups to the list of global groups.
2784 3045 *
2785 - * @param $name string The name of the flag. Default: 'insert'.
3046 + * @param string|string[] $groups A group or an array of groups to add.
2786 3047 *
2787 - * @return bool true if the flag was already set, false if it wasn't.
3048 + * @see WP_Object_Cache::add_global_groups()
3049 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3050 + *
3051 + * @since 2.6.0
2788 3052 */
2789 - public function set_flag( $name = 'insert' ) {
2790 - if ( ! $this->setflag_stmt ) {
2791 - $tbl = $this->flags_table_name;
2792 - $this->setflag_stmt =
2793 - $this->sqlite->prepare( "INSERT OR IGNORE INTO $tbl (name) VALUES (:name );" );
2794 - }
2795 - $stmt = $this->setflag_stmt;
2796 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
2797 - $result = $stmt->execute();
2798 - $already = 0 === $this->sqlite->changes();
2799 - $result->finalize();
2800 - return $already;
3053 + function wp_cache_add_global_groups( $groups ) {
3054 + global $wp_object_cache;
3055 +
3056 + $wp_object_cache->add_global_groups( $groups );
2801 3057 }
2802 3058
2803 3059 /**
2804 - * Clear a named flag.
3060 + * Adds a group or set of groups to the list of non-persistent groups.
2805 3061 *
2806 - * @param $name string The name of the flag. Default: 'insert'.
3062 + * @param string|string[] $groups A group or an array of groups to add.
2807 3063 *
2808 - * @return bool true if the flag was set, false if it wasn't.
3064 + * @since 2.6.0
2809 3065 */
2810 - public function clear_flag( $name = 'insert' ) {
2811 - if ( ! $this->clearflag_stmt ) {
2812 - $tbl = $this->flags_table_name;
2813 - $this->clearflag_stmt =
2814 - $this->sqlite->prepare( "DELETE FROM $tbl WHERE name = :name;" );
2815 - }
2816 - $stmt = $this->clearflag_stmt;
2817 - $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
2818 - $result = $stmt->execute();
2819 - $already = 1 === $this->sqlite->changes();
2820 - $result->finalize();
2821 - return $already;
2822 - }
2823 -}
3066 + function wp_cache_add_non_persistent_groups( $groups ) {
2824 3067
2825 -/**
2826 - * Object Cache API
2827 - *
2828 - * @link https://developer.wordpress.org/reference/classes/wp_object_cache/
2829 - *
2830 - * @package WordPress
2831 - * @subpackage Cache
2832 - */
3068 + global $wp_object_cache;
2833 3069
2834 -// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
2835 -/**
2836 - * Sets up Object Cache Global and assigns it.
2837 - *
2838 - * @throws RuntimeException If we cannot write the db file into the specified directory.
2839 - * @since 2.0.0
2840 - *
2841 - * @global WP_Object_Cache $wp_object_cache
2842 - */
2843 -function wp_cache_init() {
2844 - $message = WP_Object_Cache::has_sqlite();
2845 - if ( true === $message ) {
2846 - // We need to override this WordPress global in order to inject our cache.
2847 - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2848 - $GLOBALS['wp_object_cache'] = new WP_Object_Cache();
2849 - } else {
2850 - WP_Object_Cache::drop_dead( $message );
3070 + $wp_object_cache->add_non_persistent_groups( $groups );
2851 3071 }
2852 -}
2853 3072
2854 -/**
2855 - * Adds data to the cache, if the cache key doesn't already exist.
2856 - *
2857 - * @param int|string $key The cache key to use for retrieval later.
2858 - * @param mixed $data The data to add to the cache.
2859 - * @param string $group Optional. The group to add the cache to. Enables the same key
2860 - * to be used across groups. Default empty.
2861 - * @param int $expire Optional. When the cache data should expire, in seconds.
2862 - * Default 0 (no expiration).
2863 - *
2864 - * @return bool True on success, false if cache key and group already exist.
2865 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2866 - *
2867 - * @since 2.0.0
2868 - *
2869 - * @see WP_Object_Cache::add()
2870 - */
2871 -function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
2872 - global $wp_object_cache;
3073 + /**
3074 + * Switches the internal blog ID.
3075 + *
3076 + * This changes the blog id used to create keys in blog specific groups.
3077 + *
3078 + * @param int $blog_id Site ID.
3079 + *
3080 + * @see WP_Object_Cache::switch_to_blog()
3081 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3082 + *
3083 + * @since 3.5.0
3084 + */
3085 + function wp_cache_switch_to_blog( $blog_id ) {
3086 + global $wp_object_cache;
2873 3087
2874 - return $wp_object_cache->add( $key, $data, $group, (int) $expire );
2875 -}
2876 -
2877 -/**
2878 - * Adds multiple values to the cache in one call.
2879 - *
2880 - * @param array $data Array of keys and values to be set.
2881 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
2882 - * @param int $expire Optional. When to expire the cache contents, in seconds.
2883 - * Default 0 (no expiration).
2884 - *
2885 - * @return bool[] Array of return values, grouped by key. Each value is either
2886 - * true on success, or false if cache key and group already exist.
2887 - * @see WP_Object_Cache::add_multiple()
2888 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2889 - *
2890 - * @since 6.0.0
2891 - */
2892 -function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
2893 - global $wp_object_cache;
2894 -
2895 - return $wp_object_cache->add_multiple( $data, $group, $expire );
2896 -}
2897 -
2898 -/**
2899 - * Replaces the contents of the cache with new data.
2900 - *
2901 - * @param int|string $key The key for the cache data that should be replaced.
2902 - * @param mixed $data The new data to store in the cache.
2903 - * @param string $group Optional. The group for the cache data that should be replaced.
2904 - * Default empty.
2905 - * @param int $expire Optional. When to expire the cache contents, in seconds.
2906 - * Default 0 (no expiration).
2907 - *
2908 - * @return bool True if contents were replaced, false if original value does not exist.
2909 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2910 - *
2911 - * @since 2.0.0
2912 - *
2913 - * @see WP_Object_Cache::replace()
2914 - */
2915 -function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
2916 - global $wp_object_cache;
2917 -
2918 - return $wp_object_cache->replace( $key, $data, $group, (int) $expire );
2919 -}
2920 -
2921 -/**
2922 - * Saves the data to the cache.
2923 - *
2924 - * Differs from wp_cache_add() and wp_cache_replace() in that it will always write data.
2925 - *
2926 - * @param int|string $key The cache key to use for retrieval later.
2927 - * @param mixed $data The contents to store in the cache.
2928 - * @param string $group Optional. Where to group the cache contents. Enables the same key
2929 - * to be used across groups. Default empty.
2930 - * @param int $expire Optional. When to expire the cache contents, in seconds.
2931 - * Default 0 (no expiration).
2932 - *
2933 - * @return bool True on success, false on failure.
2934 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2935 - *
2936 - * @since 2.0.0
2937 - *
2938 - * @see WP_Object_Cache::set()
2939 - */
2940 -function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
2941 - global $wp_object_cache;
2942 -
2943 - return $wp_object_cache->set( $key, $data, $group, (int) $expire );
2944 -}
2945 -
2946 -/**
2947 - * Sets multiple values to the cache in one call.
2948 - *
2949 - * @param array $data Array of keys and values to be set.
2950 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
2951 - * @param int $expire Optional. When to expire the cache contents, in seconds.
2952 - * Default 0 (no expiration).
2953 - *
2954 - * @return bool[] Array of return values, grouped by key. Each value is either
2955 - * true on success, or false on failure.
2956 - * @see WP_Object_Cache::set_multiple()
2957 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2958 - *
2959 - * @since 6.0.0
2960 - */
2961 -function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
2962 - global $wp_object_cache;
2963 -
2964 - return $wp_object_cache->set_multiple( $data, $group, $expire );
2965 -}
2966 -
2967 -/**
2968 - * Retrieves the cache contents from the cache by key and group.
2969 - *
2970 - * @param int|string $key The key under which the cache contents are stored.
2971 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
2972 - * @param bool $force Optional. Whether to force an update of the local cache
2973 - * from the persistent cache. Default false.
2974 - * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
2975 - * Disambiguates a return of false, a storable value. Default null.
2976 - *
2977 - * @return mixed|false The cache contents on success, false on failure to retrieve contents.
2978 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2979 - *
2980 - * @since 2.0.0
2981 - *
2982 - * @see WP_Object_Cache::get()
2983 - */
2984 -function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
2985 - global $wp_object_cache;
2986 -
2987 - return $wp_object_cache->get( $key, $group, $force, $found );
2988 -}
2989 -
2990 -/**
2991 - * Retrieves multiple values from the cache in one call.
2992 - *
2993 - * @param array $keys Array of keys under which the cache contents are stored.
2994 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
2995 - * @param bool $force Optional. Whether to force an update of the local cache
2996 - * from the persistent cache. Default false.
2997 - *
2998 - * @return array Array of return values, grouped by key. Each value is either
2999 - * the cache contents on success, or false on failure.
3000 - * @see WP_Object_Cache::get_multiple()
3001 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3002 - *
3003 - * @since 5.5.0
3004 - */
3005 -function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
3006 - if ( 0 === count( $keys ) ) {
3007 - return array();
3088 + $wp_object_cache->switch_to_blog( $blog_id );
3008 3089 }
3009 - global $wp_object_cache;
3010 3090
3011 - return $wp_object_cache->get_multiple( $keys, $group, $force );
3012 -}
3091 + /**
3092 + * Resets internal cache keys and structures.
3093 + *
3094 + * If the cache back end uses global blog or site IDs as part of its cache keys,
3095 + * this function instructs the back end to reset those keys and perform any cleanup
3096 + * since blog or site IDs have changed since cache init.
3097 + *
3098 + * This function is deprecated. Use wp_cache_switch_to_blog() instead of this
3099 + * function when preparing the cache for a blog switch. For clearing the cache
3100 + * during unit tests, consider using wp_cache_init(). wp_cache_init() is not
3101 + * recommended outside unit tests as the performance penalty for using it is high.
3102 + *
3103 + * @since 3.0.0
3104 + * @deprecated 3.5.0 Use wp_cache_switch_to_blog()
3105 + * @see WP_Object_Cache::reset()
3106 + *
3107 + * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3108 + */
3109 + function wp_cache_reset() {
3110 + _deprecated_function( __FUNCTION__, '3.5.0', 'wp_cache_switch_to_blog()' );
3013 3111
3014 -/**
3015 - * Removes the cache contents matching key and group.
3016 - *
3017 - * @param int|string $key What the contents in the cache are called.
3018 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
3019 - *
3020 - * @return bool True on successful removal, false on failure.
3021 - * @since 2.0.0
3022 - *
3023 - * @see WP_Object_Cache::delete()
3024 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3025 - */
3026 -function wp_cache_delete( $key, $group = '' ) {
3027 - global $wp_object_cache;
3112 + global $wp_object_cache;
3028 3113
3029 - return $wp_object_cache->delete( $key, $group );
3030 -}
3031 -
3032 -/**
3033 - * Deletes multiple values from the cache in one call.
3034 - *
3035 - * @param array $keys Array of keys for deletion.
3036 - * @param string $group Optional. Where the cache contents are grouped. Default empty.
3037 - *
3038 - * @return bool[] Array of return values, grouped by key. Each value is either
3039 - * true on success, or false if the contents were not deleted.
3040 - * @since 6.0.0
3041 - *
3042 - * @see WP_Object_Cache::delete_multiple()
3043 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3044 - */
3045 -function wp_cache_delete_multiple( array $keys, $group = '' ) {
3046 - global $wp_object_cache;
3047 -
3048 - return $wp_object_cache->delete_multiple( $keys, $group );
3049 -}
3050 -
3051 -/**
3052 - * Increments numeric cache item's value.
3053 - *
3054 - * @param int|string $key The key for the cache contents that should be incremented.
3055 - * @param int $offset Optional. The amount by which to increment the item's value.
3056 - * Default 1.
3057 - * @param string $group Optional. The group the key is in. Default empty.
3058 - *
3059 - * @return int|false The item's new value on success, false on failure.
3060 - * @see WP_Object_Cache::incr()
3061 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3062 - *
3063 - * @since 3.3.0
3064 - */
3065 -function wp_cache_incr( $key, $offset = 1, $group = '' ) {
3066 - global $wp_object_cache;
3067 -
3068 - return $wp_object_cache->incr( $key, $offset, $group );
3069 -}
3070 -
3071 -/**
3072 - * Decrements numeric cache item's value.
3073 - *
3074 - * @param int|string $key The cache key to decrement.
3075 - * @param int $offset Optional. The amount by which to decrement the item's value.
3076 - * Default 1.
3077 - * @param string $group Optional. The group the key is in. Default empty.
3078 - *
3079 - * @return int|false The item's new value on success, false on failure.
3080 - * @see WP_Object_Cache::decr()
3081 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3082 - *
3083 - * @since 3.3.0
3084 - */
3085 -function wp_cache_decr( $key, $offset = 1, $group = '' ) {
3086 - global $wp_object_cache;
3087 -
3088 - return $wp_object_cache->decr( $key, $offset, $group );
3089 -}
3090 -
3091 -/**
3092 - * Removes all cache items.
3093 - *
3094 - * @return bool True on success, false on failure.
3095 - * @see WP_Object_Cache::flush()
3096 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3097 - *
3098 - * @since 2.0.0
3099 - *
3100 - */
3101 -function wp_cache_flush() {
3102 - global $wp_object_cache;
3103 -
3104 - return $wp_object_cache->flush();
3105 -}
3106 -
3107 -/**
3108 - * Removes all cache items from the in-memory runtime cache.
3109 - *
3110 - * @return bool True on success, false on failure.
3111 - * @see WP_Object_Cache::flush()
3112 - *
3113 - * @since 6.0.0
3114 - *
3115 - */
3116 -function wp_cache_flush_runtime() {
3117 - global $wp_object_cache;
3118 -
3119 - return $wp_object_cache->flush_runtime();
3120 -}
3121 -
3122 -/**
3123 - * Removes all cache items in a group, if the object cache implementation supports it.
3124 - *
3125 - * Before calling this function, always check for group flushing support using the
3126 - * `wp_cache_supports( 'flush_group' )` function.
3127 - *
3128 - * @param string $group Name of group to remove from cache.
3129 - *
3130 - * @return bool True if group was flushed, false otherwise.
3131 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3132 - *
3133 - * @since 6.1.0
3134 - *
3135 - * @see WP_Object_Cache::flush_group()
3136 - */
3137 -function wp_cache_flush_group( $group ) {
3138 - global $wp_object_cache;
3139 -
3140 - return $wp_object_cache->flush_group( $group );
3141 -}
3142 -
3143 -/**
3144 - * Determines whether the object cache implementation supports a particular feature.
3145 - *
3146 - * @param string $feature Name of the feature to check for. Possible values include:
3147 - * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
3148 - * 'flush_runtime', 'flush_group'.
3149 - *
3150 - * @return bool True if the feature is supported, false otherwise.
3151 - * @since 6.1.0
3152 - */
3153 -function wp_cache_supports( $feature ) {
3154 - switch ( $feature ) {
3155 - case 'add_multiple':
3156 - case 'set_multiple':
3157 - case 'get_multiple':
3158 - case 'delete_multiple':
3159 - case 'flush_runtime':
3160 - case 'flush_group':
3161 - return true;
3162 -
3163 - default:
3164 - return false;
3114 + $wp_object_cache->reset();
3165 3115 }
3166 -}
3167 -
3168 -/**
3169 - * Closes the cache.
3170 - *
3171 - * This function has ceased to do anything since WordPress 2.5. The
3172 - * functionality was removed along with the rest of the persistent cache.
3173 - *
3174 - * This does not mean that plugins can't implement this function when they need
3175 - * to make sure that the cache is cleaned up after WordPress no longer needs it.
3176 - *
3177 - * @return true Always returns true.
3178 - * @since 2.0.0
3179 - */
3180 -function wp_cache_close() {
3181 - global $wp_object_cache;
3182 -
3183 - return $wp_object_cache->close();
3184 -}
3185 -
3186 -/**
3187 - * Adds a group or set of groups to the list of global groups.
3188 - *
3189 - * @param string|string[] $groups A group or an array of groups to add.
3190 - *
3191 - * @see WP_Object_Cache::add_global_groups()
3192 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3193 - *
3194 - * @since 2.6.0
3195 - */
3196 -function wp_cache_add_global_groups( $groups ) {
3197 - global $wp_object_cache;
3198 -
3199 - $wp_object_cache->add_global_groups( $groups );
3200 -}
3201 -
3202 -/**
3203 - * Adds a group or set of groups to the list of non-persistent groups.
3204 - *
3205 - * @param string|string[] $groups A group or an array of groups to add.
3206 - *
3207 - * @since 2.6.0
3208 - */
3209 -function wp_cache_add_non_persistent_groups( $groups ) {
3210 -
3211 - global $wp_object_cache;
3212 -
3213 - $wp_object_cache->add_non_persistent_groups( $groups );
3214 -}
3215 -
3216 -/**
3217 - * Switches the internal blog ID.
3218 - *
3219 - * This changes the blog id used to create keys in blog specific groups.
3220 - *
3221 - * @param int $blog_id Site ID.
3222 - *
3223 - * @see WP_Object_Cache::switch_to_blog()
3224 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3225 - *
3226 - * @since 3.5.0
3227 - */
3228 -function wp_cache_switch_to_blog( $blog_id ) {
3229 - global $wp_object_cache;
3230 -
3231 - $wp_object_cache->switch_to_blog( $blog_id );
3232 -}
3233 -
3234 -/**
3235 - * Resets internal cache keys and structures.
3236 - *
3237 - * If the cache back end uses global blog or site IDs as part of its cache keys,
3238 - * this function instructs the back end to reset those keys and perform any cleanup
3239 - * since blog or site IDs have changed since cache init.
3240 - *
3241 - * This function is deprecated. Use wp_cache_switch_to_blog() instead of this
3242 - * function when preparing the cache for a blog switch. For clearing the cache
3243 - * during unit tests, consider using wp_cache_init(). wp_cache_init() is not
3244 - * recommended outside unit tests as the performance penalty for using it is high.
3245 - *
3246 - * @since 3.0.0
3247 - * @deprecated 3.5.0 Use wp_cache_switch_to_blog()
3248 - * @see WP_Object_Cache::reset()
3249 - *
3250 - * @global WP_Object_Cache $wp_object_cache Object cache global instance.
3251 - */
3252 -function wp_cache_reset() {
3253 - _deprecated_function( __FUNCTION__, '3.5.0', 'wp_cache_switch_to_blog()' );
3254 -
3255 - global $wp_object_cache;
3256 -
3257 - $wp_object_cache->reset();
3258 -}
3259 -// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
3260 -
3261 -/**
3262 - * Manage the deferring of destructing the SQLite3 connection.
3263 - *
3264 - * This is necessary because some plugins set transients in their object destructors.
3265 - */
3266 -class SQLite_Object_Cache_Reaper {
3267 - private $db;
3268 - public function __construct( $db ) {
3269 - $this->db = $db;
3270 - }
3271 - public function __destruct() {
3272 - $this->db->close();
3273 - unset( $this->db );
3274 - }
3275 -}
3116 +endif;
3117 +// phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect