25 private $tableCache =
null;
27 private readonly
int $cacheTTL;
30 private $normalizationCallback;
32 private $insertCallback;
55 private readonly LoggerInterface $logger,
56 private readonly
string $table,
57 private readonly
string $idField,
58 private readonly
string $nameField,
59 ?callable $normalizationCallback =
null,
60 private readonly
bool|
string $domain =
false,
61 ?callable $insertCallback =
null,
63 $this->normalizationCallback = $normalizationCallback;
64 $this->cacheTTL = BagOStuff::TTL_MONTH;
65 $this->insertCallback = $insertCallback;
73 private function getDBConnection( $index, $flags = 0 ) {
74 return $this->loadBalancer->getConnection( $index, [], $this->domain, $flags );
85 private function getCacheKey() {
86 return $this->cache->makeGlobalKey(
89 $this->loadBalancer->resolveDomainID( $this->domain )
97 private function normalizeName( $name ) {
98 if ( $this->normalizationCallback ===
null ) {
101 return ( $this->normalizationCallback )( $name );
120 $name = $this->normalizeName( $name );
122 $table = $this->getTableFromCachesOrReplica();
123 $searchResult = array_search( $name, $table,
true );
124 if ( $searchResult ===
false ) {
125 $id = $this->store( $name );
127 if ( isset( $table[$id] ) ) {
133 $m =
"Got ID $id for '$name' from insert"
134 .
" into '{$this->table}', but ID $id was previously associated with"
135 .
" the name '{$table[$id]}'. Overriding the old value, which presumably"
136 .
" has been removed from the database due to a transaction rollback.";
137 $this->logger->warning( $m );
143 $this->tableCache = $table;
146 return $searchResult;
162 $dbw = $this->getDBConnection(
DB_PRIMARY, $connFlags );
163 $this->tableCache = $this->loadTable( $dbw );
164 $dbw->onTransactionPreCommitOrIdle(
function () {
165 $this->cache->delete( $this->getCacheKey() );
168 return $this->tableCache;
181 public function getId(
string $name ) {
182 $name = $this->normalizeName( $name );
184 $table = $this->getTableFromCachesOrReplica();
185 $searchResult = array_search( $name, $table,
true );
187 if ( $searchResult !==
false ) {
188 return $searchResult;
206 $table = $this->getTableFromCachesOrReplica();
207 if ( array_key_exists( $id, $table ) ) {
212 $table = $this->cache->getWithSetCallback(
213 $this->getCacheKey(),
215 function ( $oldValue, &$ttl, &$setOpts ) use ( $id, $fname ) {
217 if ( is_array( $oldValue ) && array_key_exists( $id, $oldValue ) ) {
219 $ttl = WANObjectCache::TTL_UNCACHEABLE;
228 $fname .
' falling back to primary select from ' .
229 $this->table .
' with id ' . $id
232 $db = $this->getDBConnection(
$source );
234 $table = $this->loadTable( $db );
235 if ( array_key_exists( $id, $table ) ) {
240 $setOpts += $cacheSetOpts;
247 $this->tableCache = $table;
249 if ( array_key_exists( $id, $table ) ) {
264 return $this->getTableFromCachesOrReplica();
270 private function getTableFromCachesOrReplica() {
271 if ( $this->tableCache !==
null ) {
272 return $this->tableCache;
275 $table = $this->cache->getWithSetCallback(
276 $this->getCacheKey(),
278 function ( $oldValue, &$ttl, &$setOpts ) {
281 return $this->loadTable( $dbr );
285 $this->tableCache = $table;
296 private function loadTable( IReadableDatabase $db ) {
297 $result = $db->newSelectQueryBuilder()
299 'id' => $this->idField,
300 'name' => $this->nameField
302 ->from( $this->table )
304 ->caller( __METHOD__ )->fetchResultSet();
307 foreach ( $result as $row ) {
308 $assocArray[(int)$row->id] = $row->name;
320 private function store(
string $name ) {
321 Assert::parameter( $name !==
'',
'$name',
'should not be an empty string' );
324 $dbw = $this->getDBConnection(
DB_PRIMARY, ILoadBalancer::CONN_TRX_AUTOCOMMIT );
326 $dbw->newInsertQueryBuilder()
327 ->insertInto( $this->table )
329 ->row( $this->getFieldsToStore( $name ) )
330 ->caller( __METHOD__ )->execute();
332 if ( $dbw->affectedRows() > 0 ) {
333 $id = $dbw->insertId();
335 $dbw->onTransactionPreCommitOrIdle(
337 $this->cache->delete( $this->getCacheKey() );
346 'Tried to insert name into table ' . $this->table .
', but value already existed.'
354 $id = $dbw->newSelectQueryBuilder()
355 ->select( [
'id' => $this->idField ] )
356 ->from( $this->table )
357 ->where( [ $this->nameField => $name ] )
358 ->caller( __METHOD__ )->fetchField();
360 if ( $id ===
false ) {
362 $m =
"No insert possible but primary DB didn't give us a record for " .
363 "'{$name}' in '{$this->table}'";
364 $this->logger->error( $m );
365 throw new NameTableAccessException( $m );
376 private function getFieldsToStore( $name, $id =
null ) {
379 $fields[$this->nameField] = $name;
381 if ( $id !==
null ) {
382 $fields[$this->idField] = $id;
385 if ( $this->insertCallback !==
null ) {
386 $fields = ( $this->insertCallback )( $fields );