3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\DBAL\Sharding\SQLAzure;
22 use Doctrine\DBAL\Schema\Schema;
23 use Doctrine\DBAL\Connection;
24 use Doctrine\DBAL\Types\Type;
26 use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer;
27 use Doctrine\DBAL\Sharding\SingleDatabaseSynchronizer;
30 * SQL Azure Schema Synchronizer
32 * Will iterate over all shards when performing schema operations. This is done
33 * by partitioning the passed schema into subschemas for the federation and the
34 * global database and then applying the operations step by step using the
35 * {@see \Doctrine\DBAL\Sharding\SingleDatabaseSynchronizer}.
37 * @author Benjamin Eberlei <kontakt@beberlei.de>
39 class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
41 const FEDERATION_TABLE_FEDERATED = 'azure.federated';
42 const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
46 * @var SQLAzureShardManager
48 private $shardManager;
51 * @var SchemaSynchronizer
53 private $synchronizer;
55 public function __construct(Connection $conn, SQLAzureShardManager $shardManager, SchemaSynchronizer $sync = null)
57 parent::__construct($conn);
58 $this->shardManager = $shardManager;
59 $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
63 * Get the SQL statements that can be executed to create the schema.
65 * @param Schema $createSchema
68 public function getCreateSchema(Schema $createSchema)
72 list($global, $federation) = $this->partitionSchema($createSchema);
74 $globalSql = $this->synchronizer->getCreateSchema($global);
76 $sql[] = "-- Create Root Federation\n" .
77 "USE FEDERATION ROOT WITH RESET;";
78 $sql = array_merge($sql, $globalSql);
81 $federationSql = $this->synchronizer->getCreateSchema($federation);
84 $defaultValue = $this->getFederationTypeDefaultValue();
86 $sql[] = $this->getCreateFederationStatement();
87 $sql[] = "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $defaultValue . ") WITH RESET, FILTERING = OFF;";
88 $sql = array_merge($sql, $federationSql);
95 * Get the SQL Statements to update given schema with the underlying db.
97 * @param Schema $toSchema
98 * @param bool $noDrops
101 public function getUpdateSchema(Schema $toSchema, $noDrops = false)
103 return $this->work($toSchema, function($synchronizer, $schema) use ($noDrops) {
104 return $synchronizer->getUpdateSchema($schema, $noDrops);
109 * Get the SQL Statements to drop the given schema from underlying db.
111 * @param Schema $dropSchema
114 public function getDropSchema(Schema $dropSchema)
116 return $this->work($dropSchema, function($synchronizer, $schema) {
117 return $synchronizer->getDropSchema($schema);
124 * @param Schema $createSchema
127 public function createSchema(Schema $createSchema)
129 $this->processSql($this->getCreateSchema($createSchema));
133 * Update the Schema to new schema version.
135 * @param Schema $toSchema
138 public function updateSchema(Schema $toSchema, $noDrops = false)
140 $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
144 * Drop the given database schema from the underlying db.
146 * @param Schema $dropSchema
149 public function dropSchema(Schema $dropSchema)
151 $this->processSqlSafely($this->getDropSchema($dropSchema));
155 * Get the SQL statements to drop all schema assets from underlying db.
159 public function getDropAllSchema()
161 $this->shardManager->selectGlobal();
162 $globalSql = $this->synchronizer->getDropAllSchema();
165 $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
166 $sql = array_merge($sql, $globalSql);
169 $shards = $this->shardManager->getShards();
170 foreach ($shards as $shard) {
171 $this->shardManager->selectShard($shard['rangeLow']);
173 $federationSql = $this->synchronizer->getDropAllSchema();
174 if ($federationSql) {
175 $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" .
176 "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;";
177 $sql = array_merge($sql, $federationSql);
181 $sql[] = "USE FEDERATION ROOT WITH RESET;";
182 $sql[] = "DROP FEDERATION " . $this->shardManager->getFederationName();
188 * Drop all assets from the underyling db.
192 public function dropAllSchema()
194 $this->processSqlSafely($this->getDropAllSchema());
197 private function partitionSchema(Schema $schema)
200 $this->extractSchemaFederation($schema, false),
201 $this->extractSchemaFederation($schema, true),
205 private function extractSchemaFederation(Schema $schema, $isFederation)
207 $partionedSchema = clone $schema;
209 foreach ($partionedSchema->getTables() as $table) {
211 $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
214 if ( $table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
215 $partionedSchema->dropTable($table->getName());
217 foreach ($table->getForeignKeys() as $fk) {
218 $foreignTable = $schema->getTable($fk->getForeignTableName());
219 if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
220 throw new \RuntimeException("Cannot have foreign key between global/federation.");
226 return $partionedSchema;
230 * Work on the Global/Federation based on currently existing shards and
231 * perform the given operation on the underyling schema synchronizer given
232 * the different partioned schema instances.
234 * @param Schema $schema
235 * @param Closure $operation
238 private function work(Schema $schema, \Closure $operation)
240 list($global, $federation) = $this->partitionSchema($schema);
243 $this->shardManager->selectGlobal();
244 $globalSql = $operation($this->synchronizer, $global);
247 $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
248 $sql = array_merge($sql, $globalSql);
251 $shards = $this->shardManager->getShards();
253 foreach ($shards as $shard) {
254 $this->shardManager->selectShard($shard['rangeLow']);
256 $federationSql = $operation($this->synchronizer, $federation);
257 if ($federationSql) {
258 $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" .
259 "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;";
260 $sql = array_merge($sql, $federationSql);
267 private function getFederationTypeDefaultValue()
269 $federationType = Type::getType($this->shardManager->getDistributionType());
271 switch ($federationType->getName()) {
273 $defaultValue = '00000000-0000-0000-0000-000000000000';
284 return $defaultValue;
287 private function getCreateFederationStatement()
289 $federationType = Type::getType($this->shardManager->getDistributionType());
290 $federationTypeSql = $federationType->getSqlDeclaration(array(), $this->conn->getDatabasePlatform());
292 return "--Create Federation\n" .
293 "CREATE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " " . $federationTypeSql ." RANGE)";