Completed
Pull Request — master (#3699)
by
unknown
61:24
created

Index::samePartialIndex()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.6111
cc 5
nc 3
nop 1
crap 5
1
<?php
2
3
namespace Doctrine\DBAL\Schema;
4
5
use Doctrine\DBAL\Platforms\AbstractPlatform;
6
use InvalidArgumentException;
7
use function array_filter;
8
use function array_keys;
9
use function array_map;
10
use function array_search;
11
use function array_shift;
12
use function count;
13
use function is_string;
14
use function strtolower;
15
16
class Index extends AbstractAsset implements Constraint
17
{
18
    /**
19
     * Asset identifier instances of the column names the index is associated with.
20
     * array($columnName => Identifier)
21
     *
22
     * @var Identifier[]
23
     */
24
    protected $_columns = [];
25
26
    /** @var bool */
27
    protected $_isUnique = false;
28
29
    /** @var bool */
30
    protected $_isPrimary = false;
31
32
    /**
33
     * Platform specific flags for indexes.
34
     * array($flagName => true)
35
     *
36
     * @var true[]
37
     */
38
    protected $_flags = [];
39
40
    /**
41
     * Platform specific options
42
     *
43
     * @todo $_flags should eventually be refactored into options
44
     * @var mixed[]
45
     */
46
    private $options = [];
47
48
    /**
49
     * @param string   $indexName
50
     * @param string[] $columns
51
     * @param bool     $isUnique
52
     * @param bool     $isPrimary
53
     * @param string[] $flags
54
     * @param mixed[]  $options
55
     */
56 18565
    public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [])
57
    {
58 18565
        $isUnique = $isUnique || $isPrimary;
59
60 18565
        $this->_setName($indexName);
61 18565
        $this->_isUnique  = $isUnique;
62 18565
        $this->_isPrimary = $isPrimary;
63 18565
        $this->options    = $options;
64
65 18565
        foreach ($columns as $column) {
66 18523
            $this->_addColumn($column);
67
        }
68 18565
        foreach ($flags as $flag) {
69 13266
            $this->addFlag($flag);
70
        }
71 18565
    }
72
73
    /**
74
     * @param string $column
75
     *
76
     * @return void
77
     *
78
     * @throws InvalidArgumentException
79
     */
80 18523
    protected function _addColumn($column)
81
    {
82 18523
        if (! is_string($column)) {
0 ignored issues
show
introduced by
The condition is_string($column) is always true.
Loading history...
83
            throw new InvalidArgumentException('Expecting a string as Index Column');
84
        }
85
86 18523
        $this->_columns[$column] = new Identifier($column);
87 18523
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 18208
    public function getColumns()
93
    {
94 18208
        return array_keys($this->_columns);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 18051
    public function getQuotedColumns(AbstractPlatform $platform)
101
    {
102 18051
        $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths')
103 18051
            ? $this->getOption('lengths') : [];
104
105 18051
        $columns = [];
106
107 18051
        foreach ($this->_columns as $column) {
108 18035
            $length = array_shift($subParts);
109
110 18035
            $quotedColumn = $column->getQuotedName($platform);
111
112 18035
            if ($length !== null) {
113 9034
                $quotedColumn .= '(' . $length . ')';
114
            }
115
116 18035
            $columns[] = $quotedColumn;
117
        }
118
119 18051
        return $columns;
120
    }
121
122
    /**
123
     * @return string[]
124
     */
125 1461
    public function getUnquotedColumns()
126
    {
127 1461
        return array_map([$this, 'trimQuotes'], $this->getColumns());
128
    }
129
130
    /**
131
     * Is the index neither unique nor primary key?
132
     *
133
     * @return bool
134
     */
135 1634
    public function isSimpleIndex()
136
    {
137 1634
        return ! $this->_isPrimary && ! $this->_isUnique;
138
    }
139
140
    /**
141
     * @return bool
142
     */
143 17517
    public function isUnique()
144
    {
145 17517
        return $this->_isUnique;
146
    }
147
148
    /**
149
     * @return bool
150
     */
151 18465
    public function isPrimary()
152
    {
153 18465
        return $this->_isPrimary;
154
    }
155
156
    /**
157
     * @param string $columnName
158
     * @param int    $pos
159
     *
160
     * @return bool
161
     */
162 1461
    public function hasColumnAtPosition($columnName, $pos = 0)
163
    {
164 1461
        $columnName   = $this->trimQuotes(strtolower($columnName));
165 1461
        $indexColumns = array_map('strtolower', $this->getUnquotedColumns());
166
167 1461
        return array_search($columnName, $indexColumns) === $pos;
168
    }
169
170
    /**
171
     * Checks if this index exactly spans the given column names in the correct order.
172
     *
173
     * @param string[] $columnNames
174
     *
175
     * @return bool
176
     */
177 17144
    public function spansColumns(array $columnNames)
178
    {
179 17144
        $columns         = $this->getColumns();
180 17144
        $numberOfColumns = count($columns);
181 17144
        $sameColumns     = true;
182
183 17144
        for ($i = 0; $i < $numberOfColumns; $i++) {
184 17144
            if (isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i]))) {
185 16621
                continue;
186
            }
187
188 16988
            $sameColumns = false;
189
        }
190
191 17144
        return $sameColumns;
192
    }
193
194
    /**
195
     * Checks if the other index already fulfills all the indexing and constraint needs of the current one.
196
     *
197
     * @return bool
198
     */
199 17156
    public function isFulfilledBy(Index $other)
200
    {
201
        // allow the other index to be equally large only. It being larger is an option
202
        // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo)
203 17156
        if (count($other->getColumns()) !== count($this->getColumns())) {
204 14491
            return false;
205
        }
206
207
        // Check if columns are the same, and even in the same order
208 17136
        $sameColumns = $this->spansColumns($other->getColumns());
209
210 17136
        if ($sameColumns) {
0 ignored issues
show
introduced by
The condition $sameColumns is always true.
Loading history...
211 16613
            if (! $this->samePartialIndex($other)) {
212 1659
                return false;
213
            }
214
215 16613
            if (! $this->hasSameColumnLengths($other)) {
216 1561
                return false;
217
            }
218
219 16609
            if (! $this->isUnique() && ! $this->isPrimary()) {
220
                // this is a special case: If the current key is neither primary or unique, any unique or
221
                // primary key will always have the same effect for the index and there cannot be any constraint
222
                // overlaps. This means a primary or unique index can always fulfill the requirements of just an
223
                // index that has no constraints.
224 16245
                return true;
225
            }
226
227 16434
            if ($other->isPrimary() !== $this->isPrimary()) {
228 15090
                return false;
229
            }
230
231 16428
            return $other->isUnique() === $this->isUnique();
232
        }
233
234 16988
        return false;
235
    }
236
237
    /**
238
     * Detects if the other index is a non-unique, non primary index that can be overwritten by this one.
239
     *
240
     * @return bool
241
     */
242 1634
    public function overrules(Index $other)
243
    {
244 1634
        if ($other->isPrimary()) {
245
            return false;
246
        }
247
248 1634
        if ($this->isSimpleIndex() && $other->isUnique()) {
249
            return false;
250
        }
251
252 1634
        return $this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other);
253
    }
254
255
    /**
256
     * Returns platform specific flags for indexes.
257
     *
258
     * @return string[]
259
     */
260 15161
    public function getFlags()
261
    {
262 15161
        return array_keys($this->_flags);
263
    }
264
265
    /**
266
     * Adds Flag for an index that translates to platform specific handling.
267
     *
268
     * @param string $flag
269
     *
270
     * @return Index
271
     *
272
     * @example $index->addFlag('CLUSTERED')
273
     */
274 15397
    public function addFlag($flag)
275
    {
276 15397
        $this->_flags[strtolower($flag)] = true;
277
278 15397
        return $this;
279
    }
280
281
    /**
282
     * Does this index have a specific flag?
283
     *
284
     * @param string $flag
285
     *
286
     * @return bool
287
     */
288 16234
    public function hasFlag($flag)
289
    {
290 16234
        return isset($this->_flags[strtolower($flag)]);
291
    }
292
293
    /**
294
     * Removes a flag.
295
     *
296
     * @param string $flag
297
     *
298
     * @return void
299
     */
300 1484
    public function removeFlag($flag)
301
    {
302 1484
        unset($this->_flags[strtolower($flag)]);
303 1484
    }
304
305
    /**
306
     * @param string $name
307
     *
308
     * @return bool
309
     */
310 17424
    public function hasOption($name)
311
    {
312 17424
        return isset($this->options[strtolower($name)]);
313
    }
314
315
    /**
316
     * @param string $name
317
     *
318
     * @return mixed
319
     */
320 15243
    public function getOption($name)
321
    {
322 15243
        return $this->options[strtolower($name)];
323
    }
324
325
    /**
326
     * @return mixed[]
327
     */
328 15113
    public function getOptions()
329
    {
330 15113
        return $this->options;
331
    }
332
333
    /**
334
     * Return whether the two indexes have the same partial index
335
     *
336
     * @return bool
337
     */
338 16615
    private function samePartialIndex(Index $other)
339
    {
340 16615
        if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where')) {
341 5695
            return true;
342
        }
343
344 16615
        return ! $this->hasOption('where') && ! $other->hasOption('where');
345
    }
346
347
    /**
348
     * Returns whether the index has the same column lengths as the other
349
     */
350 16613
    private function hasSameColumnLengths(self $other) : bool
351
    {
352
        $filter = static function (?int $length) : bool {
353 16455
            return $length !== null;
354 16613
        };
355
356 16613
        return array_filter($this->options['lengths'] ?? [], $filter)
357 16613
            === array_filter($other->options['lengths'] ?? [], $filter);
358
    }
359
}
360