Commit 38bd0693 authored by Martin Hasoň's avatar Martin Hasoň

Added support for foreign keys and alter table in Sqlite platform

parent 95522656
......@@ -20,6 +20,10 @@
namespace Doctrine\DBAL\Platforms;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
/**
* The SqlitePlatform class describes the specifics and dialects of the SQLite
......@@ -263,24 +267,34 @@ class SqlitePlatform extends AbstractPlatform
*/
protected function _getCreateTableSQL($name, array $columns, array $options = array())
{
$name = str_replace(".", "__", $name);
$name = str_replace('.', '__', $name);
$queryFields = $this->getColumnDeclarationListSQL($columns);
if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
foreach ($options['uniqueConstraints'] as $name => $definition) {
$queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
}
}
if (isset($options['primary']) && ! empty($options['primary'])) {
$keyColumns = array_unique(array_values($options['primary']));
$queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')';
}
if (isset($options['foreignKeys'])) {
foreach ($options['foreignKeys'] as $foreignKey) {
$queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey);
}
}
$query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')';
if (isset($options['indexes']) && ! empty($options['indexes'])) {
foreach ($options['indexes'] as $index => $indexDef) {
$query[] = $this->getCreateIndexSQL($indexDef, $name);
}
if (isset($options['alter']) && true === $options['alter']) {
return $query;
}
if (isset($options['unique']) && ! empty($options['unique'])) {
foreach ($options['unique'] as $index => $indexDef) {
if (isset($options['indexes']) && ! empty($options['indexes'])) {
foreach ($options['indexes'] as $index => $indexDef) {
$query[] = $this->getCreateIndexSQL($indexDef, $name);
}
}
......@@ -307,7 +321,7 @@ class SqlitePlatform extends AbstractPlatform
public function getListTableConstraintsSQL($table)
{
$table = str_replace(".", "__", $table);
$table = str_replace('.', '__', $table);
return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = '$table' AND sql NOT NULL ORDER BY name";
}
......@@ -324,7 +338,7 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getListTableIndexesSQL($table, $currentDatabase = null)
{
$table = str_replace(".", "__", $table);
$table = str_replace('.', '__', $table);
return "PRAGMA index_list($table)";
}
......@@ -354,26 +368,6 @@ class SqlitePlatform extends AbstractPlatform
return 'DROP VIEW '. $name;
}
/**
* {@inheritDoc}
*
* SQLite does support foreign key constraints, but only in CREATE TABLE statements...
* This really limits their usefulness and requires SQLite specific handling, so
* we simply say that SQLite does NOT support foreign keys for now...
*/
public function supportsForeignKeyConstraints()
{
return false;
}
/**
* {@inheritDoc}
*/
public function supportsAlterTable()
{
return false;
}
/**
* {@inheritDoc}
*/
......@@ -395,7 +389,7 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getTruncateTableSQL($tableName, $cascade = false)
{
$tableName = str_replace(".", "__", $tableName);
$tableName = str_replace('.', '__', $tableName);
return 'DELETE FROM '.$tableName;
}
......@@ -495,6 +489,60 @@ class SqlitePlatform extends AbstractPlatform
return 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords';
}
/**
* {@inheritDoc}
*/
protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
{
if ( ! $diff->fromTable instanceof Table) {
throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
}
$sql = array();
foreach ($diff->fromTable->getIndexes() as $index) {
if ( ! $index->isPrimary()) {
$sql[] = $this->getDropIndexSQL($index, $diff->name);
}
}
return $sql;
}
/**
* {@inheritDoc}
*/
protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
{
if ( ! $diff->fromTable instanceof Table) {
throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
}
$sql = array();
$indexes = $diff->fromTable->getIndexes();
foreach ($diff->removedIndexes as $index) {
if (isset($indexes[$index->getName()])) {
unset($indexes[$index->getName()]);
}
}
foreach (array_merge($diff->changedIndexes, $diff->addedIndexes) as $index) {
$name = $index->getName();
$indexes[$name] = $index;
}
$tableName = $diff->newName ?: $diff->name;
foreach ($indexes as $indexName => $index) {
if ($index->isPrimary()) {
continue;
}
$sql[] = $this->getCreateIndexSQL($index, $tableName);
}
return $sql;
}
/**
* {@inheritDoc}
*/
......@@ -508,7 +556,7 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getTemporaryTableName($tableName)
{
$tableName = str_replace(".", "__", $tableName);
$tableName = str_replace('.', '__', $tableName);
return $tableName;
}
......@@ -526,4 +574,167 @@ class SqlitePlatform extends AbstractPlatform
{
return true;
}
/**
* {@inheritDoc}
*/
public function getCreatePrimaryKeySQL(Index $index, $table)
{
throw new DBALException('Sqlite platform does not support alter primary key.');
}
/**
* {@inheritdoc}
*/
public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
{
throw new DBALException('Sqlite platform does not support alter foreign key.');
}
/**
* {@inheritdoc}
*/
public function getDropForeignKeySQL($foreignKey, $table)
{
throw new DBALException('Sqlite platform does not support alter foreign key.');
}
/**
* {@inheritDoc}
*/
public function getListTableForeignKeysSQL($table, $database = null)
{
$table = str_replace('.', '__', $table);
return "PRAGMA foreign_key_list($table)";
}
/**
* {@inheritDoc}
*/
public function getAlterTableSQL(TableDiff $diff)
{
$fromTable = $diff->fromTable;
if ( ! $fromTable instanceof Table) {
throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
}
$table = clone $fromTable;
$columns = $table->getColumns();
$columnSql = array();
foreach ($diff->removedColumns as $columnName => $column) {
if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
continue;
}
unset($columns[$columnName]);
}
$fromColumns = array();
$toColumns = array();
foreach ($columns as $columnName => $column) {
$fromColumns[$columnName] = $toColumns[$columnName] = $column->getQuotedName($this);
}
foreach ($diff->renamedColumns as $oldColumnName => $column) {
if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
continue;
}
unset($columns[$oldColumnName]);
$columns[$column->getName()] = $column;
$toColumns[$oldColumnName] = $column->getQuotedName($this);
}
foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
continue;
}
unset($columns[$oldColumnName]);
$columnName = $columnDiff->column->getName();
$columns[$columnName] = $columnDiff->column;
$toColumns[$oldColumnName] = $columnDiff->column->getQuotedName($this);
}
foreach ($diff->addedColumns as $columnName => $column) {
if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
continue;
}
$columns[$columnName] = $column;
}
$foreignKeys = $table->getForeignKeys();
foreach ($diff->removedForeignKeys as $constraint) {
$constraintName = strtolower($constraint->getName());
if (isset($foreignKeys[$constraintName])) {
unset($foreignKeys[$constraintName]);
}
}
foreach ($diff->changedForeignKeys as $constraint) {
$constraintName = strtolower($constraint->getName());
$foreignKeys[$constraintName] = $constraint;
}
foreach ($diff->addedForeignKeys as $constraint) {
$foreignKeys[] = $constraint;
}
$sql = array();
$tableSql = array();
if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
$newTableName = $diff->newName ?: $diff->name;
$tempTable = new Table('__temp__'.$newTableName, $columns, $this->getPrimaryIndex($diff), $foreignKeys, 0, $table->getOptions());
$tempTable->addOption('alter', true);
$newTable = new Table($newTableName);
$sql = array_merge($this->getPreAlterTableIndexForeignKeySQL($diff), $this->getCreateTableSQL($tempTable, self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS));
$sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $tempTable->getQuotedName($this), implode(', ', $toColumns), implode(', ', $fromColumns), $table->getQuotedName($this));
$sql[] = $this->getDropTableSQL($fromTable);
$sql[] = 'ALTER TABLE '.$tempTable->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this);
$sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
}
return array_merge($sql, $tableSql, $columnSql);
}
private function getPrimaryIndex(TableDiff $diff)
{
$primaryIndex = array();
foreach ($diff->fromTable->getIndexes() as $index) {
if ($index->isPrimary()) {
$primaryIndex = array($index->getName() => $index);
}
}
foreach ($diff->removedIndexes as $index) {
if (isset($primaryIndex[$index->getName()])) {
$primaryIndex = array();
break;
}
}
foreach ($diff->changedIndexes as $index) {
if ($index->isPrimary()) {
$primaryIndex = array($index);
} elseif (isset($primaryIndex[$index->getName()])) {
$primaryIndex = array();
}
}
foreach ($diff->addedIndexes as $index) {
if ($index->isPrimary()) {
$primaryIndex = array($index);
}
}
return $primaryIndex;
}
}
......@@ -19,6 +19,8 @@
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\DBALException;
/**
* SqliteSchemaManager
*
......@@ -61,6 +63,79 @@ class SqliteSchemaManager extends AbstractSchemaManager
$conn->close();
}
/**
* {@inheritdoc}
*/
public function renameTable($name, $newName)
{
$tableDiff = new TableDiff($name);
$tableDiff->fromTable = $this->listTableDetails($name);
$tableDiff->newName = $newName;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
{
$tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
$tableDiff->addedForeignKeys[] = $foreignKey;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
{
$tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
$tableDiff->changedForeignKeys[] = $foreignKey;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function dropForeignKey($foreignKey, $table)
{
$tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
$tableDiff->removedForeignKeys[] = $foreignKey;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function listTableForeignKeys($table, $database = null)
{
if (null === $database) {
$database = $this->_conn->getDatabase();
}
$sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
$tableForeignKeys = $this->_conn->fetchAll($sql);
if ( ! empty($tableForeignKeys)) {
$createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
$createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : '';
if (preg_match_all('#(?:CONSTRAINT\s+([^\s]+)\s+)?FOREIGN\s+KEY\s+\(#', $createSql, $match)) {
$names = array_reverse($match[1]);
} else {
$names = array();
}
foreach ($tableForeignKeys as $key => $value) {
$id = $value['id'];
$tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id;
}
}
return $this->_getPortableTableForeignKeysList($tableForeignKeys);
}
protected function _getPortableTableDefinition($table)
{
return $table['name'];
......@@ -187,4 +262,62 @@ class SqliteSchemaManager extends AbstractSchemaManager
{
return new View($view['name'], $view['sql']);
}
protected function _getPortableTableForeignKeysList($tableForeignKeys)
{
$list = array();
foreach ($tableForeignKeys as $key => $value) {
$value = array_change_key_case($value, CASE_LOWER);
if ( ! isset($list[$value['constraint_name']])) {
if ( ! isset($value['on_delete']) || $value['on_delete'] == "RESTRICT") {
$value['on_delete'] = null;
}
if ( ! isset($value['on_update']) || $value['on_update'] == "RESTRICT") {
$value['on_update'] = null;
}
$list[$value['constraint_name']] = array(
'name' => $value['constraint_name'],
'local' => array(),
'foreign' => array(),
'foreignTable' => $value['table'],
'onDelete' => $value['on_delete'],
'onUpdate' => $value['on_update'],
);
}
$list[$value['constraint_name']]['local'][] = $value['from'];
$list[$value['constraint_name']]['foreign'][] = $value['to'];
}
$result = array();
foreach($list as $constraint) {
$result[] = new ForeignKeyConstraint(
array_values($constraint['local']), $constraint['foreignTable'],
array_values($constraint['foreign']), $constraint['name'],
array(
'onDelete' => $constraint['onDelete'],
'onUpdate' => $constraint['onUpdate'],
)
);
}
return $result;
}
private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table)
{
if ( ! $table instanceof Table) {
$tableDetails = $this->tryMethod('listTableDetails', $table);
if (false === $table) {
throw new \DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
}
$table = $tableDetails;
}
$tableDiff = new TableDiff($table->getName());
$tableDiff->fromTable = $table;
return $tableDiff;
}
}
......@@ -17,14 +17,14 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
*/
protected $_sm;
protected function getPlatformName()
{
$class = get_class($this);
protected function getPlatformName()
{
$class = get_class($this);
$e = explode('\\', $class);
$testClass = end($e);
$dbms = strtolower(str_replace('SchemaManagerTest', null, $testClass));
return $dbms;
}
}
protected function setUp()
{
......@@ -376,7 +376,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->markTestSkipped('Alter Table is not supported by this platform.');
}
$this->createTestTable('alter_table');
$alterTable = $this->createTestTable('alter_table');
$this->createTestTable('alter_table_foreign');
$table = $this->_sm->listTableDetails('alter_table');
......@@ -387,6 +387,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->assertEquals(1, count($table->getIndexes()));
$tableDiff = new \Doctrine\DBAL\Schema\TableDiff("alter_table");
$tableDiff->fromTable = $alterTable;
$tableDiff->addedColumns['foo'] = new \Doctrine\DBAL\Schema\Column('foo', Type::getType('integer'));
$tableDiff->removedColumns['test'] = $table->getColumn('test');
......@@ -397,6 +398,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->assertTrue($table->hasColumn('foo'));
$tableDiff = new \Doctrine\DBAL\Schema\TableDiff("alter_table");
$tableDiff->fromTable = $table;
$tableDiff->addedIndexes[] = new \Doctrine\DBAL\Schema\Index('foo_idx', array('foo'));
$this->_sm->alterTable($tableDiff);
......@@ -409,6 +411,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->assertFalse($table->getIndex('foo_idx')->isUnique());
$tableDiff = new \Doctrine\DBAL\Schema\TableDiff("alter_table");
$tableDiff->fromTable = $table;
$tableDiff->changedIndexes[] = new \Doctrine\DBAL\Schema\Index('foo_idx', array('foo', 'foreign_key_test'));
$this->_sm->alterTable($tableDiff);
......@@ -419,6 +422,7 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->assertEquals(array('foo', 'foreign_key_test'), array_map('strtolower', $table->getIndex('foo_idx')->getColumns()));
$tableDiff = new \Doctrine\DBAL\Schema\TableDiff("alter_table");
$tableDiff->fromTable = $table;
$tableDiff->removedIndexes[] = new \Doctrine\DBAL\Schema\Index('foo_idx', array('foo', 'foreign_key_test'));
$fk = new \Doctrine\DBAL\Schema\ForeignKeyConstraint(array('foreign_key_test'), 'alter_table_foreign', array('id'));
$tableDiff->addedForeignKeys[] = $fk;
......@@ -585,6 +589,8 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$table = $this->getTestTable($name, $options);
$this->_sm->dropAndCreateTable($table);
return $table;
}
protected function getTestTable($name, $options=array())
......
......@@ -28,12 +28,14 @@ class SqliteSchemaManagerTest extends SchemaManagerFunctionalTestCase
$this->assertEquals(false, file_exists($path));
}
/**
* @expectedException \Doctrine\DBAL\DBALException
*/
public function testRenameTable()
{
$this->createTestTable('oldname');
$this->_sm->renameTable('oldname', 'newname');
$tables = $this->_sm->listTableNames();
$this->assertContains('newname', $tables);
$this->assertNotContains('oldname', $tables);
}
public function testAutoincrementDetection()
......@@ -43,4 +45,4 @@ class SqliteSchemaManagerTest extends SchemaManagerFunctionalTestCase
. 'auto-increment. So, while it does support a single identity column, we cannot with '
. 'certainty determine which it is.');
}
}
\ No newline at end of file
}
......@@ -205,7 +205,15 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
{
$expectedSql = $this->getGenerateAlterTableSql();
$table = new Table('mytable');
$table->addColumn('id', 'integer', array('autoincrement' => true));
$table->addColumn('foo', 'integer');
$table->addColumn('bar', 'string');
$table->addColumn('bloo', 'boolean');
$table->setPrimaryKey(array('id'));
$tableDiff = new TableDiff('mytable');
$tableDiff->fromTable = $table;
$tableDiff->newName = 'userlist';
$tableDiff->addedColumns['quota'] = new \Doctrine\DBAL\Schema\Column('quota', \Doctrine\DBAL\Types\Type::getType('integer'), array('notnull' => false));
$tableDiff->removedColumns['foo'] = new \Doctrine\DBAL\Schema\Column('foo', \Doctrine\DBAL\Types\Type::getType('integer'));
......@@ -309,7 +317,13 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
$this->_platform->setEventManager($eventManager);
$table = new Table('mytable');
$table->addColumn('removed', 'integer');
$table->addColumn('changed', 'integer');
$table->addColumn('renamed', 'integer');
$tableDiff = new TableDiff('mytable');
$tableDiff->fromTable = $table;
$tableDiff->addedColumns['added'] = new \Doctrine\DBAL\Schema\Column('added', \Doctrine\DBAL\Types\Type::getType('integer'), array());
$tableDiff->removedColumns['removed'] = new \Doctrine\DBAL\Schema\Column('removed', \Doctrine\DBAL\Types\Type::getType('integer'), array());
$tableDiff->changedColumns['changed'] = new \Doctrine\DBAL\Schema\ColumnDiff(
......
......@@ -105,9 +105,17 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
}
/**
* @expectedException \Doctrine\DBAL\DBALException
*/
public function testGeneratesForeignKeyCreationSql()
{
parent::testGeneratesForeignKeyCreationSql();
}
public function getGenerateForeignKeySql()
{
$this->markTestSkipped('SQLite does not support ForeignKeys.');
return null;
}
public function testModifyLimitQuery()
......@@ -124,12 +132,12 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
public function getGenerateAlterTableSql()
{
$this->markTestSkipped('SQlite does not support ALTER Table.');
}
public function testGetAlterTableSqlDispatchEvent()
{
$this->markTestSkipped('SQlite does not support ALTER Table.');
return array(
"CREATE TABLE __temp__userlist (id INTEGER NOT NULL, baz VARCHAR(255) DEFAULT 'def' NOT NULL, bloo BOOLEAN DEFAULT '0' NOT NULL, quota INTEGER DEFAULT NULL, PRIMARY KEY(id))",
"INSERT INTO __temp__userlist (id, baz, bloo) SELECT id, bar, bloo FROM mytable",
"DROP TABLE mytable",
"ALTER TABLE __temp__userlist RENAME TO userlist",
);
}
/**
......@@ -146,11 +154,6 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
'CREATE TABLE test ("like" INTEGER NOT NULL, PRIMARY KEY("like"))',
$createTableSQL[0]
);
$this->assertEquals(
'ALTER TABLE test ADD PRIMARY KEY ("like")',
$this->_platform->getCreatePrimaryKeySQL($table->getIndex('primary'), 'test')
);
}
protected function getQuotedColumnInPrimaryKeySQL()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment