Identifier) * * @var Identifier[] */ protected $_columns = []; /** @var bool */ protected $_isUnique = false; /** @var bool */ protected $_isPrimary = false; /** * Platform specific flags for indexes. * array($flagName => true) * * @var true[] */ protected $_flags = []; /** * Platform specific options * * @todo $_flags should eventually be refactored into options * @var mixed[] */ private $options = []; /** * @param string $name * @param string[] $columns * @param bool $isUnique * @param bool $isPrimary * @param string[] $flags * @param mixed[] $options */ public function __construct( $name, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [] ) { $isUnique = $isUnique || $isPrimary; $this->_setName($name); $this->_isUnique = $isUnique; $this->_isPrimary = $isPrimary; $this->options = $options; foreach ($columns as $column) { $this->_addColumn($column); } foreach ($flags as $flag) { $this->addFlag($flag); } } /** * @param string $column * * @return void * * @throws InvalidArgumentException */ protected function _addColumn($column) { if (! is_string($column)) { throw new InvalidArgumentException('Expecting a string as Index Column'); } $this->_columns[$column] = new Identifier($column); } /** * {@inheritdoc} */ public function getColumns() { return array_keys($this->_columns); } /** * {@inheritdoc} */ public function getQuotedColumns(AbstractPlatform $platform) { $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') ? $this->getOption('lengths') : []; $columns = []; foreach ($this->_columns as $column) { $length = array_shift($subParts); $quotedColumn = $column->getQuotedName($platform); if ($length !== null) { $quotedColumn .= '(' . $length . ')'; } $columns[] = $quotedColumn; } return $columns; } /** * @return string[] */ public function getUnquotedColumns() { return array_map([$this, 'trimQuotes'], $this->getColumns()); } /** * Is the index neither unique nor primary key? * * @return bool */ public function isSimpleIndex() { return ! $this->_isPrimary && ! $this->_isUnique; } /** * @return bool */ public function isUnique() { return $this->_isUnique; } /** * @return bool */ public function isPrimary() { return $this->_isPrimary; } /** * @param string $name * @param int $pos * * @return bool */ public function hasColumnAtPosition($name, $pos = 0) { $name = $this->trimQuotes(strtolower($name)); $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); return array_search($name, $indexColumns) === $pos; } /** * Checks if this index exactly spans the given column names in the correct order. * * @param string[] $columnNames * * @return bool */ public function spansColumns(array $columnNames) { $columns = $this->getColumns(); $numberOfColumns = count($columns); $sameColumns = true; for ($i = 0; $i < $numberOfColumns; $i++) { if ( isset($columnNames[$i]) && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i])) ) { continue; } $sameColumns = false; } return $sameColumns; } /** * Checks if the other index already fulfills all the indexing and constraint needs of the current one. * * @return bool */ 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) if (count($other->getColumns()) !== count($this->getColumns())) { return false; } // Check if columns are the same, and even in the same order $sameColumns = $this->spansColumns($other->getColumns()); if ($sameColumns) { if (! $this->samePartialIndex($other)) { return false; } if (! $this->hasSameColumnLengths($other)) { return false; } if (! $this->isUnique() && ! $this->isPrimary()) { // this is a special case: If the current key is neither primary or unique, any unique or // primary key will always have the same effect for the index and there cannot be any constraint // overlaps. This means a primary or unique index can always fulfill the requirements of just an // index that has no constraints. return true; } if ($other->isPrimary() !== $this->isPrimary()) { return false; } return $other->isUnique() === $this->isUnique(); } return false; } /** * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. * * @return bool */ public function overrules(Index $other) { if ($other->isPrimary()) { return false; } if ($this->isSimpleIndex() && $other->isUnique()) { return false; } return $this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other); } /** * Returns platform specific flags for indexes. * * @return string[] */ public function getFlags() { return array_keys($this->_flags); } /** * Adds Flag for an index that translates to platform specific handling. * * @param string $flag * * @return Index * * @example $index->addFlag('CLUSTERED') */ public function addFlag($flag) { $this->_flags[strtolower($flag)] = true; return $this; } /** * Does this index have a specific flag? * * @param string $flag * * @return bool */ public function hasFlag($flag) { return isset($this->_flags[strtolower($flag)]); } /** * Removes a flag. * * @param string $flag * * @return void */ public function removeFlag($flag) { unset($this->_flags[strtolower($flag)]); } /** * @param string $name * * @return bool */ public function hasOption($name) { return isset($this->options[strtolower($name)]); } /** * @param string $name * * @return mixed */ public function getOption($name) { return $this->options[strtolower($name)]; } /** * @return mixed[] */ public function getOptions() { return $this->options; } /** * Return whether the two indexes have the same partial index * * @return bool */ private function samePartialIndex(Index $other) { if ( $this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') === $other->getOption('where') ) { return true; } return ! $this->hasOption('where') && ! $other->hasOption('where'); } /** * Returns whether the index has the same column lengths as the other */ private function hasSameColumnLengths(self $other): bool { $filter = static function (?int $length): bool { return $length !== null; }; return array_filter($this->options['lengths'] ?? [], $filter) === array_filter($other->options['lengths'] ?? [], $filter); } }