Completed
Push — master ( 09e072...c7757e )
by Luís
15s
created

MultiTenantVisitor   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 117
Duplicated Lines 0 %

Test Coverage

Coverage 86.11%

Importance

Changes 0
Metric Value
wmc 15
dl 0
loc 117
ccs 31
cts 36
cp 0.8611
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A acceptColumn() 0 2 1
A acceptTable() 0 22 3
A __construct() 0 5 2
A acceptIndex() 0 2 1
A acceptForeignKey() 0 2 1
A acceptSequence() 0 2 1
A acceptSchema() 0 2 1
B getClusteredIndex() 0 10 5
1
<?php
2
/*
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.
14
 *
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>.
18
 */
19
20
namespace Doctrine\DBAL\Sharding\SQLAzure\Schema;
21
22
use Doctrine\DBAL\Schema\Visitor\Visitor;
23
use Doctrine\DBAL\Schema\Table;
24
use Doctrine\DBAL\Schema\Schema;
25
use Doctrine\DBAL\Schema\Column;
26
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
27
use Doctrine\DBAL\Schema\Sequence;
28
use Doctrine\DBAL\Schema\Index;
29
30
/**
31
 * Converts a single tenant schema into a multi-tenant schema for SQL Azure
32
 * Federations under the following assumptions:
33
 *
34
 * - Every table is part of the multi-tenant application, only explicitly
35
 *   excluded tables are non-federated. The behavior of the tables being in
36
 *   global or federated database is undefined. It depends on you selecting a
37
 *   federation before DDL statements or not.
38
 * - Every Primary key of a federated table is extended by another column
39
 *   'tenant_id' with a default value of the SQLAzure function
40
 *   `federation_filtering_value('tenant_id')`.
41
 * - You always have to work with `filtering=On` when using federations with this
42
 *   multi-tenant approach.
43
 * - Primary keys are either using globally unique ids (GUID, Table Generator)
44
 *   or you explicitly add the tenant_id in every UPDATE or DELETE statement
45
 *   (otherwise they will affect the same-id rows from other tenants as well).
46
 *   SQLAzure throws errors when you try to create IDENTIY columns on federated
47
 *   tables.
48
 *
49
 * @author Benjamin Eberlei <[email protected]>
50
 */
51
class MultiTenantVisitor implements Visitor
52
{
53
    /**
54
     * @var array
55
     */
56
    private $excludedTables = [];
57
58
    /**
59
     * @var string
60
     */
61
    private $tenantColumnName;
62
63
    /**
64
     * @var string
65
     */
66
    private $tenantColumnType = 'integer';
67
68
    /**
69
     * Name of the federation distribution, defaulting to the tenantColumnName
70
     * if not specified.
71
     *
72
     * @var string
73
     */
74
    private $distributionName;
75
76
    /**
77
     * @param array       $excludedTables
78
     * @param string      $tenantColumnName
79
     * @param string|null $distributionName
80
     */
81 2
    public function __construct(array $excludedTables = [], $tenantColumnName = 'tenant_id', $distributionName = null)
82
    {
83 2
        $this->excludedTables = $excludedTables;
84 2
        $this->tenantColumnName = $tenantColumnName;
85 2
        $this->distributionName = $distributionName ?: $tenantColumnName;
86 2
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 2
    public function acceptTable(Table $table)
92
    {
93 2
        if (in_array($table->getName(), $this->excludedTables)) {
94
            return;
95
        }
96
97 2
        $table->addColumn($this->tenantColumnName, $this->tenantColumnType, [
98 2
            'default' => "federation_filtering_value('". $this->distributionName ."')",
99
        ]);
100
101 2
        $clusteredIndex = $this->getClusteredIndex($table);
102
103 2
        $indexColumns = $clusteredIndex->getColumns();
104 2
        $indexColumns[] = $this->tenantColumnName;
105
106 2
        if ($clusteredIndex->isPrimary()) {
107 1
            $table->dropPrimaryKey();
108 1
            $table->setPrimaryKey($indexColumns);
109
        } else {
110 1
            $table->dropIndex($clusteredIndex->getName());
111 1
            $table->addIndex($indexColumns, $clusteredIndex->getName());
112 1
            $table->getIndex($clusteredIndex->getName())->addFlag('clustered');
113
        }
114 2
    }
115
116
    /**
117
     * @param \Doctrine\DBAL\Schema\Table $table
118
     *
119
     * @return \Doctrine\DBAL\Schema\Index
120
     *
121
     * @throws \RuntimeException
122
     */
123 2
    private function getClusteredIndex($table)
124
    {
125 2
        foreach ($table->getIndexes() as $index) {
126 2
            if ($index->isPrimary() && ! $index->hasFlag('nonclustered')) {
127 1
                return $index;
128 1
            } elseif ($index->hasFlag('clustered')) {
129 1
                return $index;
130
            }
131
        }
132
        throw new \RuntimeException("No clustered index found on table " . $table->getName());
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 2
    public function acceptSchema(Schema $schema)
139
    {
140 2
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 2
    public function acceptColumn(Table $table, Column $column)
146
    {
147 2
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
153
    {
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 2
    public function acceptIndex(Table $table, Index $index)
160
    {
161 2
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function acceptSequence(Sequence $sequence)
167
    {
168
    }
169
}
170