24 private $tableCache =
null;
26 private readonly
int $cacheTTL;
29 private $normalizationCallback;
31 private $insertCallback;
54 private readonly LoggerInterface $logger,
55 private readonly
string $table,
56 private readonly
string $idField,
57 private readonly
string $nameField,
58 ?callable $normalizationCallback =
null,
59 private readonly
bool|
string $domain =
false,
60 ?callable $insertCallback =
null,
62 $this->normalizationCallback = $normalizationCallback;
63 $this->cacheTTL = BagOStuff::TTL_MONTH;
64 $this->insertCallback = $insertCallback;
72 private function getDBConnection( $index, $flags = 0 ) {
73 return $this->loadBalancer->getConnection( $index, [], $this->domain, $flags );
84 private function getCacheKey() {
85 return $this->cache->makeGlobalKey(
88 $this->loadBalancer->resolveDomainID( $this->domain )
96 private function normalizeName( $name ) {
97 if ( $this->normalizationCallback ===
null ) {
100 return ( $this->normalizationCallback )( $name );
119 $name = $this->normalizeName( $name );
121 $table = $this->getTableFromCachesOrReplica();
122 $searchResult = array_search( $name, $table,
true );
123 if ( $searchResult ===
false ) {
124 $id = $this->store( $name );
126 if ( isset( $table[$id] ) ) {
132 $m =
"Got ID $id for '$name' from insert"
133 .
" into '{$this->table}', but ID $id was previously associated with"
134 .
" the name '{$table[$id]}'. Overriding the old value, which presumably"
135 .
" has been removed from the database due to a transaction rollback.";
136 $this->logger->warning( $m );
142 $this->tableCache = $table;
145 return $searchResult;
161 $dbw = $this->getDBConnection(
DB_PRIMARY, $connFlags );
162 $this->tableCache = $this->loadTable( $dbw );
163 $dbw->onTransactionPreCommitOrIdle(
function () {
164 $this->cache->delete( $this->getCacheKey() );
167 return $this->tableCache;
180 public function getId(
string $name ) {
181 $name = $this->normalizeName( $name );
183 $table = $this->getTableFromCachesOrReplica();
184 $searchResult = array_search( $name, $table,
true );
186 if ( $searchResult !==
false ) {
187 return $searchResult;
205 $table = $this->getTableFromCachesOrReplica();
206 if ( array_key_exists( $id, $table ) ) {
211 $table = $this->cache->getWithSetCallback(
212 $this->getCacheKey(),
214 function () use ( $id, $fname ) {
220 $fname .
' falling back to primary select from ' .
221 $this->table .
' with id ' . $id
224 $db = $this->getDBConnection(
$source );
225 $table = $this->loadTable( $db );
226 if ( array_key_exists( $id, $table ) ) {
233 [
'touchedCallback' =>
static function ( $oldValue ) use ( $id ) {
236 if ( !is_array( $oldValue ) || !array_key_exists( $id, $oldValue ) ) {
245 $this->tableCache = $table;
247 if ( array_key_exists( $id, $table ) ) {
262 return $this->getTableFromCachesOrReplica();
268 private function getTableFromCachesOrReplica() {
269 if ( $this->tableCache !==
null ) {
270 return $this->tableCache;
273 $table = $this->cache->getWithSetCallback(
274 $this->getCacheKey(),
278 return $this->loadTable( $dbr );
282 $this->tableCache = $table;
293 private function loadTable( IReadableDatabase $db ) {
294 $result = $db->newSelectQueryBuilder()
296 'id' => $this->idField,
297 'name' => $this->nameField
299 ->from( $this->table )
301 ->caller( __METHOD__ )->fetchResultSet();
304 foreach ( $result as $row ) {
305 $assocArray[(int)$row->id] = $row->name;
317 private function store(
string $name ) {
318 Assert::parameter( $name !==
'',
'$name',
'should not be an empty string' );
321 $dbw = $this->getDBConnection(
DB_PRIMARY, ILoadBalancer::CONN_TRX_AUTOCOMMIT );
323 $dbw->newInsertQueryBuilder()
324 ->insertInto( $this->table )
326 ->row( $this->getFieldsToStore( $name ) )
327 ->caller( __METHOD__ )->execute();
329 if ( $dbw->affectedRows() > 0 ) {
330 $id = $dbw->insertId();
332 $dbw->onTransactionPreCommitOrIdle(
334 $this->cache->delete( $this->getCacheKey() );
343 'Tried to insert name into table ' . $this->table .
', but value already existed.'
351 $id = $dbw->newSelectQueryBuilder()
352 ->select( [
'id' => $this->idField ] )
353 ->from( $this->table )
354 ->where( [ $this->nameField => $name ] )
355 ->caller( __METHOD__ )->fetchField();
357 if ( $id ===
false ) {
359 $m =
"No insert possible but primary DB didn't give us a record for " .
360 "'{$name}' in '{$this->table}'";
361 $this->logger->error( $m );
362 throw new NameTableAccessException( $m );
373 private function getFieldsToStore( $name, $id =
null ) {
376 $fields[$this->nameField] = $name;
378 if ( $id !==
null ) {
379 $fields[$this->idField] = $id;
382 if ( $this->insertCallback !==
null ) {
383 $fields = ( $this->insertCallback )( $fields );