SQLAzureFederationsSynchronizer.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. namespace Doctrine\DBAL\Sharding\SQLAzure;
  3. use Closure;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\DBAL\Schema\Schema;
  6. use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer;
  7. use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer;
  8. use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
  9. use Doctrine\DBAL\Types\Type;
  10. use Doctrine\DBAL\Types\Types;
  11. use RuntimeException;
  12. use function array_merge;
  13. /**
  14. * SQL Azure Schema Synchronizer.
  15. *
  16. * Will iterate over all shards when performing schema operations. This is done
  17. * by partitioning the passed schema into subschemas for the federation and the
  18. * global database and then applying the operations step by step using the
  19. * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}.
  20. *
  21. * @deprecated
  22. */
  23. class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer
  24. {
  25. public const FEDERATION_TABLE_FEDERATED = 'azure.federated';
  26. public const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName';
  27. /** @var SQLAzureShardManager */
  28. private $shardManager;
  29. /** @var SchemaSynchronizer */
  30. private $synchronizer;
  31. public function __construct(Connection $conn, SQLAzureShardManager $shardManager, ?SchemaSynchronizer $sync = null)
  32. {
  33. parent::__construct($conn);
  34. $this->shardManager = $shardManager;
  35. $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn);
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public function getCreateSchema(Schema $createSchema)
  41. {
  42. $sql = [];
  43. [$global, $federation] = $this->partitionSchema($createSchema);
  44. $globalSql = $this->synchronizer->getCreateSchema($global);
  45. if ($globalSql) {
  46. $sql[] = "-- Create Root Federation\n" .
  47. 'USE FEDERATION ROOT WITH RESET;';
  48. $sql = array_merge($sql, $globalSql);
  49. }
  50. $federationSql = $this->synchronizer->getCreateSchema($federation);
  51. if ($federationSql) {
  52. $defaultValue = $this->getFederationTypeDefaultValue();
  53. $sql[] = $this->getCreateFederationStatement();
  54. $sql[] = 'USE FEDERATION ' . $this->shardManager->getFederationName()
  55. . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $defaultValue . ')'
  56. . ' WITH RESET, FILTERING = OFF;';
  57. $sql = array_merge($sql, $federationSql);
  58. }
  59. return $sql;
  60. }
  61. /**
  62. * {@inheritdoc}
  63. */
  64. public function getUpdateSchema(Schema $toSchema, $noDrops = false)
  65. {
  66. return $this->work($toSchema, static function ($synchronizer, $schema) use ($noDrops) {
  67. return $synchronizer->getUpdateSchema($schema, $noDrops);
  68. });
  69. }
  70. /**
  71. * {@inheritdoc}
  72. */
  73. public function getDropSchema(Schema $dropSchema)
  74. {
  75. return $this->work($dropSchema, static function ($synchronizer, $schema) {
  76. return $synchronizer->getDropSchema($schema);
  77. });
  78. }
  79. /**
  80. * {@inheritdoc}
  81. */
  82. public function createSchema(Schema $createSchema)
  83. {
  84. $this->processSql($this->getCreateSchema($createSchema));
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. public function updateSchema(Schema $toSchema, $noDrops = false)
  90. {
  91. $this->processSql($this->getUpdateSchema($toSchema, $noDrops));
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function dropSchema(Schema $dropSchema)
  97. {
  98. $this->processSqlSafely($this->getDropSchema($dropSchema));
  99. }
  100. /**
  101. * {@inheritdoc}
  102. */
  103. public function getDropAllSchema()
  104. {
  105. $this->shardManager->selectGlobal();
  106. $globalSql = $this->synchronizer->getDropAllSchema();
  107. $sql = [];
  108. if ($globalSql) {
  109. $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
  110. $sql = array_merge($sql, $globalSql);
  111. }
  112. $shards = $this->shardManager->getShards();
  113. foreach ($shards as $shard) {
  114. $this->shardManager->selectShard($shard['rangeLow']);
  115. $federationSql = $this->synchronizer->getDropAllSchema();
  116. if (! $federationSql) {
  117. continue;
  118. }
  119. $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n" .
  120. 'USE FEDERATION ' . $this->shardManager->getFederationName()
  121. . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ')'
  122. . ' WITH RESET, FILTERING = OFF;';
  123. $sql = array_merge($sql, $federationSql);
  124. }
  125. $sql[] = 'USE FEDERATION ROOT WITH RESET;';
  126. $sql[] = 'DROP FEDERATION ' . $this->shardManager->getFederationName();
  127. return $sql;
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. public function dropAllSchema()
  133. {
  134. $this->processSqlSafely($this->getDropAllSchema());
  135. }
  136. /**
  137. * @return Schema[]
  138. */
  139. private function partitionSchema(Schema $schema)
  140. {
  141. return [
  142. $this->extractSchemaFederation($schema, false),
  143. $this->extractSchemaFederation($schema, true),
  144. ];
  145. }
  146. /**
  147. * @param bool $isFederation
  148. *
  149. * @return Schema
  150. *
  151. * @throws RuntimeException
  152. */
  153. private function extractSchemaFederation(Schema $schema, $isFederation)
  154. {
  155. $partitionedSchema = clone $schema;
  156. foreach ($partitionedSchema->getTables() as $table) {
  157. if ($isFederation) {
  158. $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey());
  159. }
  160. if ($table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
  161. $partitionedSchema->dropTable($table->getName());
  162. } else {
  163. foreach ($table->getForeignKeys() as $fk) {
  164. $foreignTable = $schema->getTable($fk->getForeignTableName());
  165. if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) {
  166. throw new RuntimeException('Cannot have foreign key between global/federation.');
  167. }
  168. }
  169. }
  170. }
  171. return $partitionedSchema;
  172. }
  173. /**
  174. * Work on the Global/Federation based on currently existing shards and
  175. * perform the given operation on the underlying schema synchronizer given
  176. * the different partitioned schema instances.
  177. *
  178. * @return string[]
  179. */
  180. private function work(Schema $schema, Closure $operation)
  181. {
  182. [$global, $federation] = $this->partitionSchema($schema);
  183. $sql = [];
  184. $this->shardManager->selectGlobal();
  185. $globalSql = $operation($this->synchronizer, $global);
  186. if ($globalSql) {
  187. $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;";
  188. $sql = array_merge($sql, $globalSql);
  189. }
  190. $shards = $this->shardManager->getShards();
  191. foreach ($shards as $shard) {
  192. $this->shardManager->selectShard($shard['rangeLow']);
  193. $federationSql = $operation($this->synchronizer, $federation);
  194. if (! $federationSql) {
  195. continue;
  196. }
  197. $sql[] = '-- Work on Federation ID ' . $shard['id'] . "\n"
  198. . 'USE FEDERATION ' . $this->shardManager->getFederationName()
  199. . ' (' . $this->shardManager->getDistributionKey() . ' = ' . $shard['rangeLow'] . ')'
  200. . ' WITH RESET, FILTERING = OFF;';
  201. $sql = array_merge($sql, $federationSql);
  202. }
  203. return $sql;
  204. }
  205. /**
  206. * @return string
  207. */
  208. private function getFederationTypeDefaultValue()
  209. {
  210. $federationType = Type::getType($this->shardManager->getDistributionType());
  211. switch ($federationType->getName()) {
  212. case Types::GUID:
  213. $defaultValue = '00000000-0000-0000-0000-000000000000';
  214. break;
  215. case Types::INTEGER:
  216. case Types::SMALLINT:
  217. case Types::BIGINT:
  218. $defaultValue = '0';
  219. break;
  220. default:
  221. $defaultValue = '';
  222. break;
  223. }
  224. return $defaultValue;
  225. }
  226. /**
  227. * @return string
  228. */
  229. private function getCreateFederationStatement()
  230. {
  231. $federationType = Type::getType($this->shardManager->getDistributionType());
  232. $federationTypeSql = $federationType->getSQLDeclaration([], $this->conn->getDatabasePlatform());
  233. return "--Create Federation\n"
  234. . 'CREATE FEDERATION ' . $this->shardManager->getFederationName()
  235. . ' (' . $this->shardManager->getDistributionKey()
  236. . ' ' . $federationTypeSql . ' RANGE)';
  237. }
  238. }