SQLAzureFederationsSynchronizer.php 9.94 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17 18 19 20 21 22 23 24 25
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\DBAL\Sharding\SQLAzure;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Types\Type;

26
use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer;
27 28
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
29 30

/**
Benjamin Morel's avatar
Benjamin Morel committed
31
 * SQL Azure Schema Synchronizer.
32 33
 *
 * Will iterate over all shards when performing schema operations. This is done
34
 * by partitioning the passed schema into subschemas for the federation and the
35
 * global database and then applying the operations step by step using the
36
 * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}.
37 38 39
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 */
40
class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
41
{
42 43 44
    const FEDERATION_TABLE_FEDERATED   = 'azure.federated';
    const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';

45
    /**
Benjamin Morel's avatar
Benjamin Morel committed
46
     * @var \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager
47 48 49 50
     */
    private $shardManager;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
51
     * @var \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer
52 53 54
     */
    private $synchronizer;

Benjamin Morel's avatar
Benjamin Morel committed
55 56 57 58 59
    /**
     * @param \Doctrine\DBAL\Connection                                  $conn
     * @param \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager      $shardManager
     * @param \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer|null $sync
     */
60 61
    public function __construct(Connection $conn, SQLAzureShardManager $shardManager, SchemaSynchronizer $sync = null)
    {
62
        parent::__construct($conn);
63
        $this->shardManager = $shardManager;
64
        $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
65 66 67
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
68
     * {@inheritdoc}
69 70 71
     */
    public function getCreateSchema(Schema $createSchema)
    {
72
        $sql = [];
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

        list($global, $federation) = $this->partitionSchema($createSchema);

        $globalSql = $this->synchronizer->getCreateSchema($global);
        if ($globalSql) {
            $sql[] = "-- Create Root Federation\n" .
                     "USE FEDERATION ROOT WITH RESET;";
            $sql = array_merge($sql, $globalSql);
        }

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

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

            $sql[] = $this->getCreateFederationStatement();
            $sql[] = "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $defaultValue . ") WITH RESET, FILTERING = OFF;";
            $sql = array_merge($sql, $federationSql);
        }

        return $sql;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
97
     * {@inheritdoc}
98 99 100
     */
    public function getUpdateSchema(Schema $toSchema, $noDrops = false)
    {
Steve Müller's avatar
Steve Müller committed
101
        return $this->work($toSchema, function ($synchronizer, $schema) use ($noDrops) {
102 103 104 105 106
            return $synchronizer->getUpdateSchema($schema, $noDrops);
        });
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
107
     * {@inheritdoc}
108 109 110
     */
    public function getDropSchema(Schema $dropSchema)
    {
Steve Müller's avatar
Steve Müller committed
111
        return $this->work($dropSchema, function ($synchronizer, $schema) {
112 113 114 115 116
            return $synchronizer->getDropSchema($schema);
        });
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
117
     * {@inheritdoc}
118 119 120 121 122 123 124
     */
    public function createSchema(Schema $createSchema)
    {
        $this->processSql($this->getCreateSchema($createSchema));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
125
     * {@inheritdoc}
126 127 128 129 130 131 132
     */
    public function updateSchema(Schema $toSchema, $noDrops = false)
    {
        $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
133
     * {@inheritdoc}
134 135 136 137 138 139 140
     */
    public function dropSchema(Schema $dropSchema)
    {
        $this->processSqlSafely($this->getDropSchema($dropSchema));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
141
     * {@inheritdoc}
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
     */
    public function getDropAllSchema()
    {
        $this->shardManager->selectGlobal();
        $globalSql = $this->synchronizer->getDropAllSchema();

        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 = $this->synchronizer->getDropAllSchema();
            if ($federationSql) {
                $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);
            }
        }

        $sql[] = "USE FEDERATION ROOT WITH RESET;";
        $sql[] = "DROP FEDERATION " . $this->shardManager->getFederationName();

        return $sql;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
172
     * {@inheritdoc}
173 174 175 176 177 178
     */
    public function dropAllSchema()
    {
        $this->processSqlSafely($this->getDropAllSchema());
    }

Benjamin Morel's avatar
Benjamin Morel committed
179 180 181 182 183
    /**
     * @param \Doctrine\DBAL\Schema\Schema $schema
     *
     * @return array
     */
184 185 186 187 188 189 190 191
    private function partitionSchema(Schema $schema)
    {
        return array(
            $this->extractSchemaFederation($schema, false),
            $this->extractSchemaFederation($schema, true),
        );
    }

Benjamin Morel's avatar
Benjamin Morel committed
192 193 194 195 196 197 198 199
    /**
     * @param \Doctrine\DBAL\Schema\Schema $schema
     * @param boolean                      $isFederation
     *
     * @return \Doctrine\DBAL\Schema\Schema
     *
     * @throws \RuntimeException
     */
200 201
    private function extractSchemaFederation(Schema $schema, $isFederation)
    {
Pascal Borreli's avatar
Pascal Borreli committed
202
        $partitionedSchema = clone $schema;
203

Pascal Borreli's avatar
Pascal Borreli committed
204
        foreach ($partitionedSchema->getTables() as $table) {
205 206 207 208
            if ($isFederation) {
                $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
            }

Steve Müller's avatar
Steve Müller committed
209
            if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
Pascal Borreli's avatar
Pascal Borreli committed
210
                $partitionedSchema->dropTable($table->getName());
211 212 213 214 215 216 217 218 219 220
            } else {
                foreach ($table->getForeignKeys() as $fk) {
                    $foreignTable = $schema->getTable($fk->getForeignTableName());
                    if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
                        throw new \RuntimeException("Cannot have foreign key between global/federation.");
                    }
                }
            }
        }

Pascal Borreli's avatar
Pascal Borreli committed
221
        return $partitionedSchema;
222 223 224 225
    }

    /**
     * Work on the Global/Federation based on currently existing shards and
Pascal Borreli's avatar
Pascal Borreli committed
226 227
     * perform the given operation on the underlying schema synchronizer given
     * the different partitioned schema instances.
228
     *
Benjamin Morel's avatar
Benjamin Morel committed
229 230 231
     * @param \Doctrine\DBAL\Schema\Schema $schema
     * @param \Closure                     $operation
     *
232 233 234 235 236
     * @return array
     */
    private function work(Schema $schema, \Closure $operation)
    {
        list($global, $federation) = $this->partitionSchema($schema);
237
        $sql = [];
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262

        $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);
            if ($federationSql) {
                $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);
            }
        }

        return $sql;
    }

Benjamin Morel's avatar
Benjamin Morel committed
263 264 265
    /**
     * @return string
     */
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
    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;
        }
283

284 285 286
        return $defaultValue;
    }

Benjamin Morel's avatar
Benjamin Morel committed
287 288 289
    /**
     * @return string
     */
290 291 292
    private function getCreateFederationStatement()
    {
        $federationType = Type::getType($this->shardManager->getDistributionType());
293
        $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform());
294 295 296 297 298

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