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

/**
Benjamin Morel's avatar
Benjamin Morel committed
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 26
    public const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
    public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
27

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

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

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
    }

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

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

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

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

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

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

        return $sql;
    }

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

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

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

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

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

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

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

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

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

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

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

        return $sql;
    }

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

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

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

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

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

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

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

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

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

        return $sql;
    }

Benjamin Morel's avatar
Benjamin Morel committed
235 236 237
    /**
     * @return string
     */
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    private function getFederationTypeDefaultValue()
    {
        $federationType = Type::getType($this->shardManager->getDistributionType());

        switch ($federationType->getName()) {
            case Type::GUID:
                $defaultValue = '00000000-0000-0000-0000-000000000000';
                break;
            case Type::INTEGER:
            case Type::SMALLINT:
            case Type::BIGINT:
                $defaultValue = '0';
                break;
            default:
                $defaultValue = '';
                break;
        }
255

256 257 258
        return $defaultValue;
    }

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