SQLAzureFederationsSynchronizer.php 8.5 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
class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
25
{
26 27
    public const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
    public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
28

29
    /** @var SQLAzureShardManager */
30 31
    private $shardManager;

32
    /** @var SchemaSynchronizer */
33 34
    private $synchronizer;

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
    }

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

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

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

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

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

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

        return $sql;
    }

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

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

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

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

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

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

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

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

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

            $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);
140 141
        }

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

        return $sql;
    }

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

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

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

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

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

Pascal Borreli's avatar
Pascal Borreli committed
195
        return $partitionedSchema;
196 197 198 199
    }

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

        $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);
224 225
            if (! $federationSql) {
                continue;
226
            }
227 228 229 230

            $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);
231 232 233 234 235
        }

        return $sql;
    }

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

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

257 258 259
        return $defaultValue;
    }

Benjamin Morel's avatar
Benjamin Morel committed
260 261 262
    /**
     * @return string
     */
263 264
    private function getCreateFederationStatement()
    {
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
    }
}