Completed
Push — 2.10.x ( 61a6b9...f20ba1 )
by Grégoire
13:37 queued 11s
created

SQLAzureShardManager   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 192
Duplicated Lines 0 %

Test Coverage

Coverage 38.24%

Importance

Changes 0
Metric Value
wmc 21
eloc 61
dl 0
loc 192
ccs 26
cts 68
cp 0.3824
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getDistributionType() 0 3 1
A getFederationName() 0 3 1
A getShards() 0 11 1
A getDistributionKey() 0 3 1
A selectShard() 0 17 3
A queryAll() 0 24 5
A setFilteringEnabled() 0 3 1
A getCurrentDistributionValue() 0 3 1
A selectGlobal() 0 9 2
A __construct() 0 21 4
A splitFederation() 0 8 1
1
<?php
2
3
namespace Doctrine\DBAL\Sharding\SQLAzure;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Sharding\ShardingException;
7
use Doctrine\DBAL\Sharding\ShardManager;
8
use Doctrine\DBAL\Types\Type;
9
use RuntimeException;
10
use function sprintf;
11
12
/**
13
 * Sharding using the SQL Azure Federations support.
14
 */
15
class SQLAzureShardManager implements ShardManager
16
{
17
    /** @var string */
18
    private $federationName;
19
20
    /** @var bool */
21
    private $filteringEnabled;
22
23
    /** @var string */
24
    private $distributionKey;
25
26
    /** @var string */
27
    private $distributionType;
28
29
    /** @var Connection */
30
    private $conn;
31
32
    /** @var string|null */
33
    private $currentDistributionValue;
34
35
    /**
36
     * @throws ShardingException
37
     */
38 145
    public function __construct(Connection $conn)
39
    {
40 145
        $this->conn = $conn;
41 145
        $params     = $conn->getParams();
42
43 145
        if (! isset($params['sharding']['federationName'])) {
44 139
            throw ShardingException::missingDefaultFederationName();
45
        }
46
47 121
        if (! isset($params['sharding']['distributionKey'])) {
48 1
            throw ShardingException::missingDefaultDistributionKey();
49
        }
50
51 120
        if (! isset($params['sharding']['distributionType'])) {
52 116
            throw ShardingException::missingDistributionType();
53
        }
54
55 96
        $this->federationName   = $params['sharding']['federationName'];
56 96
        $this->distributionKey  = $params['sharding']['distributionKey'];
57 96
        $this->distributionType = $params['sharding']['distributionType'];
58 96
        $this->filteringEnabled = (bool) ($params['sharding']['filteringEnabled'] ?? false);
59 96
    }
60
61
    /**
62
     * Gets the name of the federation.
63
     *
64
     * @return string
65
     */
66
    public function getFederationName()
67
    {
68
        return $this->federationName;
69
    }
70
71
    /**
72
     * Gets the distribution key.
73
     *
74
     * @return string
75
     */
76
    public function getDistributionKey()
77
    {
78
        return $this->distributionKey;
79
    }
80
81
    /**
82
     * Gets the Doctrine Type name used for the distribution.
83
     *
84
     * @return string
85
     */
86
    public function getDistributionType()
87
    {
88
        return $this->distributionType;
89
    }
90
91
    /**
92
     * Sets Enabled/Disable filtering on the fly.
93
     *
94
     * @param bool $flag
95
     *
96
     * @return void
97
     */
98
    public function setFilteringEnabled($flag)
99
    {
100
        $this->filteringEnabled = (bool) $flag;
101
    }
102
103
    /**
104
     * {@inheritDoc}
105
     */
106 71
    public function selectGlobal()
107
    {
108 71
        if ($this->conn->isTransactionActive()) {
109 70
            throw ShardingException::activeTransaction();
110
        }
111
112 47
        $sql = 'USE FEDERATION ROOT WITH RESET';
113 47
        $this->conn->exec($sql);
114 47
        $this->currentDistributionValue = null;
115 47
    }
116
117
    /**
118
     * {@inheritDoc}
119
     */
120 24
    public function selectShard($distributionValue)
121
    {
122 24
        if ($this->conn->isTransactionActive()) {
123 24
            throw ShardingException::activeTransaction();
124
        }
125
126
        $platform = $this->conn->getDatabasePlatform();
127
        $sql      = sprintf(
128
            'USE FEDERATION %s (%s = %s) WITH RESET, FILTERING = %s;',
129
            $platform->quoteIdentifier($this->federationName),
130
            $platform->quoteIdentifier($this->distributionKey),
131
            $this->conn->quote($distributionValue),
132
            ($this->filteringEnabled ? 'ON' : 'OFF')
133
        );
134
135
        $this->conn->exec($sql);
136
        $this->currentDistributionValue = $distributionValue;
137
    }
138
139
    /**
140
     * {@inheritDoc}
141
     */
142 93
    public function getCurrentDistributionValue()
143
    {
144 93
        return $this->currentDistributionValue;
145
    }
146
147
    /**
148
     * {@inheritDoc}
149
     */
150
    public function getShards()
151
    {
152
        $sql = 'SELECT member_id as id,
153
                      distribution_name as distribution_key,
154
                      CAST(range_low AS CHAR) AS rangeLow,
155
                      CAST(range_high AS CHAR) AS rangeHigh
156
                      FROM sys.federation_member_distributions d
157
                      INNER JOIN sys.federations f ON f.federation_id = d.federation_id
158
                      WHERE f.name = ' . $this->conn->quote($this->federationName);
159
160
        return $this->conn->fetchAll($sql);
161
    }
162
163
     /**
164
      * {@inheritDoc}
165
      */
166
    public function queryAll($sql, array $params = [], array $types = [])
167
    {
168
        $shards = $this->getShards();
169
        if (! $shards) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $shards of type array<mixed,mixed> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
170
            throw new RuntimeException('No shards found for ' . $this->federationName);
171
        }
172
173
        $result          = [];
174
        $oldDistribution = $this->getCurrentDistributionValue();
175
176
        foreach ($shards as $shard) {
177
            $this->selectShard($shard['rangeLow']);
178
            foreach ($this->conn->fetchAll($sql, $params, $types) as $row) {
179
                $result[] = $row;
180
            }
181
        }
182
183
        if ($oldDistribution === null) {
184
            $this->selectGlobal();
185
        } else {
186
            $this->selectShard($oldDistribution);
187
        }
188
189
        return $result;
190
    }
191
192
    /**
193
     * Splits Federation at a given distribution value.
194
     *
195
     * @param mixed $splitDistributionValue
196
     *
197
     * @return void
198
     */
199
    public function splitFederation($splitDistributionValue)
200
    {
201
        $type = Type::getType($this->distributionType);
202
203
        $sql = 'ALTER FEDERATION ' . $this->getFederationName() . ' ' .
204
               'SPLIT AT (' . $this->getDistributionKey() . ' = ' .
205
               $this->conn->quote($splitDistributionValue, $type->getBindingType()) . ')';
206
        $this->conn->exec($sql);
207
    }
208
}
209