Index.php 8.08 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Schema;

5
use Doctrine\DBAL\Platforms\AbstractPlatform;
6
use InvalidArgumentException;
7 8 9
use function array_keys;
use function array_map;
use function array_search;
10
use function array_shift;
11 12 13
use function count;
use function is_string;
use function strtolower;
14

15
class Index extends AbstractAsset implements Constraint
16 17
{
    /**
18 19 20 21
     * Asset identifier instances of the column names the index is associated with.
     * array($columnName => Identifier)
     *
     * @var Identifier[]
22
     */
23
    protected $_columns = [];
24

25
    /** @var bool */
26 27
    protected $_isUnique = false;

28
    /** @var bool */
29 30
    protected $_isPrimary = false;

31 32
    /**
     * Platform specific flags for indexes.
33
     * array($flagName => true)
34
     *
35
     * @var true[]
36
     */
37
    protected $_flags = [];
38

39
    /**
40
     * Platform specific options
41
     *
42
     * @todo $_flags should eventually be refactored into options
43
     * @var mixed[]
44
     */
45
    private $options = [];
46

47
    /**
48 49
     * @param string   $indexName
     * @param string[] $columns
50 51
     * @param bool     $isUnique
     * @param bool     $isPrimary
52
     * @param string[] $flags
53
     * @param mixed[]  $options
54
     */
55
    public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [])
56
    {
57
        $isUnique = $isUnique || $isPrimary;
58

59
        $this->_setName($indexName);
60
        $this->_isUnique  = $isUnique;
61
        $this->_isPrimary = $isPrimary;
62
        $this->options    = $options;
63

64
        foreach ($columns as $column) {
65 66
            $this->_addColumn($column);
        }
67 68 69
        foreach ($flags as $flag) {
            $this->addFlag($flag);
        }
70 71 72 73
    }

    /**
     * @param string $column
Benjamin Morel's avatar
Benjamin Morel committed
74 75 76
     *
     * @return void
     *
77
     * @throws InvalidArgumentException
78 79 80
     */
    protected function _addColumn($column)
    {
81 82
        if (! is_string($column)) {
            throw new InvalidArgumentException('Expecting a string as Index Column');
83
        }
84 85

        $this->_columns[$column] = new Identifier($column);
86 87 88
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
89
     * {@inheritdoc}
90 91 92
     */
    public function getColumns()
    {
93 94 95 96
        return array_keys($this->_columns);
    }

    /**
97
     * {@inheritdoc}
98 99 100
     */
    public function getQuotedColumns(AbstractPlatform $platform)
    {
101 102 103
        $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths')
            ? $this->getOption('lengths') : [];

104
        $columns = [];
105 106

        foreach ($this->_columns as $column) {
107 108 109 110 111 112 113 114 115
            $length = array_shift($subParts);

            $quotedColumn = $column->getQuotedName($platform);

            if ($length !== null) {
                $quotedColumn .= '(' . $length . ')';
            }

            $columns[] = $quotedColumn;
116 117 118
        }

        return $columns;
119
    }
120

121
    /**
122
     * @return string[]
123 124 125
     */
    public function getUnquotedColumns()
    {
126
        return array_map([$this, 'trimQuotes'], $this->getColumns());
127 128
    }

129 130
    /**
     * Is the index neither unique nor primary key?
131
     *
132
     * @return bool
133 134 135
     */
    public function isSimpleIndex()
    {
136
        return ! $this->_isPrimary && ! $this->_isUnique;
137
    }
138 139

    /**
140
     * @return bool
141 142 143 144 145 146 147
     */
    public function isUnique()
    {
        return $this->_isUnique;
    }

    /**
148
     * @return bool
149 150 151 152 153
     */
    public function isPrimary()
    {
        return $this->_isPrimary;
    }
154 155

    /**
156 157
     * @param string $columnName
     * @param int    $pos
Benjamin Morel's avatar
Benjamin Morel committed
158
     *
159
     * @return bool
160
     */
161
    public function hasColumnAtPosition($columnName, $pos = 0)
162
    {
163 164
        $columnName   = $this->trimQuotes(strtolower($columnName));
        $indexColumns = array_map('strtolower', $this->getUnquotedColumns());
Benjamin Morel's avatar
Benjamin Morel committed
165

166
        return array_search($columnName, $indexColumns) === $pos;
167
    }
168 169

    /**
Benjamin Morel's avatar
Benjamin Morel committed
170
     * Checks if this index exactly spans the given column names in the correct order.
171
     *
172
     * @param string[] $columnNames
Benjamin Morel's avatar
Benjamin Morel committed
173
     *
174
     * @return bool
175 176 177
     */
    public function spansColumns(array $columnNames)
    {
Steve Müller's avatar
Steve Müller committed
178
        $columns         = $this->getColumns();
179
        $numberOfColumns = count($columns);
Steve Müller's avatar
Steve Müller committed
180 181
        $sameColumns     = true;

182
        for ($i = 0; $i < $numberOfColumns; $i++) {
183 184
            if (isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i]))) {
                continue;
185
            }
186 187

            $sameColumns = false;
188
        }
Steve Müller's avatar
Steve Müller committed
189

190 191 192 193
        return $sameColumns;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
194 195
     * Checks if the other index already fulfills all the indexing and constraint needs of the current one.
     *
196
     * @return bool
197 198 199 200 201
     */
    public function isFullfilledBy(Index $other)
    {
        // allow the other index to be equally large only. It being larger is an option
        // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo)
202
        if (count($other->getColumns()) !== count($this->getColumns())) {
203 204 205 206 207 208 209
            return false;
        }

        // Check if columns are the same, and even in the same order
        $sameColumns = $this->spansColumns($other->getColumns());

        if ($sameColumns) {
210
            if (! $this->samePartialIndex($other)) {
211
                return false;
212 213
            }

214
            if (! $this->isUnique() && ! $this->isPrimary()) {
Possum's avatar
Possum committed
215
                // this is a special case: If the current key is neither primary or unique, any unique or
216
                // primary key will always have the same effect for the index and there cannot be any constraint
Pascal Borreli's avatar
Pascal Borreli committed
217
                // overlaps. This means a primary or unique index can always fulfill the requirements of just an
218 219
                // index that has no constraints.
                return true;
220 221
            }

222
            if ($other->isPrimary() !== $this->isPrimary()) {
223
                return false;
224 225
            }

Gabriel Caruso's avatar
Gabriel Caruso committed
226
            return $other->isUnique() === $this->isUnique();
227
        }
Benjamin Morel's avatar
Benjamin Morel committed
228

229 230 231 232
        return false;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
233 234
     * Detects if the other index is a non-unique, non primary index that can be overwritten by this one.
     *
235
     * @return bool
236 237 238
     */
    public function overrules(Index $other)
    {
239 240
        if ($other->isPrimary()) {
            return false;
Steve Müller's avatar
Steve Müller committed
241
        } elseif ($this->isSimpleIndex() && $other->isUnique()) {
242 243 244
            return false;
        }

Gabriel Caruso's avatar
Gabriel Caruso committed
245
        return $this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other);
246
    }
247

248 249 250
    /**
     * Returns platform specific flags for indexes.
     *
251
     * @return string[]
252 253 254 255 256 257
     */
    public function getFlags()
    {
        return array_keys($this->_flags);
    }

258
    /**
Benjamin Morel's avatar
Benjamin Morel committed
259
     * Adds Flag for an index that translates to platform specific handling.
260 261
     *
     * @param string $flag
Benjamin Morel's avatar
Benjamin Morel committed
262
     *
263
     * @return Index
264 265
     *
     * @example $index->addFlag('CLUSTERED')
266 267 268
     */
    public function addFlag($flag)
    {
Steve Müller's avatar
Steve Müller committed
269
        $this->_flags[strtolower($flag)] = true;
Benjamin Morel's avatar
Benjamin Morel committed
270

271 272 273 274 275 276 277
        return $this;
    }

    /**
     * Does this index have a specific flag?
     *
     * @param string $flag
Benjamin Morel's avatar
Benjamin Morel committed
278
     *
279
     * @return bool
280 281 282
     */
    public function hasFlag($flag)
    {
Steve Müller's avatar
Steve Müller committed
283
        return isset($this->_flags[strtolower($flag)]);
284 285 286
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
287
     * Removes a flag.
288 289
     *
     * @param string $flag
Benjamin Morel's avatar
Benjamin Morel committed
290
     *
291 292 293 294
     * @return void
     */
    public function removeFlag($flag)
    {
Steve Müller's avatar
Steve Müller committed
295
        unset($this->_flags[strtolower($flag)]);
296
    }
297 298

    /**
299 300
     * @param string $name
     *
301
     * @return bool
302 303 304
     */
    public function hasOption($name)
    {
305
        return isset($this->options[strtolower($name)]);
306 307 308 309 310 311
    }

    /**
     * @param string $name
     *
     * @return mixed
312
     */
313
    public function getOption($name)
314
    {
315
        return $this->options[strtolower($name)];
316
    }
317 318

    /**
319
     * @return mixed[]
320 321 322 323 324 325 326 327
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Return whether the two indexes have the same partial index
328
     *
329
     * @return bool
330 331 332
     */
    private function samePartialIndex(Index $other)
    {
333
        if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where')) {
334
            return true;
335 336
        }

Gabriel Caruso's avatar
Gabriel Caruso committed
337
        return ! $this->hasOption('where') && ! $other->hasOption('where');
338
    }
339
}