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
|
|||
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
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
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
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
Loading history...
|
|||
123 | $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
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 |
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.