1
0
mirror of synced 2024-12-12 22:36:02 +03:00

[2.0] DDC-214, DDC-303, DDC-304 - Fix several errors with Schema Inference from Database and Metadata and Comparisons, aswell as related bugs in DatabaseDriver. DDC-305 - Abstracted TRUNCATE command for all platforms.

This commit is contained in:
beberlei 2010-02-07 12:36:30 +00:00
parent 1d7946b7d1
commit ac4c33c371
19 changed files with 267 additions and 43 deletions

View File

@ -810,12 +810,12 @@ abstract class AbstractPlatform
$sql = array(); $sql = array();
if ($this->supportsForeignKeyConstraints()) { if ($this->supportsForeignKeyConstraints()) {
foreach ($diff->addedForeignKeys AS $foreignKey) {
$sql[] = $this->getCreateForeignKeySql($foreignKey, $tableName);
}
foreach ($diff->removedForeignKeys AS $foreignKey) { foreach ($diff->removedForeignKeys AS $foreignKey) {
$sql[] = $this->getDropForeignKeySql($foreignKey, $tableName); $sql[] = $this->getDropForeignKeySql($foreignKey, $tableName);
} }
foreach ($diff->addedForeignKeys AS $foreignKey) {
$sql[] = $this->getCreateForeignKeySql($foreignKey, $tableName);
}
foreach ($diff->changedForeignKeys AS $foreignKey) { foreach ($diff->changedForeignKeys AS $foreignKey) {
$sql[] = $this->getDropForeignKeySql($foreignKey, $tableName); $sql[] = $this->getDropForeignKeySql($foreignKey, $tableName);
$sql[] = $this->getCreateForeignKeySql($foreignKey, $tableName); $sql[] = $this->getCreateForeignKeySql($foreignKey, $tableName);
@ -1831,4 +1831,19 @@ abstract class AbstractPlatform
{ {
return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)'; return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)';
} }
/**
* Generate a Truncate Table SQL statement for a given table.
*
* Cascade is not supported on many platforms but would optionally cascade the truncate by
* following the foreign keys.
*
* @param string $tableName
* @param bool $cascade
* @return string
*/
public function getTruncateTableSql($tableName, $cascade = false)
{
return 'TRUNCATE '.$tableName;
}
} }

View File

@ -505,4 +505,12 @@ class MsSqlPlatform extends AbstractPlatform
{ {
return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
} }
/**
* @inheritdoc
*/
public function getTruncateTableSql($tableName, $cascade = false)
{
return 'TRUNCATE TABLE '.$tableName;
}
} }

View File

@ -203,8 +203,12 @@ class MySqlPlatform extends AbstractPlatform
public function getListTableForeignKeysSql($table, $database = null) public function getListTableForeignKeysSql($table, $database = null)
{ {
$sql = "SELECT `CONSTRAINT_NAME`, `COLUMN_NAME`, `REFERENCED_TABLE_NAME`, ". $sql = "SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ".
"`REFERENCED_COLUMN_NAME` FROM information_schema.key_column_usage WHERE table_name = '" . $table . "'"; "k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ".
"FROM information_schema.key_column_usage k /*!50116 ".
"INNER JOIN information_schema.referential_constraints c ON k.`CONSTRAINT_NAME` = c.constraint_name AND ".
" c.constraint_name = k.constraint_name AND ".
" c.table_name = k.table_name */ WHERE k.table_name = '$table'";
if ( ! is_null($database)) { if ( ! is_null($database)) {
$sql .= " AND table_schema = '$database'"; $sql .= " AND table_schema = '$database'";

View File

@ -418,18 +418,27 @@ END;';
{ {
$table = strtoupper($table); $table = strtoupper($table);
return "SELECT rel.constraint_name, rel.position, col.column_name AS local_column, ". return "SELECT alc.constraint_name,
" rel.table_name, rel.column_name AS foreign_column, cc.delete_rule ". alc.DELETE_RULE,
"FROM (user_tab_columns col ". alc.search_condition,
"JOIN user_cons_columns con ". cols.column_name \"local_column\",
" ON col.table_name = con.table_name ". cols.position,
" AND col.column_name = con.column_name ". r_alc.table_name \"references_table\",
"JOIN user_constraints cc ". r_cols.column_name \"foreign_column\"
" ON con.constraint_name = cc.constraint_name ". FROM all_cons_columns cols
"JOIN user_cons_columns rel ". LEFT JOIN all_constraints alc
" ON cc.r_constraint_name = rel.constraint_name ". ON alc.constraint_name = cols.constraint_name
" AND con.position = rel.position) ". AND alc.owner = cols.owner
"WHERE cc.constraint_type = 'R' AND col.table_name = '".$table."'"; LEFT JOIN all_constraints r_alc
ON alc.r_constraint_name = r_alc.constraint_name
AND alc.r_owner = r_alc.owner
LEFT JOIN all_cons_columns r_cols
ON r_alc.constraint_name = r_cols.constraint_name
AND r_alc.owner = r_cols.owner
AND cols.position = r_cols.position
WHERE alc.constraint_name = cols.constraint_name
AND alc.constraint_type = 'R'
AND alc.table_name = '".$table."'";
} }
public function getListTableConstraintsSql($table) public function getListTableConstraintsSql($table)
@ -458,6 +467,24 @@ END;';
return 'DROP SEQUENCE ' . $sequence; return 'DROP SEQUENCE ' . $sequence;
} }
/**
* @param ForeignKeyConstraint|string $foreignKey
* @param Table|string $table
* @return string
*/
public function getDropForeignKeySql($foreignKey, $table)
{
if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
$foreignKey = $foreignKey->getName();
}
if ($table instanceof \Doctrine\DBAL\Schema\Table) {
$table = $table->getName();
}
return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
}
public function getDropDatabaseSql($database) public function getDropDatabaseSql($database)
{ {
return 'DROP USER ' . $database . ' CASCADE'; return 'DROP USER ' . $database . ' CASCADE';
@ -627,4 +654,12 @@ END;';
{ {
return false; return false;
} }
/**
* @inheritdoc
*/
public function getTruncateTableSql($tableName, $cascade = false)
{
return 'TRUNCATE TABLE '.$tableName;
}
} }

View File

@ -784,4 +784,12 @@ class PostgreSqlPlatform extends AbstractPlatform
{ {
return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
} }
/**
* @inheritdoc
*/
public function getTruncateTableSql($tableName, $cascade = false)
{
return 'TRUNCATE '.$tableName.' '.($cascade)?'CASCADE':'';
}
} }

View File

@ -456,4 +456,12 @@ class SqlitePlatform extends AbstractPlatform
{ {
return 'sqlite'; return 'sqlite';
} }
/**
* @inheritdoc
*/
public function getTruncateTableSql($tableName, $cascade = false)
{
return 'DELETE FROM '.$tableName;
}
} }

View File

@ -238,6 +238,20 @@ abstract class AbstractSchemaManager
return $this->_getPortableTableIndexesList($tableIndexes, $table); return $this->_getPortableTableIndexesList($tableIndexes, $table);
} }
/**
* Return a list of all tables in the current database
*
* @return array
*/
public function listTableNames()
{
$sql = $this->_platform->getListTablesSql();
$tables = $this->_conn->fetchAll($sql);
return $this->_getPortableTablesList($tables);
}
/** /**
* List the tables for this connection * List the tables for this connection
* *
@ -245,13 +259,9 @@ abstract class AbstractSchemaManager
*/ */
public function listTables() public function listTables()
{ {
$sql = $this->_platform->getListTablesSql(); $tableNames = $this->listTableNames();
$tables = $this->_conn->fetchAll($sql);
$tableNames = $this->_getPortableTablesList($tables);
$tables = array(); $tables = array();
foreach ($tableNames AS $tableName) { foreach ($tableNames AS $tableName) {
$columns = $this->listTableColumns($tableName); $columns = $this->listTableColumns($tableName);
$foreignKeys = array(); $foreignKeys = array();

View File

@ -272,11 +272,11 @@ class Comparator
*/ */
public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2)
{ {
if ($key1->getLocalColumns() != $key2->getLocalColumns()) { if (array_map('strtolower', $key1->getLocalColumns()) != array_map('strtolower', $key2->getLocalColumns())) {
return true; return true;
} }
if ($key1->getForeignColumns() != $key2->getForeignColumns()) { if (array_map('strtolower', $key1->getForeignColumns()) != array_map('strtolower', $key2->getForeignColumns())) {
return true; return true;
} }

View File

@ -265,13 +265,23 @@ class MySqlSchemaManager extends AbstractSchemaManager
public function _getPortableTableForeignKeyDefinition($tableForeignKey) public function _getPortableTableForeignKeyDefinition($tableForeignKey)
{ {
$tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER);
if ($tableForeignKey['delete_rule'] == "RESTRICT") {
$tableForeignKey['delete_rule'] = null;
}
if ($tableForeignKey['update_rule'] == "RESTRICT") {
$tableForeignKey['update_rule'] = null;
}
return new ForeignKeyConstraint( return new ForeignKeyConstraint(
(array)$tableForeignKey['column_name'], (array)$tableForeignKey['column_name'],
$tableForeignKey['referenced_table_name'], $tableForeignKey['referenced_table_name'],
(array)$tableForeignKey['referenced_column_name'], (array)$tableForeignKey['referenced_column_name'],
$tableForeignKey['constraint_name'], $tableForeignKey['constraint_name'],
array() array(
'onUpdate' => $tableForeignKey['update_rule'],
'onDelete' => $tableForeignKey['delete_rule'],
)
); );
} }
} }

View File

@ -22,11 +22,12 @@
namespace Doctrine\DBAL\Schema; namespace Doctrine\DBAL\Schema;
/** /**
* xxx * Oracle Schema Manager
* *
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
* @author Konsta Vesterinen <kvesteri@cc.hut.fi> * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library) * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @version $Revision$ * @version $Revision$
* @since 2.0 * @since 2.0
*/ */
@ -198,11 +199,15 @@ class OracleSchemaManager extends AbstractSchemaManager
foreach ($tableForeignKeys as $key => $value) { foreach ($tableForeignKeys as $key => $value) {
$value = \array_change_key_case($value, CASE_LOWER); $value = \array_change_key_case($value, CASE_LOWER);
if (!isset($list[$value['constraint_name']])) { if (!isset($list[$value['constraint_name']])) {
if ($value['delete_rule'] == "NO ACTION") {
$value['delete_rule'] = null;
}
$list[$value['constraint_name']] = array( $list[$value['constraint_name']] = array(
'name' => $value['constraint_name'], 'name' => $value['constraint_name'],
'local' => array(), 'local' => array(),
'foreign' => array(), 'foreign' => array(),
'foreignTable' => $value['table_name'], 'foreignTable' => $value['references_table'],
'onDelete' => $value['delete_rule'], 'onDelete' => $value['delete_rule'],
); );
} }

View File

@ -201,6 +201,30 @@ class Table extends AbstractAsset
return $this->_createIndex($columnNames, $indexName, true, false); return $this->_createIndex($columnNames, $indexName, true, false);
} }
/**
* Check if an index begins in the order of the given columns.
*
* @param array $columnsNames
* @return bool
*/
public function columnsAreIndexed(array $columnsNames)
{
foreach ($this->getIndexes() AS $index) {
$indexColumns = $index->getColumns();
$areIndexed = true;
for ($i = 0; $i < count($columnsNames); $i++) {
if ($columnsNames[$i] != $indexColumns[$i]) {
$areIndexed = false;
}
}
if ($areIndexed) {
return true;
}
}
return false;
}
/** /**
* *
* @param array $columnNames * @param array $columnNames
@ -585,7 +609,7 @@ class Table extends AbstractAsset
$visitor->acceptTable($this); $visitor->acceptTable($this);
foreach ($this->getColumns() AS $column) { foreach ($this->getColumns() AS $column) {
$visitor->acceptColunn($this, $column); $visitor->acceptColumn($this, $column);
} }
foreach ($this->getIndexes() AS $index) { foreach ($this->getIndexes() AS $index) {

View File

@ -81,7 +81,7 @@ class CreateSchemaSqlCollector implements Visitor
); );
} }
public function acceptColunn(Table $table, Column $column) public function acceptColumn(Table $table, Column $column)
{ {
} }

View File

@ -89,7 +89,7 @@ class DropSchemaSqlCollector implements Visitor
/** /**
* @param Column $column * @param Column $column
*/ */
public function acceptColunn(Table $table, Column $column) public function acceptColumn(Table $table, Column $column)
{ {
} }

View File

@ -42,7 +42,7 @@ class FixSchema implements Visitor
/** /**
* @param Column $column * @param Column $column
*/ */
public function acceptColunn(Table $table, Column $column) public function acceptColumn(Table $table, Column $column)
{ {
} }
@ -54,7 +54,12 @@ class FixSchema implements Visitor
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{ {
if ($this->_addExplicitIndexForForeignKey) { if ($this->_addExplicitIndexForForeignKey) {
$localTable->addIndex($fkConstraint->getColumns()); $columns = $fkConstraint->getColumns();
if ($localTable->columnsAreIndexed($columns)) {
return;
}
$localTable->addIndex($columns);
} }
} }

View File

@ -54,7 +54,7 @@ interface Visitor
/** /**
* @param Column $column * @param Column $column
*/ */
public function acceptColunn(Table $table, Column $column); public function acceptColumn(Table $table, Column $column);
/** /**
* @param Table $localTable * @param Table $localTable

View File

@ -90,7 +90,7 @@ class DatabaseDriver implements Driver
$fieldMapping['id'] = true; $fieldMapping['id'] = true;
} }
$fieldMapping['fieldName'] = Inflector::camelize($column->getName()); $fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName()));
$fieldMapping['columnName'] = $column->getName(); $fieldMapping['columnName'] = $column->getName();
$fieldMapping['type'] = strtolower((string) $column->getType()); $fieldMapping['type'] = strtolower((string) $column->getType());

View File

@ -109,8 +109,12 @@ class SchemaTool
{ {
$processedClasses = array(); // Reminder for processed classes, used for hierarchies $processedClasses = array(); // Reminder for processed classes, used for hierarchies
$metadataSchemaConfig = new \Doctrine\DBAL\Schema\SchemaConfig();
$metadataSchemaConfig->setExplicitForeignKeyIndexes(false);
$metadataSchemaConfig->setMaxIdentifierLength(63);
$sm = $this->_em->getConnection()->getSchemaManager(); $sm = $this->_em->getConnection()->getSchemaManager();
$schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $sm->createSchemaConfig()); $schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $metadataSchemaConfig);
foreach ($classes as $class) { foreach ($classes as $class) {
if (isset($processedClasses[$class->name]) || $class->isMappedSuperclass) { if (isset($processedClasses[$class->name]) || $class->isMappedSuperclass) {
@ -235,6 +239,11 @@ class SchemaTool
{ {
$discrColumn = $class->discriminatorColumn; $discrColumn = $class->discriminatorColumn;
if (!isset($discrColumn['type']) || (strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null)) {
$discrColumn['type'] = 'string';
$discrColumn['length'] = 255;
}
$table->createColumn( $table->createColumn(
$class->getQuotedDiscriminatorColumnName($this->_platform), $class->getQuotedDiscriminatorColumnName($this->_platform),
$discrColumn['type'], $discrColumn['type'],
@ -329,11 +338,8 @@ class SchemaTool
* This includes the SQL for foreign key constraints and join tables. * This includes the SQL for foreign key constraints and join tables.
* *
* @param ClassMetadata $class * @param ClassMetadata $class
* @param array $sql The sequence of SQL statements where any new statements should be appended. * @param \Doctrine\DBAL\Schema\Table $table
* @param array $columns The list of columns in the class's primary table where any additional * @param \Doctrine\DBAL\Schema\Schema $schema
* columns required by relations should be appended.
* @param array $constraints The constraints of the table where any additional constraints
* required by relations should be appended.
* @return void * @return void
*/ */
private function _gatherRelationsSql($class, $table, $schema) private function _gatherRelationsSql($class, $table, $schema)
@ -344,10 +350,10 @@ class SchemaTool
} }
$foreignClass = $this->_em->getClassMetadata($mapping->targetEntityName); $foreignClass = $this->_em->getClassMetadata($mapping->targetEntityName);
if ($mapping->isOneToOne() && $mapping->isOwningSide) { if ($mapping->isOneToOne() && $mapping->isOwningSide) {
$primaryKeyColumns = $uniqueConstraints = array(); // unnecessary for this relation-type $primaryKeyColumns = $uniqueConstraints = array(); // unnecessary for this relation-type
$this->_gatherRelationJoinColumns($mapping->getJoinColumns(), $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints); $this->_gatherRelationJoinColumns($mapping->getJoinColumns(), $table, $foreignClass, $mapping, $primaryKeyColumns, $uniqueConstraints);
} else if ($mapping->isOneToMany() && $mapping->isOwningSide) { } else if ($mapping->isOneToMany() && $mapping->isOwningSide) {
//... create join table, one-many through join table supported later //... create join table, one-many through join table supported later
@ -423,6 +429,9 @@ class SchemaTool
$columnDef = $fieldMapping['columnDefinition']; $columnDef = $fieldMapping['columnDefinition'];
} }
$columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef); $columnOptions = array('notnull' => false, 'columnDefinition' => $columnDef);
if (isset($joinColumn['nullable'])) {
$columnOptions['notnull'] = !$joinColumn['nullable'];
}
$theJoinTable->createColumn( $theJoinTable->createColumn(
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions $columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions

View File

@ -22,6 +22,10 @@ class DatabaseDriverTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testCreateSimpleYamlFromDatabase() public function testCreateSimpleYamlFromDatabase()
{ {
if (!$this->_em->getConnection()->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped('Platform does not support foreign keys.');
}
$table = new \Doctrine\DBAL\Schema\Table("dbdriver_foo"); $table = new \Doctrine\DBAL\Schema\Table("dbdriver_foo");
$table->createColumn('id', 'integer'); $table->createColumn('id', 'integer');
$table->setPrimaryKey(array('id')); $table->setPrimaryKey(array('id'));
@ -92,7 +96,7 @@ class DatabaseDriverTest extends \Doctrine\Tests\OrmFunctionalTestCase
$output = false; $output = false;
foreach ($metadatas AS $metadata) { foreach ($metadatas AS $metadata) {
if ($metadata->name == $className) { if (strtolower($metadata->name) == strtolower($className)) {
return $metadata; return $metadata;
} }
} }

View File

@ -0,0 +1,79 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Tools;
require_once __DIR__ . '/../../../TestInit.php';
class DDC214Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp() {
parent::setUp();
$conn = $this->_em->getConnection();
if (strpos($conn->getDriver()->getName(), "sqlite") !== false) {
$this->markTestSkipped('SQLite does not support ALTER TABLE statements.');
}
}
/**
* @group DDC-214
*/
public function testCmsAddressModel()
{
$classes = array(
'Doctrine\Tests\Models\CMS\CmsUser',
'Doctrine\Tests\Models\CMS\CmsPhonenumber',
'Doctrine\Tests\Models\CMS\CmsAddress',
'Doctrine\Tests\Models\CMS\CmsGroup',
'Doctrine\Tests\Models\CMS\CmsArticle'
);
$this->assertCreatedSchemaNeedsNoUpdates($classes);
}
/**
* @group DDC-214
*/
public function testCompanyModel()
{
$classes = array(
'Doctrine\Tests\Models\Company\CompanyPerson',
'Doctrine\Tests\Models\Company\CompanyEmployee',
'Doctrine\Tests\Models\Company\CompanyManager',
'Doctrine\Tests\Models\Company\CompanyOrganization',
'Doctrine\Tests\Models\Company\CompanyEvent',
'Doctrine\Tests\Models\Company\CompanyAuction',
'Doctrine\Tests\Models\Company\CompanyRaffle',
'Doctrine\Tests\Models\Company\CompanyCar'
);
$this->assertCreatedSchemaNeedsNoUpdates($classes);
}
public function assertCreatedSchemaNeedsNoUpdates($classes)
{
$classMetadata = array();
foreach ($classes AS $class) {
$classMetadata[] = $this->_em->getClassMetadata($class);
}
$schemaTool = new Tools\SchemaTool($this->_em);
$schemaTool->dropSchema($classMetadata);
$schemaTool->createSchema($classMetadata);
$sm = $this->_em->getConnection()->getSchemaManager();
$fromSchema = $sm->createSchema();
$toSchema = $schemaTool->getSchemaFromMetadata($classMetadata);
$comparator = new \Doctrine\DBAL\Schema\Comparator();
$schemaDiff = $comparator->compare($fromSchema, $toSchema);
$sql = $schemaDiff->toSql($this->_em->getConnection()->getDatabasePlatform());
$this->assertEquals(0, count($sql));
}
}