From b951056025508ecb3f0607990d6ba886af3c91b3 Mon Sep 17 00:00:00 2001 From: jwage Date: Tue, 26 May 2009 18:14:19 +0000 Subject: [PATCH] [2.0] Adding initial MsSql platform and tests --- .../DBAL/Platforms/AbstractPlatform.php | 21 +- lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php | 308 ++++++++++++++++-- tests/Doctrine/Tests/DBAL/AllTests.php | 3 +- .../DBAL/Platforms/MssqlPlatformTest.php | 166 ++++++++++ .../DBAL/Platforms/MySqlPlatformTest.php | 4 - 5 files changed, 465 insertions(+), 37 deletions(-) create mode 100644 tests/Doctrine/Tests/DBAL/Platforms/MssqlPlatformTest.php diff --git a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php index ab36cb631..aba7af53f 100644 --- a/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -1092,11 +1092,7 @@ abstract class AbstractPlatform $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; - $fields = array(); - foreach ($definition['fields'] as $field) { - $fields[] = $field; - } - $query .= ' (' . implode(', ', $fields) . ')'; + $query .= ' (' . $this->getIndexFieldDeclarationListSql($definition['fields']) . ')'; return $query; } @@ -1715,7 +1711,20 @@ abstract class AbstractPlatform { throw DoctrineException::updateMe('Set transaction isolation not supported by this platform.'); } - + + /** + * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the charset + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + public function getCharsetFieldDeclaration($charset) + { + throw DoctrineException::updateMe('Get charset field declaration not supported by this platform.'); + } + /** * Gets the default transaction isolation level of the platform. * diff --git a/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php b/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php index bec56df70..4d4fa6b3b 100644 --- a/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/MsSqlPlatform.php @@ -1,7 +1,36 @@ . + */ namespace Doctrine\DBAL\Platforms; +use Doctrine\Common\DoctrineException; + +/** + * The MsSqlPlatform provides the behavior, features and SQL dialect of the + * MySQL database platform. + * + * @since 2.0 + * @author Roman Borschel + * @author Jonathan H. Wage + */ class MsSqlPlatform extends AbstractPlatform { /** @@ -23,7 +52,7 @@ class MsSqlPlatform extends AbstractPlatform * @return string * @override */ - public function writeLimitClause($query, $limit, $offset) + public function writeLimitClause($query, $limit = false, $offset = false) { if ($limit > 0) { $count = intval($limit); @@ -58,7 +87,124 @@ class MsSqlPlatform extends AbstractPlatform return $query; } + + public function getAlterTableSql($name, array $changes, $check = false) + { + foreach ($changes as $changeName => $change) { + switch ($changeName) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + throw \Doctrine\Common\DoctrineException::updateMe('alterTable: change type "' . $changeName . '" not yet supported'); + } + } + + $query = ''; + if ( ! empty($changes['name'])) { + $change_name = $this->quoteIdentifier($changes['name']); + $query .= 'RENAME TO ' . $change_name; + } + + if ( ! empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $fieldName => $field) { + if ($query) { + $query .= ', '; + } + $query .= 'ADD ' . $this->getColumnDeclarationSql($fieldName, $field); + } + } + + if ( ! empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $fieldName => $field) { + if ($query) { + $query .= ', '; + } + $field_name = $this->quoteIdentifier($fieldName, true); + $query .= 'DROP COLUMN ' . $fieldName; + } + } + + $rename = array(); + if ( ! empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $fieldName => $field) { + $rename[$field['name']] = $fieldName; + } + } + + if ( ! empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $fieldName => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$fieldName])) { + $oldFieldName = $rename[$fieldName]; + unset($rename[$fieldName]); + } else { + $oldFieldName = $fieldName; + } + $oldFieldName = $this->quoteIdentifier($oldFieldName, true); + $query .= 'CHANGE ' . $oldFieldName . ' ' + . $this->getColumnDeclarationSql($fieldName, $field['definition']); + } + } + + if ( ! empty($rename) && is_array($rename)) { + foreach ($rename as $renameName => $renamedField) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamedField]; + $renamedField = $this->quoteIdentifier($renamedField, true); + $query .= 'CHANGE ' . $renamedField . ' ' + . $this->getColumnDeclarationSql($field['name'], $field['definition']); + } + } + + if ( ! $query) { + return false; + } + + $name = $this->quoteIdentifier($name, true); + return 'ALTER TABLE ' . $name . ' ' . $query; + } + + /** + * Gets the character used for identifier quoting. + * + * @return string + * @override + */ + public function getIdentifierQuoteCharacter() + { + return '`'; + } + + /** + * Returns the regular expression operator. + * + * @return string + * @override + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return string to generate float between 0 and 1 + */ + public function getRandomExpression() + { + return 'RAND()'; + } + /** * Return string to call a variable with the current timestamp inside an SQL statement * There are three special variables for current date and time: @@ -119,7 +265,43 @@ class MsSqlPlatform extends AbstractPlatform { return 'NEWID()'; } + + /** + * Whether the platform prefers identity columns for ID generation. + * MsSql prefers "autoincrement" identity columns since sequences can only + * be emulated with a table. + * + * @return boolean + * @override + */ + public function prefersIdentityColumns() + { + return true; + } + /** + * Whether the platform supports identity columns. + * MsSql supports this through AUTO_INCREMENT columns. + * + * @return boolean + * @override + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * Whether the platform supports savepoints. MsSql does not. + * + * @return boolean + * @override + */ + public function supportsSavepoints() + { + return false; + } + /** * Obtain DBMS specific SQL code portion needed to declare an text type * field to be used in statements like CREATE TABLE. @@ -280,37 +462,53 @@ class MsSqlPlatform extends AbstractPlatform 'unsigned' => $unsigned, 'fixed' => $fixed); } - + /** - * Quote a string so it can be safely used as a table / column name + * Enter description here... * - * Quoting style depends on which database driver is being used. - * - * @param string $identifier identifier name to be quoted - * @param bool $checkOption check the 'quote_identifier' option - * - * @return string quoted identifier string + * @return unknown * @override */ - public function quoteIdentifier($identifier, $checkOption = false) + public function getShowDatabasesSql() { - if ($checkOption && ! $this->getAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER)) { - return $identifier; - } - - if (strpos($identifier, '.') !== false) { - $parts = explode('.', $identifier); - $quotedParts = array(); - foreach ($parts as $p) { - $quotedParts[] = $this->quoteIdentifier($p); - } - - return implode('.', $quotedParts); - } - - return '[' . str_replace(']', ']]', $identifier) . ']'; + return 'SHOW DATABASES'; } + /** + * Enter description here... + * + * @todo Throw exception by default? + * @override + */ + public function getListTablesSql() + { + return 'SHOW TABLES'; + } + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @return string + * @override + */ + public function getCreateDatabaseSql($name) + { + return 'CREATE DATABASE ' . $this->quoteIdentifier($name); + } + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return string + * @override + */ + public function getDropDatabaseSql($name) + { + return 'DROP DATABASE ' . $this->quoteIdentifier($name); + } + /** * Enter description here... * @@ -321,5 +519,63 @@ class MsSqlPlatform extends AbstractPlatform { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSql($level); } - + + public function getIntegerTypeDeclarationSql(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSql($field); + } + + /** @override */ + public function getBigIntTypeDeclarationSql(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSql($field); + } + + /** @override */ + public function getSmallIntTypeDeclarationSql(array $field) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSql($field); + } + + public function getVarcharTypeDeclarationSql(array $field) + { + if ( ! isset($field['length'])) { + if (array_key_exists('default', $field)) { + $field['length'] = $this->getVarcharMaxLength(); + } else { + $field['length'] = false; + } + } + + $length = ($field['length'] <= $this->getVarcharMaxLength()) ? $field['length'] : false; + $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; + + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); + } + + /** @override */ + protected function _getCommonIntegerTypeDeclarationSql(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } + $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; + + return $unsigned . $autoinc; + } + + /** + * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the charset + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + public function getCharsetFieldDeclaration($charset) + { + return 'CHARACTER SET ' . $charset; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/AllTests.php b/tests/Doctrine/Tests/DBAL/AllTests.php index 2830aa017..6f0beb318 100644 --- a/tests/Doctrine/Tests/DBAL/AllTests.php +++ b/tests/Doctrine/Tests/DBAL/AllTests.php @@ -25,7 +25,8 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\DBAL\Platforms\SqlitePlatformTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Platforms\MySqlPlatformTest'); $suite->addTestSuite('Doctrine\Tests\DBAL\Platforms\PostgreSqlPlatformTest'); - + $suite->addTestSuite('Doctrine\Tests\DBAL\Platforms\MsSqlPlatformTest'); + return $suite; } } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/MssqlPlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/MssqlPlatformTest.php new file mode 100644 index 000000000..77cb5c1af --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Platforms/MssqlPlatformTest.php @@ -0,0 +1,166 @@ +_platform = new MssqlPlatform; + } + + public function testCreateTableSql() + { + $columns = array( + 'id' => array( + 'type' => Type::getType('integer'), + 'autoincrement' => true, + 'primary' => true, + 'notnull' => true + ), + 'test' => array( + 'type' => Type::getType('string'), + 'length' => 255, + 'notnull' => true + ) + ); + + $options = array( + 'primary' => array('id') + ); + + $sql = $this->_platform->getCreateTableSql('test', $columns, $options); + $this->assertEquals('CREATE TABLE test (id INT AUTO_INCREMENT NOT NULL, test VARCHAR(255) NOT NULL, PRIMARY KEY(id))', $sql[0]); + } + + public function testAlterTableSql() + { + $changes = array( + 'name' => 'userlist', + 'add' => array( + 'quota' => array( + 'type' => Type::getType('integer'), + 'unsigned' => 1 + ) + )); + + $this->assertEquals( + 'ALTER TABLE mytable RENAME TO userlist, ADD quota INT UNSIGNED DEFAULT NULL', + $this->_platform->getAlterTableSql('mytable', $changes) + ); + } + + public function testCreateIndexSql() + { + $indexDef = array( + 'fields' => array( + 'user_name' => array( + 'sorting' => 'ASC', + 'length' => 10 + ), + 'last_login' => array() + ) + ); + + $this->assertEquals( + 'CREATE INDEX my_idx ON mytable (user_name, last_login)', + $this->_platform->getCreateIndexSql('mytable', 'my_idx', $indexDef) + ); + } + + public function testSqlSnippets() + { + $this->assertEquals('RLIKE', $this->_platform->getRegexpExpression()); + $this->assertEquals('`', $this->_platform->getIdentifierQuoteCharacter()); + $this->assertEquals('RAND()', $this->_platform->getRandomExpression()); + $this->assertEquals('(column1 + column2 + column3)', $this->_platform->getConcatExpression('column1', 'column2', 'column3')); + $this->assertEquals('CHARACTER SET utf8', $this->_platform->getCharsetFieldDeclaration('utf8')); + $this->assertEquals( + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED', + $this->_platform->getSetTransactionIsolationSql(\Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED) + ); + $this->assertEquals( + 'SET TRANSACTION ISOLATION LEVEL READ COMMITTED', + $this->_platform->getSetTransactionIsolationSql(\Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED) + ); + $this->assertEquals( + 'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ', + $this->_platform->getSetTransactionIsolationSql(\Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ) + ); + $this->assertEquals( + 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', + $this->_platform->getSetTransactionIsolationSql(\Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE) + ); + } + + public function testDDLSnippets() + { + $this->assertEquals('SHOW DATABASES', $this->_platform->getShowDatabasesSql()); + $this->assertEquals('CREATE DATABASE foobar', $this->_platform->getCreateDatabaseSql('foobar')); + $this->assertEquals('DROP DATABASE foobar', $this->_platform->getDropDatabaseSql('foobar')); + $this->assertEquals('DROP TABLE foobar', $this->_platform->getDropTableSql('foobar')); + } + + public function testTypeDeclarationSql() + { + $this->assertEquals( + 'INT', + $this->_platform->getIntegerTypeDeclarationSql(array()) + ); + $this->assertEquals( + 'INT AUTO_INCREMENT', + $this->_platform->getIntegerTypeDeclarationSql(array('autoincrement' => true) + )); + $this->assertEquals( + 'INT AUTO_INCREMENT', + $this->_platform->getIntegerTypeDeclarationSql( + array('autoincrement' => true, 'primary' => true) + )); + $this->assertEquals( + 'CHAR(10)', + $this->_platform->getVarcharTypeDeclarationSql( + array('length' => 10, 'fixed' => true) + )); + $this->assertEquals( + 'VARCHAR(50)', + $this->_platform->getVarcharTypeDeclarationSql(array('length' => 50)) + ); + $this->assertEquals( + 'TEXT', + $this->_platform->getVarcharTypeDeclarationSql(array()) + ); + } + + public function testPreferences() + { + $this->assertTrue($this->_platform->prefersIdentityColumns()); + $this->assertTrue($this->_platform->supportsIdentityColumns()); + $this->assertFalse($this->_platform->supportsSavepoints()); + + } + + public function testGetCreateConstraintSql() + { + $sql = $this->_platform->getCreateConstraintSql('test', 'constraint_name', array('fields' => array('test' => array()))); + $this->assertEquals($sql, 'ALTER TABLE test ADD CONSTRAINT constraint_name (test)'); + } + + public function testGetCreateIndexSql() + { + $sql = $this->_platform->getCreateIndexSql('test', 'index_name', array('type' => 'unique', 'fields' => array('test', 'test2'))); + $this->assertEquals($sql, 'CREATE UNIQUE INDEX index_name ON test (test, test2)'); + } + + public function testGetCreateForeignKeySql() + { + $sql = $this->_platform->getCreateForeignKeySql('test', array('foreignTable' => 'other_table', 'local' => 'fk_name_id', 'foreign' => 'id')); + $this->assertEquals($sql, 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table(id)'); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php index bce833c56..d752298fc 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/MySqlPlatformTest.php @@ -146,7 +146,6 @@ class MySqlPlatformTest extends \Doctrine\Tests\DbalTestCase } -/* public function testGetCreateConstraintSql() { $sql = $this->_platform->getCreateConstraintSql('test', 'constraint_name', array('fields' => array('test' => array()))); @@ -164,7 +163,4 @@ class MySqlPlatformTest extends \Doctrine\Tests\DbalTestCase $sql = $this->_platform->getCreateForeignKeySql('test', array('foreignTable' => 'other_table', 'local' => 'fk_name_id', 'foreign' => 'id')); $this->assertEquals($sql, 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table(id)'); } - - - */ } \ No newline at end of file