Passed
Push — type-registry ( f9a1df...0931f1 )
by Michael
24:09
created

Index::hasSameColumnLengths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 17234
    public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [])
57
    {
58 17234
        $isUnique = $isUnique || $isPrimary;
59
60 17234
        $this->_setName($indexName);
61 17234
        $this->_isUnique  = $isUnique;
62 17234
        $this->_isPrimary = $isPrimary;
63 17234
        $this->options    = $options;
64
65 17234
        foreach ($columns as $column) {
66 17234
            $this->_addColumn($column);
67
        }
68 17234
        foreach ($flags as $flag) {
69 13294
            $this->addFlag($flag);
70
        }
71 17234
    }
72
73
    /**
74
     * @param string $column
75
     *
76
     * @return void
77
     *
78
     * @throws InvalidArgumentException
79
     */
80 17234
    protected function _addColumn($column)
81
    {
82 17234
        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 17234
        $this->_columns[$column] = new Identifier($column);
87 17234
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92 17234
    public function getColumns()
93
    {
94 17234
        return array_keys($this->_columns);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 17234
    public function getQuotedColumns(AbstractPlatform $platform)
101
    {
102 17234
        $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths')
103 17234
            ? $this->getOption('lengths') : [];
104
105 17234
        $columns = [];
106
107 17234
        foreach ($this->_columns as $column) {
108 17234
            $length = array_shift($subParts);
109
110 17234
            $quotedColumn = $column->getQuotedName($platform);
111
112 17234
            if ($length !== null) {
113 9065
                $quotedColumn .= '(' . $length . ')';
114
            }
115
116 17234
            $columns[] = $quotedColumn;
117
        }
118
119 17234
        return $columns;
120
    }
121
122
    /**
123
     * @return string[]
124
     */
125 1525
    public function getUnquotedColumns()
126
    {
127 1525
        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 1700
    public function isSimpleIndex()
136
    {
137 1700
        return ! $this->_isPrimary && ! $this->_isUnique;
138
    }
139
140
    /**
141
     * @return bool
142
     */
143 16948
    public function isUnique()
144
    {
145 16948
        return $this->_isUnique;
146
    }
147
148
    /**
149
     * @return bool
150
     */
151 17234
    public function isPrimary()
152
    {
153 17234
        return $this->_isPrimary;
154
    }
155
156
    /**
157
     * @param string $columnName
158
     * @param int    $pos
159
     *
160
     * @return bool
161
     */
162 1525
    public function hasColumnAtPosition($columnName, $pos = 0)
163
    {
164 1525
        $columnName   = $this->trimQuotes(strtolower($columnName));
165 1525
        $indexColumns = array_map('strtolower', $this->getUnquotedColumns());
166
167 1525
        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 16923
    public function spansColumns(array $columnNames)
178
    {
179 16923
        $columns         = $this->getColumns();
180 16923
        $numberOfColumns = count($columns);
181 16923
        $sameColumns     = true;
182
183 16923
        for ($i = 0; $i < $numberOfColumns; $i++) {
184 16923
            if (isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i]))) {
185 16489
                continue;
186
            }
187
188 16899
            $sameColumns = false;
189
        }
190
191 16923
        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 16923
    public function isFullfilledBy(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 16923
        if (count($other->getColumns()) !== count($this->getColumns())) {
204 14525
            return false;
205
        }
206
207
        // Check if columns are the same, and even in the same order
208 16923
        $sameColumns = $this->spansColumns($other->getColumns());
209
210 16923
        if ($sameColumns) {
0 ignored issues
show
introduced by
The condition $sameColumns is always true.
Loading history...
211 16489
            if (! $this->samePartialIndex($other)) {
212 1725
                return false;
213
            }
214
215 16489
            if (! $this->hasSameColumnLengths($other)) {
216 1625
                return false;
217
            }
218
219 16489
            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 16171
                return true;
225
            }
226
227 16454
            if ($other->isPrimary() !== $this->isPrimary()) {
228 15145
                return false;
229
            }
230
231 16454
            return $other->isUnique() === $this->isUnique();
232
        }
233
234 16899
        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 1700
    public function overrules(Index $other)
243
    {
244 1700
        if ($other->isPrimary()) {
245
            return false;
246
        }
247
248 1700
        if ($this->isSimpleIndex() && $other->isUnique()) {
249
            return false;
250
        }
251
252 1700
        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 15203
    public function getFlags()
261
    {
262 15203
        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 15391
    public function addFlag($flag)
275
    {
276 15391
        $this->_flags[strtolower($flag)] = true;
277
278 15391
        return $this;
279
    }
280
281
    /**
282
     * Does this index have a specific flag?
283
     *
284
     * @param string $flag
285
     *
286
     * @return bool
287
     */
288 15955
    public function hasFlag($flag)
289
    {
290 15955
        return isset($this->_flags[strtolower($flag)]);
291
    }
292
293
    /**
294
     * Removes a flag.
295
     *
296
     * @param string $flag
297
     *
298
     * @return void
299
     */
300 1550
    public function removeFlag($flag)
301
    {
302 1550
        unset($this->_flags[strtolower($flag)]);
303 1550
    }
304
305
    /**
306
     * @param string $name
307
     *
308
     * @return bool
309
     */
310 17049
    public function hasOption($name)
311
    {
312 17049
        return isset($this->options[strtolower($name)]);
313
    }
314
315
    /**
316
     * @param string $name
317
     *
318
     * @return mixed
319
     */
320 15294
    public function getOption($name)
321
    {
322 15294
        return $this->options[strtolower($name)];
323
    }
324
325
    /**
326
     * @return mixed[]
327
     */
328 15155
    public function getOptions()
329
    {
330 15155
        return $this->options;
331
    }
332
333
    /**
334
     * Return whether the two indexes have the same partial index
335
     *
336
     * @return bool
337
     */
338 16489
    private function samePartialIndex(Index $other)
339
    {
340 16489
        if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where')) {
341 5763
            return true;
342
        }
343
344 16489
        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 16489
    private function hasSameColumnLengths(self $other) : bool
351
    {
352
        $filter = static function (?int $length) : bool {
353 16489
            return $length !== null;
354 16489
        };
355
356 16489
        return array_filter($this->options['lengths'] ?? [], $filter)
357 16489
            === array_filter($other->options['lengths'] ?? [], $filter);
358
    }
359
}
360