<?php
namespace XF\Db\Schema;
use function is_array;
class Index extends AbstractDefinition
{
protected $type;
protected $columns;
protected $using;
protected $autoNamed = false;
protected function init()
{
if ($this->isAlter() && !$this->existingDefinition && $this->name)
{
/** @var Alter $ddl */
$ddl = $this->ddl;
$indexName = (strtolower($this->name) == 'primary' ? 'PRIMARY' : $this->name);
$this->existingDefinition = $ddl->getIndexDefinition($indexName);
}
}
public function type($type)
{
$type = strtolower($type);
switch ($type)
{
case 'primary':
case 'unique':
case 'key':
case 'fulltext':
$this->type = $type;
break;
default:
throw new \InvalidArgumentException("Index definition type $type is not valid.");
}
return $this;
}
public function name($indexName)
{
$this->name = $indexName;
$this->existingDefinition = null;
return $this;
}
public function columns($columns)
{
if (!is_array($columns))
{
$columns = [$columns];
}
$this->columns = $columns;
if ($this->autoNamed)
{
$this->name = null;
}
return $this;
}
public function addColumn($columnName)
{
$this->columns[] = $columnName;
return $this;
}
public function using($using = null)
{
if ($using === null)
{
$using = 'BTREE';
}
$this->using = $using;
return $this;
}
public function drop()
{
parent::drop();
if ($this->isAlter())
{
if (!$this->name)
{
throw new \LogicException("Must have an index name before dropping");
}
$this->syncExistingDefinition();
/** @var Alter $ddl */
$ddl = $this->ddl;
$ddl->forgetIndex($this->name);
}
}
public function getIndexName()
{
$this->syncIndexName();
return $this->name;
}
protected function syncIndexName()
{
$name = $this->name;
if ($this->type == 'primary')
{
$this->name = 'PRIMARY';
}
else if (!$this->name)
{
$columnNames = [];
foreach ($this->columns AS $column)
{
if (is_array($column))
{
$columnNames[] = $column[0];
}
else
{
$columnNames[] = $column;
}
}
$this->name = implode('_', $columnNames);
$this->autoNamed = true;
}
if ($name !== $this->name)
{
$this->existingDefinition = null;
}
}
protected function syncExistingDefinition($force = false)
{
$this->syncIndexName();
if ($this->isAlter() && (!$this->existingDefinition || $force))
{
/** @var Alter $ddl */
$ddl = $this->ddl;
$this->existingDefinition = $ddl->getIndexDefinition($this->name);
}
}
public function getDefinition($change = false)
{
$this->syncExistingDefinition();
$type = $this->type;
$indexName = $this->getIndexName();
$columns = $this->columns;
/** @var Alter|Create $ddl */
$ddl = $this->ddl;
$definition = '';
if ($this->drop)
{
if (!$this->existingDefinition)
{
return '';
}
else if ($type == 'primary')
{
return "DROP PRIMARY KEY";
}
else
{
return "DROP INDEX `$indexName`";
}
}
if ($this->isAlter() && $this->existingDefinition)
{
$conflictRenames = $ddl->getConflictRenames();
foreach ($this->columns AS $column)
{
if (is_array($column))
{
$column = $column[0];
}
if (isset($conflictRenames[$column]))
{
$this->syncExistingDefinition(true);
break;
}
}
}
if ($this->isAlter() && $this->existingDefinition)
{
// confirm whether we've changed anything in this index
$existing = new static($this->db, $ddl, $indexName, $this->existingDefinition);
$existing->setDefinition();
if ($this->compare($existing))
{
// new index is identical to existing index so skip it
return '';
}
// otherwise, we need to drop the old one
if ($existing->type == 'primary')
{
$definition .= "DROP PRIMARY KEY";
}
else
{
$definition .= "DROP INDEX `$indexName`";
}
}
if ($this->isAlter() && !$this->existingDefinition && $change)
{
throw new \InvalidArgumentException("Index definition '{$indexName}' does not exist therefore it cannot be changed.");
}
if (!$columns)
{
throw new \InvalidArgumentException("Index definition must include columns.");
}
if ($definition)
{
$definition .= ",\n";
}
if ($this->isAlter())
{
$definition .= 'ADD ';
}
$definition .= $this->getIndexDefinitionSql();
return $definition;
}
protected function getIndexDefinitionSql()
{
$type = $this->type;
$indexName = $this->getIndexName();
$columns = $this->columns;
$using = $this->using;
$definition = '';
$columnsString = $this->getColumnsString($columns);
switch ($type)
{
case 'primary':
$definition .= 'PRIMARY KEY (' . $columnsString . ')';
break;
case 'unique':
case 'key':
case 'fulltext':
$typePrefix = '';
if ($type == 'unique')
{
$typePrefix = 'UNIQUE ';
}
else if ($type == 'fulltext')
{
$typePrefix = 'FULLTEXT ';
}
$key = $typePrefix . "KEY `$indexName` ($columnsString)";
if ($using)
{
$key .= ' USING ' . strtoupper($using);
}
$definition .= $key;
break;
}
return $definition;
}
public function setDefinition()
{
if (!$this->isAlter())
{
return;
}
$definition = $this->existingDefinition;
if (!$definition)
{
return;
}
$this->setupFromExistingDefinition($definition);
}
public function setupFromExistingDefinition(array $indexColumns)
{
$type = null;
$columns = [];
foreach ($indexColumns AS $key => $index)
{
if ($type === null)
{
if (strtolower($index['Key_name']) == 'primary')
{
$type = 'primary';
}
else if ($index['Non_unique'] == 1)
{
$type = 'key';
}
else
{
$type = 'unique';
}
}
if ($index['Sub_part'] === null)
{
$column = $index['Column_name'];
}
else
{
$column = [$index['Column_name'], $index['Sub_part']];
}
$columns[] = $column;
}
$this->type($type);
$this->columns($columns);
// name should already be set
}
public function resetDefinition() { }
protected function getColumnsString(array $columns)
{
$output = [];
foreach ($columns AS $column)
{
if (is_array($column))
{
if (!empty($column[1]))
{
$sql = "`$column[0]`($column[1])";
}
else
{
$sql = "`$column[0]`";
}
if (!empty($column['descending']))
{
$sql .= ' DESC';
}
$output[] = $sql;
}
else
{
$output[] = "`$column`";
}
}
return implode(', ', $output);
}
public function getComparisonValue()
{
return 'index: ' . $this->getIndexDefinitionSql();
}
}