sintonia/library/propel/generator/lib/builder/sql/mysql/MysqlDDLBuilder.php

414 lines
13 KiB
PHP
Raw Blame History

<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'builder/sql/DDLBuilder.php';
/**
* DDL Builder class for MySQL.
*
* @author David Z<>lke
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.generator.builder.sql.mysql
*/
class MysqlDDLBuilder extends DDLBuilder
{
/**
* Returns some header SQL that disables foreign key checking.
* @return string DDL
*/
public static function getDatabaseStartDDL()
{
$ddl = "
# This is a fix for InnoDB in MySQL >= 4.1.x
# It \"suspends judgement\" for fkey relationships until are tables are set.
SET FOREIGN_KEY_CHECKS = 0;
";
return $ddl;
}
/**
* Returns some footer SQL that re-enables foreign key checking.
* @return string DDL
*/
public static function getDatabaseEndDDL()
{
$ddl = "
# This restores the fkey checks, after having unset them earlier
SET FOREIGN_KEY_CHECKS = 1;
";
return $ddl;
}
/**
*
* @see parent::addDropStatement()
*/
protected function addDropStatements(&$script)
{
$script .= "
DROP TABLE IF EXISTS ".$this->quoteIdentifier($this->getTable()->getName()).";
";
}
/**
* Builds the SQL for current table and returns it as a string.
*
* This is the main entry point and defines a basic structure that classes should follow.
* In most cases this method will not need to be overridden by subclasses.
*
* @return string The resulting SQL DDL.
*/
public function build()
{
$script = "";
$this->addTable($script);
return $script;
}
/**
*
* @see parent::addColumns()
*/
protected function addTable(&$script)
{
$table = $this->getTable();
$platform = $this->getPlatform();
$script .= "
#-----------------------------------------------------------------------------
#-- ".$table->getName()."
#-----------------------------------------------------------------------------
";
$this->addDropStatements($script);
$script .= "
CREATE TABLE ".$this->quoteIdentifier($table->getName())."
(
";
$lines = array();
$databaseType = $this->getPlatform()->getDatabaseType();
foreach ($table->getColumns() as $col) {
$entry = $this->getColumnDDL($col);
$colinfo = $col->getVendorInfoForType($databaseType);
if ( $colinfo->hasParameter('Charset') ) {
$entry .= ' CHARACTER SET '.$platform->quote($colinfo->getParameter('Charset'));
}
if ( $colinfo->hasParameter('Collation') ) {
$entry .= ' COLLATE '.$platform->quote($colinfo->getParameter('Collation'));
} elseif ( $colinfo->hasParameter('Collate') ) {
$entry .= ' COLLATE '.$platform->quote($colinfo->getParameter('Collate'));
}
if ($col->getDescription()) {
$entry .= " COMMENT ".$platform->quote($col->getDescription());
}
$lines[] = $entry;
}
if ($table->hasPrimaryKey()) {
$lines[] = "PRIMARY KEY (".$this->getColumnList($table->getPrimaryKey()).")";
}
$this->addIndicesLines($lines);
$this->addForeignKeysLines($lines);
$sep = ",
";
$script .= implode($sep, $lines);
$script .= "
)";
$vendorSpecific = $table->getVendorInfoForType($this->getPlatform()->getDatabaseType());
if ($vendorSpecific->hasParameter('Type')) {
$mysqlTableType = $vendorSpecific->getParameter('Type');
} elseif ($vendorSpecific->hasParameter('Engine')) {
$mysqlTableType = $vendorSpecific->getParameter('Engine');
} else {
$mysqlTableType = $this->getBuildProperty("mysqlTableType");
}
$script .= sprintf(' %s=%s', $this->getBuildProperty("mysqlTableEngineKeyword"), $mysqlTableType);
$dbVendorSpecific = $table->getDatabase()->getVendorInfoForType($databaseType);
$tableVendorSpecific = $table->getVendorInfoForType($databaseType);
$vendorSpecific = $dbVendorSpecific->getMergedVendorInfo($tableVendorSpecific);
if ( $vendorSpecific->hasParameter('Charset') ) {
$script .= ' CHARACTER SET '.$platform->quote($vendorSpecific->getParameter('Charset'));
}
if ( $vendorSpecific->hasParameter('Collate') ) {
$script .= ' COLLATE '.$platform->quote($vendorSpecific->getParameter('Collate'));
}
if ( $vendorSpecific->hasParameter('Checksum') ) {
$script .= ' CHECKSUM='.$platform->quote($vendorSpecific->getParameter('Checksum'));
}
if ( $vendorSpecific->hasParameter('Pack_Keys') ) {
$script .= ' PACK_KEYS='.$platform->quote($vendorSpecific->getParameter('Pack_Keys'));
}
if ( $vendorSpecific->hasParameter('Delay_key_write') ) {
$script .= ' DELAY_KEY_WRITE='.$platform->quote($vendorSpecific->getParameter('Delay_key_write'));
}
if ($table->getDescription()) {
$script .= " COMMENT=".$platform->quote($table->getDescription());
}
$script .= ";
";
}
/**
* Creates a comma-separated list of column names for the index.
* For MySQL unique indexes there is the option of specifying size, so we cannot simply use
* the getColumnsList() method.
* @param Index $index
* @return string
*/
private function getIndexColumnList(Index $index)
{
$platform = $this->getPlatform();
$cols = $index->getColumns();
$list = array();
foreach ($cols as $col) {
$list[] = $this->quoteIdentifier($col) . ($index->hasColumnSize($col) ? '(' . $index->getColumnSize($col) . ')' : '');
}
return implode(', ', $list);
}
/**
* Adds indexes
*/
protected function addIndicesLines(&$lines)
{
$table = $this->getTable();
$platform = $this->getPlatform();
foreach ($table->getUnices() as $unique) {
$lines[] = "UNIQUE KEY ".$this->quoteIdentifier($unique->getName())." (".$this->getIndexColumnList($unique).")";
}
foreach ($table->getIndices() as $index ) {
$vendorInfo = $index->getVendorInfoForType($platform->getDatabaseType());
$lines[] .= (($vendorInfo && $vendorInfo->getParameter('Index_type') == 'FULLTEXT') ? 'FULLTEXT ' : '') . "KEY " . $this->quoteIdentifier($index->getName()) . "(" . $this->getIndexColumnList($index) . ")";
}
}
/**
* Adds foreign key declarations & necessary indexes for mysql (if they don't exist already).
* @see parent::addForeignKeys()
*/
protected function addForeignKeysLines(&$lines)
{
$table = $this->getTable();
$platform = $this->getPlatform();
/**
* A collection of indexed columns. The keys is the column name
* (concatenated with a comma in the case of multi-col index), the value is
* an array with the names of the indexes that index these columns. We use
* it to determine which additional indexes must be created for foreign
* keys. It could also be used to detect duplicate indexes, but this is not
* implemented yet.
* @var array
*/
$_indices = array();
$this->collectIndexedColumns('PRIMARY', $table->getPrimaryKey(), $_indices, 'getName');
$_tableIndices = array_merge($table->getIndices(), $table->getUnices());
foreach ($_tableIndices as $_index) {
$this->collectIndexedColumns($_index->getName(), $_index->getColumns(), $_indices);
}
// we're determining which tables have foreign keys that point to this table, since MySQL needs an index on
// any column that is referenced by another table (yep, MySQL _is_ a PITA)
$counter = 0;
$allTables = $table->getDatabase()->getTables();
foreach ($allTables as $_table) {
foreach ($_table->getForeignKeys() as $_foreignKey) {
if ($_foreignKey->getForeignTableName() == $table->getName()) {
$referencedColumns = $_foreignKey->getForeignColumns();
$referencedColumnsHash = $this->getColumnList($referencedColumns);
if (!array_key_exists($referencedColumnsHash, $_indices)) {
// no matching index defined in the schema, so we have to create one
$indexName = "I_referenced_".$_foreignKey->getName()."_".(++$counter);
$lines[] = "INDEX ".$this->quoteIdentifier($indexName)." (" .$referencedColumnsHash.")";
// Add this new index to our collection, otherwise we might add it again (bug #725)
$this->collectIndexedColumns($indexName, $referencedColumns, $_indices);
}
}
}
}
foreach ($table->getForeignKeys() as $fk) {
$indexName = $this->quoteIdentifier(substr_replace($fk->getName(), 'FI_', strrpos($fk->getName(), 'FK_'), 3));
$localColumns = $fk->getLocalColumns();
$localColumnsHash = $this->getColumnList($localColumns);
if (!array_key_exists($localColumnsHash, $_indices)) {
// no matching index defined in the schema, so we have to create one. MySQL needs indices on any columns that serve as foreign keys. these are not auto-created prior to 4.1.2
$lines[] = "INDEX $indexName (".$localColumnsHash.")";
$this->collectIndexedColumns($indexName, $localColumns, $_indices);
}
$str = "CONSTRAINT ".$this->quoteIdentifier($fk->getName())."
FOREIGN KEY (".$this->getColumnList($fk->getLocalColumns()).")
REFERENCES ".$this->quoteIdentifier($fk->getForeignTableName()) . " (".$this->getColumnList($fk->getForeignColumns()).")";
if ($fk->hasOnUpdate()) {
$str .= "
ON UPDATE ".$fk->getOnUpdate();
}
if ($fk->hasOnDelete()) {
$str .= "
ON DELETE ".$fk->getOnDelete();
}
$lines[] = $str;
}
}
/**
* Helper function to collect indexed columns.
* @param array $columns The column names, or objects with a $callback method
* @param array $indexedColumns The collected indexes
* @param string $callback The name of a method to call on each of $columns to get the column name, if needed.
* @return unknown_type
*/
private function collectIndexedColumns($indexName, $columns, &$collectedIndexes, $callback = null)
{
// Get the actual column names, using the callback if needed.
// DDLBuilder::getColumnList tests $col instanceof Column, and no callback - maybe we should too?
$colnames = $columns;
if ($callback) {
$colnames = array();
foreach ($columns as $col) {
$colnames[] = $col->$callback();
}
}
/**
* "If the table has a multiple-column index, any leftmost prefix of the
* index can be used by the optimizer to find rows. For example, if you
* have a three-column index on (col1, col2, col3), you have indexed search
* capabilities on (col1), (col1, col2), and (col1, col2, col3)."
* @link http://dev.mysql.com/doc/refman/5.5/en/mysql-indexes.html
*/
$indexedColumns = array();
foreach ($colnames as $colname) {
$indexedColumns[] = $this->quoteIdentifier($colname);
$indexedColumnsHash = implode(',', $indexedColumns);
if (!array_key_exists($indexedColumnsHash, $collectedIndexes)) {
$collectedIndexes[$indexedColumnsHash] = array();
}
$collectedIndexes[$indexedColumnsHash][] = $indexName;
}
}
/**
* Checks whether passed-in array of Column objects contains a column with specified name.
* @param array Column[] or string[]
* @param string $searchcol Column name to search for
*/
private function containsColname($columns, $searchcol)
{
foreach ($columns as $col) {
if ($col instanceof Column) {
$col = $col->getName();
}
if ($col == $searchcol) {
return true;
}
}
return false;
}
/**
* Not used for MySQL since foreign keys are declared inside table declaration.
* @see addForeignKeysLines()
*/
protected function addForeignKeys(&$script)
{
}
/**
* Not used for MySQL since indexes are declared inside table declaration.
* @see addIndicesLines()
*/
protected function addIndices(&$script)
{
}
/**
* Builds the DDL SQL for a Column object.
* @return string
*/
public function getColumnDDL(Column $col)
{
$platform = $this->getPlatform();
$domain = $col->getDomain();
$sqlType = $domain->getSqlType();
$notNullString = $col->getNotNullString();
$defaultSetting = $col->getDefaultSetting();
// Special handling of TIMESTAMP/DATETIME types ...
// See: http://propel.phpdb.org/trac/ticket/538
if ($sqlType == 'DATETIME') {
$def = $domain->getDefaultValue();
if ($def && $def->isExpression()) { // DATETIME values can only have constant expressions
$sqlType = 'TIMESTAMP';
}
} elseif ($sqlType == 'DATE') {
$def = $domain->getDefaultValue();
if ($def && $def->isExpression()) {
throw new EngineException("DATE columns cannot have default *expressions* in MySQL.");
}
} elseif ($sqlType == 'TEXT' || $sqlType == 'BLOB') {
if ($domain->getDefaultValue()) {
throw new EngineException("BLOB and TEXT columns cannot have DEFAULT values. in MySQL.");
}
}
$sb = "";
$sb .= $this->quoteIdentifier($col->getName()) . " ";
$sb .= $sqlType;
if ($platform->hasSize($sqlType)) {
$sb .= $domain->printSize();
}
$sb .= " ";
if ($sqlType == 'TIMESTAMP') {
$notNullString = $col->getNotNullString();
$defaultSetting = $col->getDefaultSetting();
if ($notNullString == '') {
$notNullString = 'NULL';
}
if ($defaultSetting == '' && $notNullString == 'NOT NULL') {
$defaultSetting = 'DEFAULT CURRENT_TIMESTAMP';
}
$sb .= $notNullString . " " . $defaultSetting . " ";
} else {
$sb .= $defaultSetting . " ";
$sb .= $notNullString . " ";
}
$sb .= $col->getAutoIncrementString();
return trim($sb);
}
}