Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
66.67% covered (warning)
66.67%
2 / 3
CRAP
90.32% covered (success)
90.32%
28 / 31
EntitySchema\DataAccess\SqlIdGenerator
0.00% covered (danger)
0.00%
0 / 1
66.67% covered (warning)
66.67%
2 / 3
7.04
90.32% covered (success)
90.32%
28 / 31
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getNewId
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 generateNewId
0.00% covered (danger)
0.00%
0 / 1
5.05
87.50% covered (warning)
87.50%
21 / 24
<?php
namespace EntitySchema\DataAccess;
use RuntimeException;
use EntitySchema\Domain\Storage\IdGenerator;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
/**
 * Unique Id generator implemented using an SQL table.
 * The table needs to have the fields id_value
 *
 * @license GPL-2.0-or-later
 * based on
 * @see \Wikibase\SqlIdGenerator
 */
class SqlIdGenerator implements IdGenerator {
    /** @var ILoadBalancer */
    private $loadBalancer;
    /** @var string */
    private $tableName;
    /**
     * @param ILoadBalancer $loadBalancer
     * @param string       $tableName
     */
    public function __construct( ILoadBalancer $loadBalancer, $tableName ) {
        $this->loadBalancer = $loadBalancer;
        $this->tableName = $tableName;
    }
    /**
     * @return int
     *
     * @throws RuntimeException
     */
    public function getNewId() {
        $database = $this->loadBalancer->getConnection( DB_MASTER );
        $id = $this->generateNewId( $database );
        $this->loadBalancer->reuseConnection( $database );
        return $id;
    }
    /**
     * Generates and returns a new ID.
     *
     * @param IDatabase $database
     * @param bool $retry Retry once in case of e.g. race conditions. Defaults to true.
     *
     * @throws RuntimeException
     * @return int
     */
    private function generateNewId( IDatabase $database, $retry = true ) {
        $database->startAtomic( __METHOD__ );
        $currentId = $database->selectRow(
            $this->tableName,
            'id_value',
            [],
            __METHOD__,
            [ 'FOR UPDATE' ]
        );
        if ( is_object( $currentId ) ) {
            $id = $currentId->id_value + 1;
            $success = $database->update(
                $this->tableName,
                [ 'id_value' => $id ],
                []
            );
        } else {
            $id = 1;
            $success = $database->insert(
                $this->tableName,
                [
                    'id_value' => $id,
                ]
            );
            // Retry once, since a race condition on initial insert can cause one to fail.
            // Race condition is possible due to occurrence of phantom reads is possible
            // at non serializable transaction isolation level.
            if ( !$success && $retry ) {
                $id = $this->generateNewId( $database, false );
                $success = true;
            }
        }
        $database->endAtomic( __METHOD__ );
        if ( !$success ) {
            throw new RuntimeException( 'Could not generate a reliably unique ID.' );
        }
        return $id;
    }
}