| @@ -89,9 +89,23 @@ | ||
| 89 | 89 | } |
| 90 | 90 | |
| 91 | 91 | // --- Commands ----------------------------------------------------------- |
| 92 | 92 | |
| 93 | - public function auth( string $password ) { | |
| 93 | + /** | |
| 94 | + * Authenticate. With a username (Redis 6+ ACL) emit the two-argument | |
| 95 | + * `AUTH <user> <pass>`; without one fall back to the legacy | |
| 96 | + * single-argument `AUTH <pass>` (which authenticates as the built-in | |
| 97 | + * `default` user). Managed hosts that provision a dedicated ACL user | |
| 98 | + * and disable `default` reject the one-arg form with WRONGPASS, so the | |
| 99 | + * username must be threaded through here. (FBS-83118) | |
| 100 | + * | |
| 101 | + * @param string $password Redis password. | |
| 102 | + * @param string $username Optional ACL username; '' = legacy default user. | |
| 103 | + */ | |
| 104 | + public function auth( string $password, string $username = '' ) { | |
| 105 | + if ( '' !== $username ) { | |
| 106 | + return $this->command( array( 'AUTH', $username, $password ) ); | |
| 107 | + } | |
| 94 | 108 | return $this->command( array( 'AUTH', $password ) ); |
| 95 | 109 | } |
| 96 | 110 | |
| 97 | 111 | public function select( int $db ) { |
| @@ -155,8 +169,40 @@ | ||
| 155 | 169 | |
| 156 | 170 | public function flushDB(): bool { |
| 157 | 171 | $r = $this->command( array( 'FLUSHDB' ) ); |
| 158 | 172 | return '+OK' === $r || 'OK' === $r; |
| 173 | + } | |
| 174 | + | |
| 175 | + /** | |
| 176 | + * Delete every key matching a glob-style pattern, using a non-blocking | |
| 177 | + * SCAN cursor (never KEYS, which blocks the whole server on large | |
| 178 | + * datasets). Returns the number of keys deleted. Used for prefix-scoped | |
| 179 | + * flushes so we only ever touch this site's namespace, never the whole | |
| 180 | + * Redis DB. (FBS-83119) | |
| 181 | + * | |
| 182 | + * @param string $pattern e.g. "salt:*" or "salt:*:options:*". | |
| 183 | + * @param int $count SCAN COUNT hint (batch size per round trip). | |
| 184 | + */ | |
| 185 | + public function delete_by_pattern( string $pattern, int $count = 500 ): int { | |
| 186 | + $deleted = 0; | |
| 187 | + $cursor = '0'; | |
| 188 | + do { | |
| 189 | + $reply = $this->command( array( 'SCAN', $cursor, 'MATCH', $pattern, 'COUNT', (string) $count ) ); | |
| 190 | + // Expected: [ next_cursor, [ key, key, ... ] ]. Anything else | |
| 191 | + // (false on socket error, malformed) ends the loop safely. | |
| 192 | + if ( ! is_array( $reply ) || count( $reply ) < 2 || ! is_array( $reply[1] ) ) { | |
| 193 | + break; | |
| 194 | + } | |
| 195 | + $cursor = (string) $reply[0]; | |
| 196 | + $keys = $reply[1]; | |
| 197 | + if ( ! empty( $keys ) ) { | |
| 198 | + // DEL accepts variadic keys — one round trip per batch. | |
| 199 | + $args = array_merge( array( 'DEL' ), array_map( 'strval', $keys ) ); | |
| 200 | + $n = $this->command( $args ); | |
| 201 | + $deleted += is_int( $n ) ? $n : 0; | |
| 202 | + } | |
| 203 | + } while ( '0' !== $cursor ); | |
| 204 | + return $deleted; | |
| 159 | 205 | } |
| 160 | 206 | |
| 161 | 207 | public function close(): void { |
| 162 | 208 | if ( is_resource( $this->sock ) && ! $this->persistent ) { |