Index.php 8.6 KB

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