Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
94.44% covered (success)
94.44%
17 / 18
CRAP
99.03% covered (success)
99.03%
102 / 103
MySQLTableCreator
0.00% covered (danger)
0.00%
0 / 1
94.44% covered (success)
94.44%
17 / 18
40
99.03% covered (success)
99.03%
102 / 103
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 buildCreateTable
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 buildCreateTableForTable
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 buildCreateDelimitTable
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
8 / 8
 buildColumnSQL
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
8 / 8
 buildColumnSQLSnippet
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 buildPrimaryKey
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
8 / 8
 buildPrimaryKeyForDelimiter
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
5 / 5
 buildIndexList
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 buildIndex
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
10 / 10
 buildIndexPart
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 buildForeignConstraintList
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 buildForeignKeyConstraint
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
14 / 14
 buildEngineDefinition
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 buildCollateDefinition
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 buildCharsetDefinition
0.00% covered (danger)
0.00%
0 / 1
2.06
75.00% covered (success)
75.00%
3 / 4
 getDatabaseSpecific
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 quote
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
<?php
declare(strict_types = 1);
namespace Siesta\Driver\MySQL;
use Siesta\Driver\MySQL\MetaData\ConstraintRule;
use Siesta\Model\Attribute;
use Siesta\Model\DelimitAttributeList;
use Siesta\Model\Entity;
use Siesta\Model\Index;
use Siesta\Model\IndexPart;
use Siesta\Model\Reference;
use Siesta\Util\ArrayUtil;
/**
 * @author Gregor Müller
 */
class MySQLTableCreator
{
    const CREATE_TABLE_SNIPPET = "CREATE TABLE IF NOT EXISTS ";
    const COLUMN_SNIPPET = "%s %s %s";
    const PRIMARY_KEY_SNIPPET = ", PRIMARY KEY (%s)";
    const DEFAULT_CHARSET_SNIPPET = " DEFAULT CHARACTER SET %s ";
    const COLALTE_SNIPPET = " COLLATE ";
    const ENGINE_SNIPPET = " ENGINE = ";
    const FOREIGN_KEY_SNIPPET = ", CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) ON DELETE %s ON UPDATE %s";
    const MYSQL_ENGINE_ATTRIBUTE = "engine";
    const MYSQL_COLLATE_ATTRIBUTE = "collate";
    const MYSQL_CHARSET_ATTRIBUTE = "charset";
    const REFERENCE_OPTION_CASCADE = "CASCADE";
    const REFERENCE_OPTION_RESTRICT = "RESTRICT";
    const REFERENCE_OPTION_SET_NULL = "SET NULL";
    const REFERENCE_OPTION_NO_ACTION = "NO ACTION";
    const UNIQUE_SUFFIX = "_UNIQUE";
    const INDEX_SUFFIX = "_INDEX";
    const FOREIGN_KEY_SUFFIX = "_FOREIGN_KEY";
    const FOREIGN_KEY_INDEX_SUFFIX = "_FK_INDEX";
    /**
     * @var Entity
     */
    protected $entity;
    /**
     * @var bool
     */
    protected $replication;
    /**
     * @param Entity $entity
     */
    public function __construct(Entity $entity)
    {
        $this->entity = $entity;
    }
    /**
     * @return string[]
     */
    public function buildCreateTable()
    {
        $tableName = $this->entity->getTableName();
        $result = [
            $this->buildCreateTableForTable($tableName, false)
        ];
        if ($this->entity->getIsReplication()) {
            $result[] = $this->buildCreateTableForTable($this->entity->getReplicationTableName(), true);
        }
        return $result;
    }
    /**
     * @param string $tableName
     * @param bool $replication
     *
     * @return string
     */
    public function buildCreateTableForTable($tableName, $replication)
    {
        $sql = self::CREATE_TABLE_SNIPPET . $this->quote($tableName);
        $sql .= " (" . $this->buildColumnSQL();
        $sql .= $this->buildPrimaryKey();
        $sql .= $this->buildIndexList();
        $sql .= $this->buildForeignConstraintList() . ")";
        $sql .= $this->buildEngineDefinition($replication);
        $sql .= $this->buildCollateDefinition();
        $sql .= $this->buildCharsetDefinition();
        return $sql;
    }
    /**
     * @return string
     */
    public function buildCreateDelimitTable()
    {
        $delimiterAttributes = DelimitAttributeList::getDelimitAttributes($this->entity);
        $sql = self::CREATE_TABLE_SNIPPET . $this->quote($this->entity->getDelimitTableName());
        $sql .= "(" . $this->buildColumnSQL($delimiterAttributes);
        $sql .= $this->buildPrimaryKeyForDelimiter($delimiterAttributes) . ")";
        $sql .= $this->buildEngineDefinition();
        $sql .= $this->buildCollateDefinition();
        $sql .= $this->buildCharsetDefinition();
        return $sql;
    }
    /**
     * @param Attribute[] $additionalColumns
     *
     * @return string
     */
    private function buildColumnSQL(array $additionalColumns = []) : string
    {
        $columnList = [];
        // used for delimiter functionality
        foreach ($additionalColumns as $attribute) {
            if (!$attribute->getIsTransient()) {
                $columnList[] = $this->buildColumnSQLSnippet($attribute);
            }
        }
        // iterate all attributes
        foreach ($this->entity->getAttributeList() as $attribute) {
            if (!$attribute->getIsTransient()) {
                $columnList[] = $this->buildColumnSQLSnippet($attribute);
            }
        }
        return implode(", ", $columnList);
    }
    /**
     * @param Attribute $attribute
     *
     * @return string
     */
    private function buildColumnSQLSnippet(Attribute $attribute) : string
    {
        $not = ($attribute->getIsRequired()) ? "NOT NULL" : "NULL";
        $attributeName = $this->quote($attribute->getDBName());
        $attributeType = $attribute->getDbType();
        return sprintf(self::COLUMN_SNIPPET, $attributeName, $attributeType, $not);
    }
    /**
     * @return string
     */
    private function buildPrimaryKey() : string
    {
        $pkColumnList = [];
        foreach ($this->entity->getAttributeList() as $attribute) {
            if (!$attribute->getIsPrimaryKey()) {
                continue;
            }
            $pkColumnList[] = $this->quote($attribute->getDBName());
        }
        if (sizeof($pkColumnList) === 0) {
            return "";
        }
        return sprintf(self::PRIMARY_KEY_SNIPPET, implode(",", $pkColumnList));
    }
    /**
     * @param Attribute[] $delimiterAttributes
     *
     * @return string
     */
    private function buildPrimaryKeyForDelimiter(array $delimiterAttributes)
    {
        $sqlColumnList = [];
        foreach ($delimiterAttributes as $attribute) {
            if ($attribute->getIsPrimaryKey()) {
                $sqlColumnList[] = $this->quote($attribute->getDBName());
            }
        }
        return sprintf(self::PRIMARY_KEY_SNIPPET, implode(",", $sqlColumnList));
    }
    /**
     * @return string
     */
    private function buildIndexList() : string
    {
        $sql = "";
        foreach ($this->entity->getIndexList() as $index) {
            $sql .= "," . $this->buildIndex($index);
        }
        return $sql;
    }
    /**
     * @param Index $index
     *
     * @return string
     */
    private function buildIndex(Index $index)
    {
        // check if unique index or index
        $sql = $index->getIsUnique() ? " UNIQUE INDEX " : " INDEX ";
        // add index name
        $sql .= $this->quote($index->getName());
        // check if an index type has been set
        $indexType = $index->getIndexType();
        if ($indexType !== null) {
            $sql .= " USING " . $indexType;
        }
        // open columns
        $indexPartList = [];
        foreach ($index->getIndexPartList() as $indexPart) {
            $indexPartList[] = $this->buildIndexPart($indexPart);
        }
        $sql .= " (" . implode(", ", $indexPartList) . ")";
        return $sql;
    }
    /**
     * @param IndexPart $indexPart
     *
     * @return string
     */
    private function buildIndexPart(IndexPart $indexPart)
    {
        $sql = $this->quote($indexPart->getColumnName());
        if ($indexPart->getLength()) {
            $sql .= " (" . $indexPart->getLength() . ")";
        }
        $sql .= " " . $indexPart->getSortOrder();
        return $sql;
    }
    /**
     * @return string
     */
    private function buildForeignConstraintList() : string
    {
        $sql = "";
        foreach ($this->entity->getReferenceList() as $reference) {
            $sql .= $this->buildForeignKeyConstraint($reference);
        }
        return $sql;
    }
    /**
     * @param Reference $reference
     *
     * @return string
     */
    private function buildForeignKeyConstraint(Reference $reference) : string
    {
        $columnList = [];
        $foreignColumnList = [];
        foreach ($reference->getReferenceMappingList() as $referenceMapping) {
            $foreignAttribute = $referenceMapping->getForeignAttribute();
            $localAttribute = $referenceMapping->getLocalAttribute();
            $columnList[] = $this->quote($localAttribute->getDBName());
            $foreignColumnList[] = $this->quote($foreignAttribute->getDBName());
        }
        $constraintName = $this->quote($reference->getConstraintName());
        $columnNames = implode(",", $columnList);
        $foreignTable = $this->quote($reference->getForeignTable());
        $foreignColumNames = implode(",", $foreignColumnList);
        $onDelete = ConstraintRule::schemaToMySQL($reference->getOnDelete());
        $onUpdate = ConstraintRule::schemaToMySQL($reference->getOnUpdate());
        return sprintf(self::FOREIGN_KEY_SNIPPET, $constraintName, $columnNames, $foreignTable, $foreignColumNames, $onDelete, $onUpdate);
    }
    /**
     * @param bool $replication
     *
     * @return string
     */
    private function buildEngineDefinition($replication = false)
    {
        if ($replication) {
            return self::ENGINE_SNIPPET . "MEMORY";
        }
        $engine = $this->getDatabaseSpecific(self::MYSQL_ENGINE_ATTRIBUTE);
        if ($engine !== null) {
            return self::ENGINE_SNIPPET . $engine;
        }
        return "";
    }
    /**
     * @return string
     */
    private function buildCollateDefinition()
    {
        $collate = $this->getDatabaseSpecific(self::MYSQL_COLLATE_ATTRIBUTE);
        if ($collate !== null) {
            return self::COLALTE_SNIPPET . $collate;
        }
        return "";
    }
    /**
     * @return string
     */
    private function buildCharsetDefinition() : string
    {
        $charset = $this->getDatabaseSpecific(self::MYSQL_CHARSET_ATTRIBUTE);
        if ($charset !== null) {
            return sprintf(self::DEFAULT_CHARSET_SNIPPET, $charset);
        }
        return "";
    }
    /**
     * @param string $key
     *
     * @return string
     */
    private function getDatabaseSpecific(string $key)
    {
        $dbSpecific = $this->entity->getDatabaseSpecificAttributeList(MySQLDriver::MYSQL_DRIVER_NAME);
        return ArrayUtil::getFromArray($dbSpecific, $key);
    }
    /**
     * @param string $name
     *
     * @return string
     */
    private function quote($name)
    {
        return MySQLDriver::quote($name);
    }
}