Failed Conditions
Pull Request — develop (#3581)
by Jonathan
12:44
created

MultiTenantVisitor::acceptTable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3.0032

Importance

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