Completed
Push — master ( 5dd66e...5b5c2c )
by Marco
114:19 queued 111:15
created

SQLAzure/SQLAzureFederationsSynchronizer.php (4 issues)

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 RuntimeException;
13
use function array_merge;
14
15
/**
16
 * SQL Azure Schema Synchronizer.
17
 *
18
 * Will iterate over all shards when performing schema operations. This is done
19
 * by partitioning the passed schema into subschemas for the federation and the
20
 * global database and then applying the operations step by step using the
21
 * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}.
22
 */
23
class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
24
{
25
    public const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
26
    public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
27
28
    /** @var SQLAzureShardManager */
29
    private $shardManager;
30
31
    /** @var SchemaSynchronizer */
32
    private $synchronizer;
33
34
    public function __construct(Connection $conn, SQLAzureShardManager $shardManager, ?SchemaSynchronizer $sync = null)
35
    {
36
        parent::__construct($conn);
37
        $this->shardManager = $shardManager;
38
        $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44
    public function getCreateSchema(Schema $createSchema)
45
    {
46
        $sql = [];
47
48
        [$global, $federation] = $this->partitionSchema($createSchema);
49
50
        $globalSql = $this->synchronizer->getCreateSchema($global);
51
        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...
52
            $sql[] = "-- Create Root Federation\n" .
53
                     'USE FEDERATION ROOT WITH RESET;';
54
            $sql   = array_merge($sql, $globalSql);
55
        }
56
57
        $federationSql = $this->synchronizer->getCreateSchema($federation);
58
59
        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...
60
            $defaultValue = $this->getFederationTypeDefaultValue();
61
62
            $sql[] = $this->getCreateFederationStatement();
63
            $sql[] = 'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $defaultValue . ') WITH RESET, FILTERING = OFF;';
64
            $sql   = array_merge($sql, $federationSql);
65
        }
66
67
        return $sql;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function getUpdateSchema(Schema $toSchema, $noDrops = false)
74
    {
75
        return $this->work($toSchema, static function ($synchronizer, $schema) use ($noDrops) {
76
            return $synchronizer->getUpdateSchema($schema, $noDrops);
77
        });
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function getDropSchema(Schema $dropSchema)
84
    {
85
        return $this->work($dropSchema, static function ($synchronizer, $schema) {
86
            return $synchronizer->getDropSchema($schema);
87
        });
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function createSchema(Schema $createSchema)
94
    {
95
        $this->processSql($this->getCreateSchema($createSchema));
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function updateSchema(Schema $toSchema, $noDrops = false)
102
    {
103
        $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function dropSchema(Schema $dropSchema)
110
    {
111
        $this->processSqlSafely($this->getDropSchema($dropSchema));
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function getDropAllSchema()
118
    {
119
        $this->shardManager->selectGlobal();
120
        $globalSql = $this->synchronizer->getDropAllSchema();
121
122
        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...
123
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.
Loading history...
124
            $sql   = array_merge($sql, $globalSql);
125
        }
126
127
        $shards = $this->shardManager->getShards();
128
        foreach ($shards as $shard) {
129
            $this->shardManager->selectShard($shard['rangeLow']);
130
131
            $federationSql = $this->synchronizer->getDropAllSchema();
132
            if (! $federationSql) {
133
                continue;
134
            }
135
136
            $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
137
                     'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ') WITH RESET, FILTERING = OFF;';
138
            $sql   = array_merge($sql, $federationSql);
139
        }
140
141
        $sql[] = 'USE FEDERATION ROOT WITH RESET;';
142
        $sql[] = 'DROP FEDERATION ' . $this->shardManager->getFederationName();
143
144
        return $sql;
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function dropAllSchema()
151
    {
152
        $this->processSqlSafely($this->getDropAllSchema());
153
    }
154
155
    /**
156
     * @return Schema[]
157
     */
158
    private function partitionSchema(Schema $schema)
159
    {
160
        return [
161
            $this->extractSchemaFederation($schema, false),
162
            $this->extractSchemaFederation($schema, true),
163
        ];
164
    }
165
166
    /**
167
     * @param bool $isFederation
168
     *
169
     * @return Schema
170
     *
171
     * @throws RuntimeException
172
     */
173
    private function extractSchemaFederation(Schema $schema, $isFederation)
174
    {
175
        $partitionedSchema = clone $schema;
176
177
        foreach ($partitionedSchema->getTables() as $table) {
178
            if ($isFederation) {
179
                $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
180
            }
181
182
            if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
183
                $partitionedSchema->dropTable($table->getName());
184
            } else {
185
                foreach ($table->getForeignKeys() as $fk) {
186
                    $foreignTable = $schema->getTable($fk->getForeignTableName());
187
                    if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
188
                        throw new RuntimeException('Cannot have foreign key between global/federation.');
189
                    }
190
                }
191
            }
192
        }
193
194
        return $partitionedSchema;
195
    }
196
197
    /**
198
     * Work on the Global/Federation based on currently existing shards and
199
     * perform the given operation on the underlying schema synchronizer given
200
     * the different partitioned schema instances.
201
     *
202
     * @return string[]
203
     */
204
    private function work(Schema $schema, Closure $operation)
205
    {
206
        [$global, $federation] = $this->partitionSchema($schema);
207
        $sql                   = [];
208
209
        $this->shardManager->selectGlobal();
210
        $globalSql = $operation($this->synchronizer, $global);
211
212
        if ($globalSql) {
213
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
214
            $sql   = array_merge($sql, $globalSql);
215
        }
216
217
        $shards = $this->shardManager->getShards();
218
219
        foreach ($shards as $shard) {
220
            $this->shardManager->selectShard($shard['rangeLow']);
221
222
            $federationSql = $operation($this->synchronizer, $federation);
223
            if (! $federationSql) {
224
                continue;
225
            }
226
227
            $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
228
                     'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ') WITH RESET, FILTERING = OFF;';
229
            $sql   = array_merge($sql, $federationSql);
230
        }
231
232
        return $sql;
233
    }
234
235
    /**
236
     * @return string
237
     */
238
    private function getFederationTypeDefaultValue()
239
    {
240
        $federationType = Type::getType($this->shardManager->getDistributionType());
241
242
        switch ($federationType->getName()) {
243
            case Type::GUID:
244
                $defaultValue = '00000000-0000-0000-0000-000000000000';
245
                break;
246
            case Type::INTEGER:
247
            case Type::SMALLINT:
248
            case Type::BIGINT:
249
                $defaultValue = '0';
250
                break;
251
            default:
252
                $defaultValue = '';
253
                break;
254
        }
255
256
        return $defaultValue;
257
    }
258
259
    /**
260
     * @return string
261
     */
262
    private function getCreateFederationStatement()
263
    {
264
        $federationType    = Type::getType($this->shardManager->getDistributionType());
265
        $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform());
266
267
        return "--Create Federation\n" .
268
               'CREATE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' ' . $federationTypeSql . '  RANGE)';
269
    }
270
}
271