SQLAzureShardManager.php 5.61 KB
Newer Older
1 2 3 4 5
<?php

namespace Doctrine\DBAL\Sharding\SQLAzure;

use Doctrine\DBAL\Connection;
6 7
use Doctrine\DBAL\Sharding\ShardingException;
use Doctrine\DBAL\Sharding\ShardManager;
8
use Doctrine\DBAL\Types\Type;
9
use RuntimeException;
10 11 12
use function is_bool;
use function is_scalar;
use function sprintf;
13 14 15 16 17 18

/**
 * Sharding using the SQL Azure Federations support.
 */
class SQLAzureShardManager implements ShardManager
{
19
    /** @var string */
20 21
    private $federationName;

22
    /** @var bool */
23 24
    private $filteringEnabled;

25
    /** @var string */
26 27
    private $distributionKey;

28
    /** @var string */
29 30
    private $distributionType;

31
    /** @var Connection */
32 33
    private $conn;

34
    /** @var string|null */
35 36 37
    private $currentDistributionValue;

    /**
38
     * @throws ShardingException
39 40 41 42
     */
    public function __construct(Connection $conn)
    {
        $this->conn = $conn;
43
        $params     = $conn->getParams();
44

45
        if (! isset($params['sharding']['federationName'])) {
46 47 48
            throw ShardingException::missingDefaultFederationName();
        }

49
        if (! isset($params['sharding']['distributionKey'])) {
50 51 52
            throw ShardingException::missingDefaultDistributionKey();
        }

53
        if (! isset($params['sharding']['distributionType'])) {
54 55 56
            throw ShardingException::missingDistributionType();
        }

57 58
        $this->federationName   = $params['sharding']['federationName'];
        $this->distributionKey  = $params['sharding']['distributionKey'];
59
        $this->distributionType = $params['sharding']['distributionType'];
60
        $this->filteringEnabled = (bool) ($params['sharding']['filteringEnabled'] ?? false);
61 62 63
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
64
     * Gets the name of the federation.
65 66 67 68 69 70 71 72 73
     *
     * @return string
     */
    public function getFederationName()
    {
        return $this->federationName;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
74
     * Gets the distribution key.
75 76 77 78 79 80 81 82 83
     *
     * @return string
     */
    public function getDistributionKey()
    {
        return $this->distributionKey;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
84
     * Gets the Doctrine Type name used for the distribution.
85 86 87 88 89 90 91 92 93
     *
     * @return string
     */
    public function getDistributionType()
    {
        return $this->distributionType;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
94 95
     * Sets Enabled/Disable filtering on the fly.
     *
96
     * @param bool $flag
97 98 99 100 101
     *
     * @return void
     */
    public function setFilteringEnabled($flag)
    {
102
        $this->filteringEnabled = (bool) $flag;
103 104 105 106 107 108 109 110 111 112 113
    }

    /**
     * {@inheritDoc}
     */
    public function selectGlobal()
    {
        if ($this->conn->isTransactionActive()) {
            throw ShardingException::activeTransaction();
        }

114
        $sql = 'USE FEDERATION ROOT WITH RESET';
115 116 117 118 119 120 121 122 123 124 125 126 127
        $this->conn->exec($sql);
        $this->currentDistributionValue = null;
    }

    /**
     * {@inheritDoc}
     */
    public function selectShard($distributionValue)
    {
        if ($this->conn->isTransactionActive()) {
            throw ShardingException::activeTransaction();
        }

128
        if ($distributionValue === null || is_bool($distributionValue) || ! is_scalar($distributionValue)) {
129 130 131 132
            throw ShardingException::noShardDistributionValue();
        }

        $platform = $this->conn->getDatabasePlatform();
133 134
        $sql      = sprintf(
            'USE FEDERATION %s (%s = %s) WITH RESET, FILTERING = %s;',
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
            $platform->quoteIdentifier($this->federationName),
            $platform->quoteIdentifier($this->distributionKey),
            $this->conn->quote($distributionValue),
            ($this->filteringEnabled ? 'ON' : 'OFF')
        );

        $this->conn->exec($sql);
        $this->currentDistributionValue = $distributionValue;
    }

    /**
     * {@inheritDoc}
     */
    public function getCurrentDistributionValue()
    {
        return $this->currentDistributionValue;
    }

    /**
     * {@inheritDoc}
     */
    public function getShards()
    {
158
        $sql = 'SELECT member_id as id,
159 160 161 162 163
                      distribution_name as distribution_key,
                      CAST(range_low AS CHAR) AS rangeLow,
                      CAST(range_high AS CHAR) AS rangeHigh
                      FROM sys.federation_member_distributions d
                      INNER JOIN sys.federations f ON f.federation_id = d.federation_id
164
                      WHERE f.name = ' . $this->conn->quote($this->federationName);
165

166 167 168 169 170 171
        return $this->conn->fetchAll($sql);
    }

     /**
      * {@inheritDoc}
      */
172
    public function queryAll($sql, array $params = [], array $types = [])
173 174
    {
        $shards = $this->getShards();
175 176
        if (! $shards) {
            throw new RuntimeException('No shards found for ' . $this->federationName);
177 178
        }

179
        $result          = [];
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
        $oldDistribution = $this->getCurrentDistributionValue();

        foreach ($shards as $shard) {
            $this->selectShard($shard['rangeLow']);
            foreach ($this->conn->fetchAll($sql, $params, $types) as $row) {
                $result[] = $row;
            }
        }

        if ($oldDistribution === null) {
            $this->selectGlobal();
        } else {
            $this->selectShard($oldDistribution);
        }

        return $result;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
199
     * Splits Federation at a given distribution value.
200 201
     *
     * @param mixed $splitDistributionValue
Benjamin Morel's avatar
Benjamin Morel committed
202 203
     *
     * @return void
204 205 206 207 208
     */
    public function splitFederation($splitDistributionValue)
    {
        $type = Type::getType($this->distributionType);

209 210 211
        $sql = 'ALTER FEDERATION ' . $this->getFederationName() . ' ' .
               'SPLIT AT (' . $this->getDistributionKey() . ' = ' .
               $this->conn->quote($splitDistributionValue, $type->getBindingType()) . ')';
212 213 214
        $this->conn->exec($sql);
    }
}