SQLAzureFederationsSynchronizer.php 8.52 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Sharding\SQLAzure;

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 11
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
use Doctrine\DBAL\Types\Type;
12
use Doctrine\DBAL\Types\Types;
13
use RuntimeException;
14
use function array_merge;
15 16

/**
Benjamin Morel's avatar
Benjamin Morel committed
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
 *
 * @deprecated
25
 */
26
class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
27
{
28 29
    public const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
    public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
30

31
    /** @var SQLAzureShardManager */
32 33
    private $shardManager;

34
    /** @var SchemaSynchronizer */
35 36
    private $synchronizer;

37
    public function __construct(Connection $conn, SQLAzureShardManager $shardManager, ?SchemaSynchronizer $sync = null)
38
    {
39
        parent::__construct($conn);
40
        $this->shardManager = $shardManager;
41
        $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
42 43 44
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
45
     * {@inheritdoc}
46 47 48
     */
    public function getCreateSchema(Schema $createSchema)
    {
49
        $sql = [];
50

51
        [$global, $federation] = $this->partitionSchema($createSchema);
52 53 54 55

        $globalSql = $this->synchronizer->getCreateSchema($global);
        if ($globalSql) {
            $sql[] = "-- Create Root Federation\n" .
56 57
                     'USE FEDERATION ROOT WITH RESET;';
            $sql   = array_merge($sql, $globalSql);
58 59 60 61 62 63 64 65
        }

        $federationSql = $this->synchronizer->getCreateSchema($federation);

        if ($federationSql) {
            $defaultValue = $this->getFederationTypeDefaultValue();

            $sql[] = $this->getCreateFederationStatement();
66 67
            $sql[] = 'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $defaultValue . ') WITH RESET, FILTERING = OFF;';
            $sql   = array_merge($sql, $federationSql);
68 69 70 71 72 73
        }

        return $sql;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
74
     * {@inheritdoc}
75 76 77
     */
    public function getUpdateSchema(Schema $toSchema, $noDrops = false)
    {
78
        return $this->work($toSchema, static function ($synchronizer, $schema) use ($noDrops) {
79 80 81 82 83
            return $synchronizer->getUpdateSchema($schema, $noDrops);
        });
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
84
     * {@inheritdoc}
85 86 87
     */
    public function getDropSchema(Schema $dropSchema)
    {
88
        return $this->work($dropSchema, static function ($synchronizer, $schema) {
89 90 91 92 93
            return $synchronizer->getDropSchema($schema);
        });
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
94
     * {@inheritdoc}
95 96 97 98 99 100 101
     */
    public function createSchema(Schema $createSchema)
    {
        $this->processSql($this->getCreateSchema($createSchema));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
102
     * {@inheritdoc}
103 104 105 106 107 108 109
     */
    public function updateSchema(Schema $toSchema, $noDrops = false)
    {
        $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
110
     * {@inheritdoc}
111 112 113 114 115 116 117
     */
    public function dropSchema(Schema $dropSchema)
    {
        $this->processSqlSafely($this->getDropSchema($dropSchema));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
118
     * {@inheritdoc}
119 120 121 122 123 124 125 126
     */
    public function getDropAllSchema()
    {
        $this->shardManager->selectGlobal();
        $globalSql = $this->synchronizer->getDropAllSchema();

        if ($globalSql) {
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
127
            $sql   = array_merge($sql, $globalSql);
128 129 130 131 132 133 134
        }

        $shards = $this->shardManager->getShards();
        foreach ($shards as $shard) {
            $this->shardManager->selectShard($shard['rangeLow']);

            $federationSql = $this->synchronizer->getDropAllSchema();
135 136
            if (! $federationSql) {
                continue;
137
            }
138 139 140 141

            $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
                     'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ') WITH RESET, FILTERING = OFF;';
            $sql   = array_merge($sql, $federationSql);
142 143
        }

144 145
        $sql[] = 'USE FEDERATION ROOT WITH RESET;';
        $sql[] = 'DROP FEDERATION ' . $this->shardManager->getFederationName();
146 147 148 149 150

        return $sql;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
151
     * {@inheritdoc}
152 153 154 155 156 157
     */
    public function dropAllSchema()
    {
        $this->processSqlSafely($this->getDropAllSchema());
    }

Benjamin Morel's avatar
Benjamin Morel committed
158
    /**
159
     * @return Schema[]
Benjamin Morel's avatar
Benjamin Morel committed
160
     */
161 162
    private function partitionSchema(Schema $schema)
    {
163
        return [
164 165
            $this->extractSchemaFederation($schema, false),
            $this->extractSchemaFederation($schema, true),
166
        ];
167 168
    }

Benjamin Morel's avatar
Benjamin Morel committed
169
    /**
170
     * @param bool $isFederation
Benjamin Morel's avatar
Benjamin Morel committed
171
     *
172
     * @return Schema
Benjamin Morel's avatar
Benjamin Morel committed
173
     *
174
     * @throws RuntimeException
Benjamin Morel's avatar
Benjamin Morel committed
175
     */
176 177
    private function extractSchemaFederation(Schema $schema, $isFederation)
    {
Pascal Borreli's avatar
Pascal Borreli committed
178
        $partitionedSchema = clone $schema;
179

Pascal Borreli's avatar
Pascal Borreli committed
180
        foreach ($partitionedSchema->getTables() as $table) {
181 182 183 184
            if ($isFederation) {
                $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
            }

Steve Müller's avatar
Steve Müller committed
185
            if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
Pascal Borreli's avatar
Pascal Borreli committed
186
                $partitionedSchema->dropTable($table->getName());
187 188 189 190
            } else {
                foreach ($table->getForeignKeys() as $fk) {
                    $foreignTable = $schema->getTable($fk->getForeignTableName());
                    if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
191
                        throw new RuntimeException('Cannot have foreign key between global/federation.');
192 193 194 195 196
                    }
                }
            }
        }

Pascal Borreli's avatar
Pascal Borreli committed
197
        return $partitionedSchema;
198 199 200 201
    }

    /**
     * Work on the Global/Federation based on currently existing shards and
Pascal Borreli's avatar
Pascal Borreli committed
202 203
     * perform the given operation on the underlying schema synchronizer given
     * the different partitioned schema instances.
204
     *
205
     * @return string[]
206
     */
207
    private function work(Schema $schema, Closure $operation)
208
    {
209 210
        [$global, $federation] = $this->partitionSchema($schema);
        $sql                   = [];
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

        $this->shardManager->selectGlobal();
        $globalSql = $operation($this->synchronizer, $global);

        if ($globalSql) {
            $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
            $sql   = array_merge($sql, $globalSql);
        }

        $shards = $this->shardManager->getShards();

        foreach ($shards as $shard) {
            $this->shardManager->selectShard($shard['rangeLow']);

            $federationSql = $operation($this->synchronizer, $federation);
226 227
            if (! $federationSql) {
                continue;
228
            }
229 230 231 232

            $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
                     'USE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ') WITH RESET, FILTERING = OFF;';
            $sql   = array_merge($sql, $federationSql);
233 234 235 236 237
        }

        return $sql;
    }

Benjamin Morel's avatar
Benjamin Morel committed
238 239 240
    /**
     * @return string
     */
241 242 243 244 245
    private function getFederationTypeDefaultValue()
    {
        $federationType = Type::getType($this->shardManager->getDistributionType());

        switch ($federationType->getName()) {
246
            case Types::GUID:
247 248
                $defaultValue = '00000000-0000-0000-0000-000000000000';
                break;
249 250 251
            case Types::INTEGER:
            case Types::SMALLINT:
            case Types::BIGINT:
252 253 254 255 256 257
                $defaultValue = '0';
                break;
            default:
                $defaultValue = '';
                break;
        }
258

259 260 261
        return $defaultValue;
    }

Benjamin Morel's avatar
Benjamin Morel committed
262 263 264
    /**
     * @return string
     */
265 266
    private function getCreateFederationStatement()
    {
267
        $federationType    = Type::getType($this->shardManager->getDistributionType());
268
        $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform());
269 270

        return "--Create Federation\n" .
271
               'CREATE FEDERATION ' . $this->shardManager->getFederationName() . ' (' . $this->shardManager->getDistributionKey() . ' ' . $federationTypeSql . '  RANGE)';
272 273
    }
}