Completed
Pull Request — 2.10.x (#4009)
by Grégoire
08:50
created

extractSchemaFederation()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 20
cp 0
rs 8.9297
c 0
b 0
f 0
cc 6
nc 7
nop 2
crap 42
1
<?php
2
3
namespace Doctrine\DBAL\Sharding\SQLAzure;
4
5
use Closure;
6
use Doctrine\DBAL\Connection;
7
use Doctrine\DBAL\Schema\Schema;
8
use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer;
9
use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
10
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
11
use Doctrine\DBAL\Types\Type;
12
use Doctrine\DBAL\Types\Types;
13
use RuntimeException;
14
use function array_merge;
15
16
/**
17
 * SQL Azure Schema Synchronizer.
18
 *
19
 * Will iterate over all shards when performing schema operations. This is done
20
 * by partitioning the passed schema into subschemas for the federation and the
21
 * global database and then applying the operations step by step using the
22
 * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}.
23
 */
24
class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
25
{
26
    public const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
27
    public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
28
29
    /** @var SQLAzureShardManager */
30
    private $shardManager;
31
32
    /** @var SchemaSynchronizer */
33
    private $synchronizer;
34
35
    public function __construct(Connection $conn, SQLAzureShardManager $shardManager, ?SchemaSynchronizer $sync = null)
36
    {
37
        parent::__construct($conn);
38
        $this->shardManager = $shardManager;
39
        $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
40
    }
41
42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function getCreateSchema(Schema $createSchema)
46
    {
47
        $sql = [];
48
49
        [$global, $federation] = $this->partitionSchema($createSchema);
0 ignored issues
show
Bug introduced by
The variable $global does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $federation does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
50
51
        $globalSql = $this->synchronizer->getCreateSchema($global);
52
        if ($globalSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $globalSql of type string[] 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...
53
            $sql[] = "-- Create Root Federation\n" .
54
                     'USE FEDERATION ROOT WITH RESET;';
55
            $sql   = array_merge($sql, $globalSql);
56
        }
57
58
        $federationSql = $this->synchronizer->getCreateSchema($federation);
59
60
        if ($federationSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $federationSql of type string[] 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...
61
            $defaultValue = $this->getFederationTypeDefaultValue();
62
63
            $sql[] = $this->getCreateFederationStatement();
64
            $sql[] = 'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $defaultValue . ') WITH RESET, FILTERING = OFF;';
65
            $sql   = array_merge($sql, $federationSql);
66
        }
67
68
        return $sql;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function getUpdateSchema(Schema $toSchema, $noDrops = false)
75
    {
76
        return $this->work($toSchema, static function ($synchronizer, $schema) use ($noDrops) {
77
            return $synchronizer->getUpdateSchema($schema, $noDrops);
78
        });
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function getDropSchema(Schema $dropSchema)
85
    {
86
        return $this->work($dropSchema, static function ($synchronizer, $schema) {
87
            return $synchronizer->getDropSchema($schema);
88
        });
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function createSchema(Schema $createSchema)
95
    {
96
        $this->processSql($this->getCreateSchema($createSchema));
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function updateSchema(Schema $toSchema, $noDrops = false)
103
    {
104
        $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function dropSchema(Schema $dropSchema)
111
    {
112
        $this->processSqlSafely($this->getDropSchema($dropSchema));
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function getDropAllSchema()
119
    {
120
        $this->shardManager->selectGlobal();
121
        $globalSql = $this->synchronizer->getDropAllSchema();
122
123
        if ($globalSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $globalSql of type string[] 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...
124
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
125
            $sql   = array_merge($sql, $globalSql);
126
        }
127
128
        $shards = $this->shardManager->getShards();
129
        foreach ($shards as $shard) {
130
            $this->shardManager->selectShard($shard['rangeLow']);
131
132
            $federationSql = $this->synchronizer->getDropAllSchema();
133
            if (! $federationSql) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $federationSql of type string[] 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...
134
                continue;
135
            }
136
137
            $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
0 ignored issues
show
Bug introduced by
The variable $sql does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
138
                     'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ') WITH RESET, FILTERING = OFF;';
139
            $sql   = array_merge($sql, $federationSql);
140
        }
141
142
        $sql[] = 'USE FEDERATION ROOT WITH RESET;';
143
        $sql[] = 'DROP FEDERATION ' . $this->shardManager->getFederationName();
144
145
        return $sql;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function dropAllSchema()
152
    {
153
        $this->processSqlSafely($this->getDropAllSchema());
154
    }
155
156
    /**
157
     * @return Schema[]
158
     */
159
    private function partitionSchema(Schema $schema)
160
    {
161
        return [
162
            $this->extractSchemaFederation($schema, false),
163
            $this->extractSchemaFederation($schema, true),
164
        ];
165
    }
166
167
    /**
168
     * @param bool $isFederation
169
     *
170
     * @return Schema
171
     *
172
     * @throws RuntimeException
173
     */
174
    private function extractSchemaFederation(Schema $schema, $isFederation)
175
    {
176
        $partitionedSchema = clone $schema;
177
178
        foreach ($partitionedSchema->getTables() as $table) {
179
            if ($isFederation) {
180
                $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
181
            }
182
183
            if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
184
                $partitionedSchema->dropTable($table->getName());
185
            } else {
186
                foreach ($table->getForeignKeys() as $fk) {
187
                    $foreignTable = $schema->getTable($fk->getForeignTableName());
188
                    if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
189
                        throw new RuntimeException('Cannot have foreign key between global/federation.');
190
                    }
191
                }
192
            }
193
        }
194
195
        return $partitionedSchema;
196
    }
197
198
    /**
199
     * Work on the Global/Federation based on currently existing shards and
200
     * perform the given operation on the underlying schema synchronizer given
201
     * the different partitioned schema instances.
202
     *
203
     * @return string[]
204
     */
205
    private function work(Schema $schema, Closure $operation)
206
    {
207
        [$global, $federation] = $this->partitionSchema($schema);
0 ignored issues
show
Bug introduced by
The variable $global does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $federation does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
208
        $sql                   = [];
209
210
        $this->shardManager->selectGlobal();
211
        $globalSql = $operation($this->synchronizer, $global);
212
213
        if ($globalSql) {
214
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
215
            $sql   = array_merge($sql, $globalSql);
216
        }
217
218
        $shards = $this->shardManager->getShards();
219
220
        foreach ($shards as $shard) {
221
            $this->shardManager->selectShard($shard['rangeLow']);
222
223
            $federationSql = $operation($this->synchronizer, $federation);
224
            if (! $federationSql) {
225
                continue;
226
            }
227
228
            $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
229
                     'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ') WITH RESET, FILTERING = OFF;';
230
            $sql   = array_merge($sql, $federationSql);
231
        }
232
233
        return $sql;
234
    }
235
236
    /**
237
     * @return string
238
     */
239
    private function getFederationTypeDefaultValue()
240
    {
241
        $federationType = Type::getType($this->shardManager->getDistributionType());
242
243
        switch ($federationType->getName()) {
244
            case Types::GUID:
245
                $defaultValue = '00000000-0000-0000-0000-000000000000';
246
                break;
247
            case Types::INTEGER:
248
            case Types::SMALLINT:
249
            case Types::BIGINT:
250
                $defaultValue = '0';
251
                break;
252
            default:
253
                $defaultValue = '';
254
                break;
255
        }
256
257
        return $defaultValue;
258
    }
259
260
    /**
261
     * @return string
262
     */
263
    private function getCreateFederationStatement()
264
    {
265
        $federationType    = Type::getType($this->shardManager->getDistributionType());
266
        $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform());
267
268
        return "--Create Federation\n" .
269
               'CREATE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' ' . $federationTypeSql . '  RANGE)';
270
    }
271
}
272