Skip to content
Browse files

Sync, MultiTenantVisitor, composer and more README.

  • Loading branch information...
1 parent a2138a4 commit ea37c27856713db292843ba6cf9eb3446026bf16 @beberlei beberlei committed Mar 12, 2012
View
1 .gitignore
@@ -0,0 +1 @@
+vendor
View
4 README.md
@@ -4,7 +4,7 @@ Doctrine Extension to support horizontal sharding in the Doctrine ORM.
## Idea
-Implement sharding inside Doctrine at a level that is almost invisible to the developer.
+Implement sharding inside Doctrine at a level that is unobstrusive to the developer as possible.
Problems to tackle:
@@ -36,7 +36,7 @@ Instead of `Doctrine\DBAL\Connection` and the respective statement we need a sha
### SQL Azure Federations
-SQL Azure is a special case, points 1, 2, 3 and 8 are partly handled on the database level. This makes it a perfect test-implementation for just the subset of features in points 4-7. However there need to be a way to configure SchemaTool to generate the correct Schema on SQL Azure.
+SQL Azure is a special case, points 1, 2, 3, 4, 7 and 8 are partly handled on the database level. This makes it a perfect test-implementation for just the subset of features in points 5-6. However there need to be a way to configure SchemaTool to generate the correct Schema on SQL Azure.
* SELECT Operations: The most simple assumption is to always query all shards unless the user specifies otherwise explicitly.
* Queries can be merged in PHP code, this obviously does not work for DISTINCT, GROUP BY and ORDER BY queries.
View
11 composer.json
@@ -0,0 +1,11 @@
+{
+ "name": "doctrine/shards",
+ "require": {
+ "doctrine/dbal": "master-dev"
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Shards\\": "lib"
+ }
+ }
+}
View
13 composer.lock
@@ -0,0 +1,13 @@
+{
+ "hash": "e21f09d98eab36f406f41df55b7e18dd",
+ "packages": [
+ {
+ "package": "doctrine\/common",
+ "version": "2.2.0"
+ },
+ {
+ "package": "doctrine\/dbal",
+ "version": "master-dev"
+ }
+ ]
+}
View
98 lib/Doctrine/Shards/DBAL/SQLAzure/Schema/MultiTenantVisitor.php
@@ -19,6 +19,15 @@
namespace Doctrine\Shards\DBAL\SQLAzure\Schema;
+use Doctrine\DBAL\Schema\Visitor\Visitor,
+ Doctrine\DBAL\Schema\Table,
+ Doctrine\DBAL\Schema\Schema,
+ Doctrine\DBAL\Schema\Column,
+ Doctrine\DBAL\Schema\ForeignKeyConstraint,
+ Doctrine\DBAL\Schema\Constraint,
+ Doctrine\DBAL\Schema\Sequence,
+ Doctrine\DBAL\Schema\Index;
+
/**
* Converts a single tenant schema into a multi-tenant schema for SQL Azure
* Federations under the following assumptions:
@@ -28,14 +37,17 @@
* - 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')`.
- * - You have to work with `filtering=On` when using federations.
+ * - You always have to work with `filtering=On` when using federations with this
+ * multi-tenant approach.
* - 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
+ * tables.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
-class MultiTenantVisitor
+class MultiTenantVisitor implements Visitor
{
/**
* @var array
@@ -50,13 +62,89 @@ class MultiTenantVisitor
/**
* @var string
*/
- private $tenantColumnType;
+ private $tenantColumnType = 'integer';
- public function __construct(array $excludedTables = array(), $tenantColumnName = 'tenant_id', $tenantColumnType = 'integer')
+ public function __construct(array $excludedTables = array(), $tenantColumnName = 'tenant_id')
{
$this->excludedTables = $excludedTables;
$this->tenantColumnName = $tenantColumnName;
- $this->tenantColumnType = $tenantColumnType;
+ }
+
+ /**
+ * @param Table $table
+ */
+ public function acceptTable(Table $table)
+ {
+ if (in_array($table->getName(), $this->excludedTables)) {
+ return;
+ }
+
+ // TODO: will only work with integer columns as of now.
+ $table->addColumn($this->tenantColumnName, $this->tenantColumnType, array(
+ 'default' => "federation_filtering_value('". $this->tenantColumnName ."')",
+ ));
+
+ $clusteredIndex = $this->getClusteredIndex($table);
+
+ $indexColumns = $clusteredIndex->getColumns();
+ $indexColumns[] = $this->tenantColumnName;
+
+ if ($clusteredIndex->isPrimary()) {
+ $table->dropPrimaryKey();
+ $table->setPrimaryKey($indexColumns);
+ } else {
+ $table->dropIndex($clusteredIndex->getName());
+ $table->addIndex($indexColumns);
+ }
+ }
+
+ private function getClusteredIndex($table)
+ {
+ foreach ($table->getIndexes() as $index) {
+ if ($index->isPrimary() && ! $index->hasFlag('nonclustered')) {
+ return $index;
+ } else if ($index->hasFlag('clustered')) {
+ return $index;
+ }
+ }
+ throw new \RuntimeException("No clustered index found on table " . $table->getName());
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function acceptSchema(Schema $schema)
+ {
+ }
+
+ /**
+ * @param Column $column
+ */
+ public function acceptColumn(Table $table, Column $column)
+ {
+ }
+
+ /**
+ * @param Table $localTable
+ * @param ForeignKeyConstraint $fkConstraint
+ */
+ public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
+ {
+ }
+
+ /**
+ * @param Table $table
+ * @param Index $index
+ */
+ public function acceptIndex(Table $table, Index $index)
+ {
+ }
+
+ /**
+ * @param Sequence $sequence
+ */
+ public function acceptSequence(Sequence $sequence)
+ {
}
}
View
14 phpunit.xml.dist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php" colors="true">
+ <testsuites>
+ <testsuite name="Doctrine Shards">
+ <directory suffix="Test.php">tests/Doctrine/Tests/Shards</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>lib/Doctrine/Shards</directory>
+ </whitelist>
+ </filter>
+</phpunit>
View
42 tests/Doctrine/Tests/Shards/DBAL/SQLAzure/MultiTenantVisitorTest.php
@@ -0,0 +1,42 @@
+<?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
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Tests\Shards\DBAL\SQLAzure;
+
+use Doctrine\DBAL\Platforms\SQLAzurePlatform;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\Shards\DBAL\SQLAzure\Schema\MultiTenantVisitor;
+
+class MultiTenantVisitorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testMultiTenant()
+ {
+ $platform = new SQLAzurePlatform();
+ $visitor = new MultiTenantVisitor();
+
+ $schema = new Schema();
+ $foo = $schema->createTable('foo');
+ $foo->addColumn('id', 'string');
+ $foo->setPrimaryKey(array('id'));
+ $schema->visit($visitor);
+
+ var_dump($schema);
+ }
+}
+
View
11 tests/bootstrap.php
@@ -0,0 +1,11 @@
+<?php
+
+if (!@include __DIR__ . '/../vendor/.composer/autoload.php') {
+ die(<<<'EOT'
+You must set up the project dependencies, run the following commands:
+wget http://getcomposer.org/composer.phar
+php composer.phar install
+EOT
+ );
+}
+

0 comments on commit ea37c27

Please sign in to comment.
Something went wrong with that request. Please try again.