diff --git a/AZURE_FEDERATIONS.md b/AZURE_FEDERATIONS.md index 2d148a7..c8bf88b 100644 --- a/AZURE_FEDERATIONS.md +++ b/AZURE_FEDERATIONS.md @@ -22,6 +22,14 @@ Implementing Federations inside a new Doctrine Sharding Extension. Some extensio * Extend ID Generation layer * Implement API that allow users to choose the shard target for any given query +## Implementation Details + +SQL Azure requires one and exactly one clustered index. It makes no difference if the primary key +or any other key is the clustered index. Sharding requires an external ID generation (no auto-increment) +such as GUIDs. GUIDs have negative properties with regard to clustered index performance, so that +typically you would add a "created" timestamp for example that holds the clustered index instead +of making the GUID a clustered index. + ## Example API: @@@ php diff --git a/composer.lock b/composer.lock index 9055bfa..7e8d292 100644 --- a/composer.lock +++ b/composer.lock @@ -2,12 +2,14 @@ "hash": "e21f09d98eab36f406f41df55b7e18dd", "packages": [ { - "package": "doctrine\/common", - "version": "2.2.0" + "package": "doctrine/common", + "version": "2.2.1" }, { - "package": "doctrine\/dbal", - "version": "master-dev" + "package": "doctrine/dbal", + "version": "dev-master", + "source-reference": "456a756febb258b52092fa2640c77bb8400114fa" } - ] -} \ No newline at end of file + ], + "aliases": [] +} diff --git a/lib/Doctrine/Shards/DBAL/SQLAzure/Schema/MultiTenantVisitor.php b/lib/Doctrine/Shards/DBAL/SQLAzure/Schema/MultiTenantVisitor.php index fe786cd..f516811 100644 --- a/lib/Doctrine/Shards/DBAL/SQLAzure/Schema/MultiTenantVisitor.php +++ b/lib/Doctrine/Shards/DBAL/SQLAzure/Schema/MultiTenantVisitor.php @@ -33,7 +33,9 @@ * Federations under the following assumptions: * * - Every table is part of the multi-tenant application, only explicitly - * excluded tables are non-federated. + * excluded tables are non-federated. The behavior of the tables being in + * global or federated database is undefined. It depends on you selecting a + * federation before DDL statements or not. * - Every Primary key of a federated table is extended by another column * 'tenant_id' with a default value of the SQLAzure function * `federation_filtering_value('tenant_id')`. @@ -42,7 +44,7 @@ * - Primary keys are either using globally unique ids (GUID, Table Generator) * or you explicitly add the tenent_id in every UPDATE or DELETE statement * (otherwise they will affect the same-id rows from other tenents as well). - * SQLAzure throws exception when IDENTIY columns are used on federated + * SQLAzure throws errors when you try to create IDENTIY columns on federated * tables. * * @author Benjamin Eberlei @@ -64,10 +66,19 @@ class MultiTenantVisitor implements Visitor */ private $tenantColumnType = 'integer'; - public function __construct(array $excludedTables = array(), $tenantColumnName = 'tenant_id') + /** + * Name of the federation distribution, defaulting to the tenantColumnName + * if not specified. + * + * @var string + */ + private $distributionName; + + public function __construct(array $excludedTables = array(), $tenantColumnName = 'tenant_id', $distributionName = null) { $this->excludedTables = $excludedTables; $this->tenantColumnName = $tenantColumnName; + $this->distributionName = $distributionName ?: $tenantColumnName; } /** @@ -79,9 +90,8 @@ public function acceptTable(Table $table) return; } - // TODO: will only work with integer columns as of now. $table->addColumn($this->tenantColumnName, $this->tenantColumnType, array( - 'default' => "federation_filtering_value('". $this->tenantColumnName ."')", + 'default' => "federation_filtering_value('". $this->distributionName ."')", )); $clusteredIndex = $this->getClusteredIndex($table); @@ -94,7 +104,8 @@ public function acceptTable(Table $table) $table->setPrimaryKey($indexColumns); } else { $table->dropIndex($clusteredIndex->getName()); - $table->addIndex($indexColumns); + $table->addIndex($indexColumns, $clusteredIndex->getName()); + $table->getIndex($clusteredIndex->getName())->addFlag('clustered'); } } diff --git a/tests/Doctrine/Tests/Shards/DBAL/SQLAzure/MultiTenantVisitorTest.php b/tests/Doctrine/Tests/Shards/DBAL/SQLAzure/MultiTenantVisitorTest.php index 671b275..587fb80 100644 --- a/tests/Doctrine/Tests/Shards/DBAL/SQLAzure/MultiTenantVisitorTest.php +++ b/tests/Doctrine/Tests/Shards/DBAL/SQLAzure/MultiTenantVisitorTest.php @@ -25,7 +25,7 @@ class MultiTenantVisitorTest extends \PHPUnit_Framework_TestCase { - public function testMultiTenant() + public function testMultiTenantPrimaryKey() { $platform = new SQLAzurePlatform(); $visitor = new MultiTenantVisitor(); @@ -36,7 +36,30 @@ public function testMultiTenant() $foo->setPrimaryKey(array('id')); $schema->visit($visitor); - var_dump($schema); + $this->assertEquals(array('id', 'tenant_id'), $foo->getPrimaryKey()->getColumns()); + $this->assertTrue($foo->hasColumn('tenant_id')); + } + + public function testMultiTenantNonPrimaryKey() + { + $platform = new SQLAzurePlatform(); + $visitor = new MultiTenantVisitor(); + + $schema = new Schema(); + $foo = $schema->createTable('foo'); + $foo->addColumn('id', 'string'); + $foo->addColumn('created', 'datetime'); + $foo->setPrimaryKey(array('id')); + $foo->addIndex(array('created'), 'idx'); + + $foo->getPrimaryKey()->addFlag('nonclustered'); + $foo->getIndex('idx')->addFlag('clustered'); + + $schema->visit($visitor); + + $this->assertEquals(array('id'), $foo->getPrimaryKey()->getColumns()); + $this->assertTrue($foo->hasColumn('tenant_id')); + $this->assertEquals(array('created', 'tenant_id'), $foo->getIndex('idx')->getColumns()); } }