Completed
Push — master ( 7a6bdd...324ce1 )
by Sébastien
09:34
created

ShardingConnection::executeUpdate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 9
c 1
b 0
f 0
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 3
crap 2
1
<?php
2
3
namespace Bdf\Prime\Sharding;
4
5
use Bdf\Prime\Connection\SimpleConnection;
6
use Bdf\Prime\Connection\SubConnectionManagerInterface;
7
use Bdf\Prime\ConnectionManager;
0 ignored issues
show
introduced by
Unused use statement
Loading history...
8
use Bdf\Prime\Query\Compiler\Preprocessor\PreprocessorInterface;
9
use Bdf\Prime\Query\Contract\Query\InsertQueryInterface;
10
use Bdf\Prime\Query\Contract\Query\KeyValueQueryInterface;
11
use Bdf\Prime\Sharding\Query\ShardingInsertQuery;
12
use Bdf\Prime\Sharding\Query\ShardingKeyValueQuery;
13
use Doctrine\Common\EventManager;
14
use Doctrine\DBAL\Cache\QueryCacheProfile;
15
use Doctrine\DBAL\Configuration;
16
use Doctrine\DBAL\Driver;
17
use Doctrine\DBAL\Sharding\ShardingException;
18
use LogicException;
19
20
/**
21
 * ShardingConnection
22
 *
23
 * The sharding connection is a global connection (that can be a shard server) and a collection of connection wrappers for shards.
24
 *
25
 * Those methods will be used by the global connection:
26
 *    ShardingConnection#prepare    Will connect on global but should execute on shard if one is selected
27
 *    ShardingConnection#quote
28
 *    ShardingConnection#errorCode
29
 *    ShardingConnection#errorInfo
30
 *    ShardingConnection#ping
31
 *
32
 * Can connect the global connection if no shard has been selected:
33
 *    ShardingConnection#lastInsertId
34
 *    ShardingConnection#lastInsertId
35
 *
36
 * Be aware!!
37
 * This connection does not managed the distribution key. If a SQL INSERT is executed without shard selection
38
 * the SQL will be executed on each shards.
39
 *
40
 * The shard to use can be auto guess if query builder is used.
41
 *
42
 * The method MultiStatement#fetchColumn change the interface (it will return a array of result)
43
 * All aggregate function executed on each shard will return a collection of result.
44
 * The merge should be done outside this class
45
 *
46
 * @example:
0 ignored issues
show
Coding Style Documentation introduced by
@example: tag is not allowed in class comment
Loading history...
47
 * // returns the count result of the first shard
48
 * $connection->from('test')->count());
49
 *
50
 * // returns an array containing all count result of each shards
51
 * $connection->query('select count(*) from test')->fetchColumn();
52
 *
53
 * @package Bdf\Prime\Sharding
0 ignored issues
show
Coding Style Documentation introduced by
@package tag is not allowed in class comment
Loading history...
54
 */
55
class ShardingConnection extends SimpleConnection implements SubConnectionManagerInterface
56
{
57
    /**
58
     * All shard connections
59
     *
60
     * @var SimpleConnection[]
61
     */
62
    private $connections = [];
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before first member var; 0 found
Loading history...
63
    
64
    /**
65
     * The shard choser
66
     * 
67
     * @var ShardChoserInterface
68
     */
69
    private $shardChoser;
70
71
    /**
72
     * The id of current shard. Null means all shards
73
     *
74
     * @var string
75
     */
76
    private $currentShardId;
77
78
    /**
79
     * The distribution key
80
     *
81
     * @var string
82
     */
83
    private $distributionKey;
84
85
    /**
86
     * Initializes a new instance of the Connection class.
87
     * 
88
     * Here's a shard connections configuration
0 ignored issues
show
introduced by
Doc comment long description must end with a full stop
Loading history...
89
     * 
90
     * @example
91
     *
92
     * $conn = DriverManager::getConnection([
93
     *    'driver' => 'pdo_mysql',
94
     *    'user'     => 'user',
95
     *    'password' => 'password',
96
     *    'host'     => '127.0.0.1',
97
     *    'dbname'   => 'basename',
98
     *    'distributionKey' => 'id',
99
     *    'shards' => [
100
     *      '{shardId}' => [
101
     *        'user'     => 'shard1',
102
     *        'host'     => '...',
103
     *      ]
104
     *    ]
105
     * ]);
106
     *
107
     * @param array                              $params       The connection parameters.
0 ignored issues
show
Coding Style introduced by
Parameter tags must be defined first in a doc comment
Loading history...
108
     * @param \Doctrine\DBAL\Driver              $driver       The driver to use.
109
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration, optional.
110
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional.
111
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
112 72
    public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null)
113
    {
114 72
        if (!isset($params['shard_connections'])) {
115
            throw new LogicException('Sharding connection needs "shard_connections" configuration in parameters');
116
        }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
117 72
        if (!isset($params['distributionKey'])) {
118
            throw new LogicException('Sharding connection needs distribution key in parameters');
119
        }
120
121 72
        $this->distributionKey = $params['distributionKey'];
122 72
        $this->shardChoser = $params['shardChoser'] ?? new ModuloChoser();
0 ignored issues
show
Coding Style introduced by
Operation must be bracketed
Loading history...
123 72
        $this->connections = $params['shard_connections'];
124
125 72
        parent::__construct($params, $driver, $config, $eventManager);
126
127 72
        $this->factory()->alias(InsertQueryInterface::class, ShardingInsertQuery::class);
0 ignored issues
show
Bug introduced by
The method alias() does not exist on Bdf\Prime\Query\Factory\QueryFactoryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\Factory\QueryFactoryInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

127
        $this->factory()->/** @scrutinizer ignore-call */ alias(InsertQueryInterface::class, ShardingInsertQuery::class);
Loading history...
128 72
        $this->factory()->alias(KeyValueQueryInterface::class, ShardingKeyValueQuery::class);
129 72
    }
130
131
    /**
132
     * Get the shard ids
133
     *
134
     * @return array
135
     */
136 58
    public function getShardIds()
137
    {
138 58
        return array_keys($this->connections);
139
    }
140
141
    /**
142
     * Get the shard choser
143
     *
144
     * @return ShardChoserInterface
145
     */
146 39
    public function getShardChoser()
147
    {
148 39
        return $this->shardChoser;
149
    }
150
151
    /**
152
     * Get the distribution key
153
     *
154
     * @return string
155
     */
156 59
    public function getDistributionKey()
157
    {
158 59
        return $this->distributionKey;
159
    }
160
161
    /**
162
     * Get the current shard
163
     *
164
     * @return string
165
     */
166 42
    public function getCurrentShardId()
167
    {
168 42
        return $this->currentShardId;
169
    }
170
171
    /**
172
     * Select a shard to use.
173
     *
174
     * @param mixed $distributionValue
175
     *
176
     * @return $this
177
     */
178 37
    public function pickShard($distributionValue = null)
179
    {
180 37
        $this->useShard(
181 37
            $distributionValue !== null
182 30
                ? $this->shardChoser->pick($distributionValue, $this)
0 ignored issues
show
Coding Style introduced by
Inline IF statements are not allowed
Loading history...
Coding Style introduced by
Inline shorthand IF statement must be declared on a single line
Loading history...
183 37
                : null
184
        );
185
186 37
        return $this;
187
    }
188
189
    /**
190
     * Use a shard
191
     *
192
     * @param string $shardId
193
     *
194
     * @return $this
195
     *
196
     * @throws ShardingException   If the shard id is not known
0 ignored issues
show
introduced by
@throws comment must be on the next line
Loading history...
introduced by
@throws tag comment must end with a full stop
Loading history...
197
     */
198 43
    public function useShard($shardId = null)
199
    {
200 43
        if ($shardId !== null && !isset($this->connections[$shardId])) {
201
            throw new ShardingException('Trying to use an unknown shard id "'.$shardId.'"');
202
        }
203
204 43
        $this->currentShardId = $shardId;
205
206 43
        return $this;
207
    }
208
209
    /**
210
     * Check whether the connection is using a shard
211
     *
212
     * @return boolean
0 ignored issues
show
introduced by
Expected "bool" but found "boolean" for function return type
Loading history...
213
     */
214 72
    public function isUsingShard()
215
    {
216 72
        return $this->currentShardId !== null;
217
    }
218
219
    /**
220
     * Get a shard connection by its id
221
     * Returns all connection if id is null
0 ignored issues
show
introduced by
Doc comment short description must be on a single line, further text should be a separate paragraph
Loading history...
222
     *
223
     * @param mixed $shardId
224
     *
225
     * @return SimpleConnection|SimpleConnection[]
226
     *
227
     * @throws ShardingException   If the shard id is not known
0 ignored issues
show
introduced by
@throws comment must be on the next line
Loading history...
introduced by
@throws tag comment must end with a full stop
Loading history...
228
     */
229 56
    public function getShardConnection($shardId = null)
230
    {
231 56
        if ($shardId === null) {
232 2
            return $this->connections;
233
        }
234
235 54
        if (!isset($this->connections[$shardId])) {
236 1
            throw new ShardingException('Trying to get an unknown shard id "'.$shardId.'"');
237
        }
238
239 53
        return $this->connections[$shardId];
240
    }
241
242
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $name should have a doc-comment as per coding-style.
Loading history...
243
     * {@inheritdoc}
244
     */
245 31
    public function getConnection($name)
246
    {
247 31
        return $this->getShardConnection($name);
248
    }
249
250
    /**
251
     * Get the selected shards
252
     *
253
     * @return SimpleConnection[]
254
     */
255 72
    protected function getSelectedShards()
256
    {
257 72
        if ($this->isUsingShard()) {
258 26
            return [$this->connections[$this->currentShardId]];
259
        }
260
261 72
        return $this->connections;
262
    }
263
264
    /**
265
     * Get the selected shard
266
     *
267
     * @return SimpleConnection
268
     */
269 7
    protected function getSelectedShard()
270
    {
271 7
        return $this->connections[$this->currentShardId];
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277 1
    public function close()
278
    {
279 1
        parent::close();
280
281 1
        $this->currentShardId = null;
282
283 1
        foreach ($this->connections as $shard) {
284 1
            $shard->close();
285
        }
286 1
    }
287
288
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $preprocessor should have a doc-comment as per coding-style.
Loading history...
289
     * {@inheritdoc}
290
     */
291 34
    public function builder(PreprocessorInterface $preprocessor = null)
292
    {
293 34
        return $this->factory()->make(ShardingQuery::class, $preprocessor);
294
    }
295
296
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $sql should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $params should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $types should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $qcp should have a doc-comment as per coding-style.
Loading history...
297
     * {@inheritdoc}
298
     */
299 72
    public function executeQuery($sql, array $params = [], $types = [], QueryCacheProfile $qcp = null)
300
    {
301 72
        if ($this->isUsingShard()) {
302 5
            return $this->getSelectedShard()->executeQuery($sql, $params, $types, $qcp);
303
        }
304
305 72
        $stmt = new MultiStatement();
306
307 72
        foreach ($this->getSelectedShards() as $shard) {
308 72
            $stmt->add($shard->executeQuery($sql, $params, $types, $qcp));
309
        }
310
311 72
        return $stmt;
312
    }
313
314
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $sql should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $params should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $types should have a doc-comment as per coding-style.
Loading history...
315
     * {@inheritdoc}
316
     */
317 27
    public function executeUpdate($sql, array $params = [], array $types = [])
318
    {
319 27
        $result = 0;
320
321 27
        foreach ($this->getSelectedShards() as $shard) {
322 27
            $result += $shard->executeUpdate($sql, $params, $types);
323
        }
324
325 27
        return $result;
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331 4
    public function query()
332
    {
333 4
        $args = func_get_args();
334
335 4
        if ($this->isUsingShard()) {
336
            return $this->getSelectedShard()->query(...$args);
337
        }
338
339 4
        $stmt = new MultiStatement();
340
341 4
        foreach ($this->getSelectedShards() as $shard) {
342 4
            $stmt->add($shard->query(...$args));
343
        }
344
345 4
        return $stmt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stmt returns the type Bdf\Prime\Sharding\MultiStatement which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Connection::query() of Doctrine\DBAL\Driver\Statement.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
346
    }
347
348
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $statement should have a doc-comment as per coding-style.
Loading history...
349
     * {@inheritdoc}
350
     */
351 72
    public function exec($statement)
352
    {
353 72
        $result = 0;
354
355 72
        foreach ($this->getSelectedShards() as $shard) {
356 72
            $result += $shard->exec($statement);
357
        }
358
359 72
        return $result;
360
    }
361
362
    /**
363
     * {@inheritdoc}
364
     */
365 22
    public function beginTransaction()
366
    {
367 22
        foreach ($this->getSelectedShards() as $shard) {
368 22
            $shard->beginTransaction();
369
        }
370 22
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375 1
    public function commit()
376
    {
377 1
        foreach ($this->getSelectedShards() as $shard) {
378 1
            $shard->commit();
379
        }
380 1
    }
381
382
    /**
383
     * {@inheritdoc}
384
     */
385 1
    public function rollBack()
386
    {
387 1
        foreach ($this->getSelectedShards() as $shard) {
388 1
            $shard->rollBack();
389
        }
390 1
    }
391
392
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $savepoint should have a doc-comment as per coding-style.
Loading history...
393
     * {@inheritdoc}
394
     */
395
    public function createSavepoint($savepoint)
396
    {
397
        foreach ($this->getSelectedShards() as $shard) {
398
            $shard->createSavepoint($savepoint);
399
        }
400
    }
401
402
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $savepoint should have a doc-comment as per coding-style.
Loading history...
403
     * {@inheritdoc}
404
     */
405
    public function releaseSavepoint($savepoint)
406
    {
407
        foreach ($this->getSelectedShards() as $shard) {
408
            $shard->releaseSavepoint($savepoint);
409
        }
410
    }
411
412
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $savepoint should have a doc-comment as per coding-style.
Loading history...
413
     * {@inheritdoc}
414
     */
415
    public function rollbackSavepoint($savepoint)
416
    {
417
        foreach ($this->getSelectedShards() as $shard) {
418
            $shard->rollbackSavepoint($savepoint);
419
        }
420
    }
421
422
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $seqName should have a doc-comment as per coding-style.
Loading history...
423
     * {@inheritdoc}
424
     */
425 1
    public function lastInsertId($seqName = null)
426
    {
427 1
        if ($this->isUsingShard()) {
428 1
            return $this->getSelectedShard()->lastInsertId($seqName);
429
        }
430
431
        // TODO doit on lever une exception ?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
432
        return parent::lastInsertId($seqName);
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     */
438 1
    public function getWrappedConnection()
439
    {
440 1
        if ($this->isUsingShard()) {
441 1
            return $this->getSelectedShard()->getWrappedConnection();
442
        }
443
444
        // TODO doit on lever une exception ?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
445
        return parent::getWrappedConnection();
446
    }
447
}
448