Completed
Pull Request — 2.11.x (#3956)
by David
14:33
created

MultiTenantVisitor   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 113
Duplicated Lines 0 %

Test Coverage

Coverage 86.11%

Importance

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