. */ namespace Doctrine\DBAL\Platforms; use \Doctrine\DBAL\Schema\TableDiff; 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 { /** * the constructor */ public function __construct() { parent::__construct(); } /** * Adds an adapter-specific LIMIT clause to the SELECT statement. * [ borrowed from Zend Framework ] * * @param string $query * @param mixed $limit * @param mixed $offset * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html * @return string * @override */ public function writeLimitClause($query, $limit = false, $offset = false) { if ($limit > 0) { $count = intval($limit); $offset = intval($offset); if ($offset < 0) { throw \Doctrine\Common\DoctrineException::limitOffsetInvalid($offset); } $orderby = stristr($query, 'ORDER BY'); if ($orderby !== false) { $sort = (stripos($orderby, 'desc') !== false) ? 'desc' : 'asc'; $order = str_ireplace('ORDER BY', '', $orderby); $order = trim(preg_replace('/ASC|DESC/i', '', $order)); } $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($count+$offset) . ' ', $query); $query = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $query . ') AS inner_tbl'; if ($orderby !== false) { $query .= ' ORDER BY ' . $order . ' '; $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC'; } $query .= ') AS outer_tbl'; if ($orderby !== false) { $query .= ' ORDER BY ' . $order . ' ' . $sort; } return $query; } return $query; } /** * Gets the sql statements for altering an existing table. * * The method returns an array of sql statements, since some platforms need several statements. * * @param TableDiff $diff * @return array */ public function getAlterTableSql(TableDiff $diff) { $queryParts = array(); if ($diff->newName !== false) { $queryParts[] = 'RENAME TO ' . $diff->newName; } foreach ($diff->addedColumns AS $fieldName => $column) { $queryParts[] = 'ADD ' . $this->getColumnDeclarationSql($column->getName(), $column->toArray()); } foreach ($diff->removedColumns AS $column) { $queryParts[] = 'DROP ' . $column->getName(); } foreach ($diff->changedColumns AS $columnDiff) { /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */ $column = $columnDiff->column; $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' ' . $this->getColumnDeclarationSql($column->getName(), $column->toArray()); } foreach ($diff->renamedColumns AS $oldColumnName => $column) { $queryParts[] = 'CHANGE ' . $oldColumnName . ' ' . $this->getColumnDeclarationSql($column->getName(), $column->toArray()); } $sql = array(); if (count($queryParts) > 0) { $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(", ", $queryParts); } $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySql($diff)); return $sql; } /** * 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: * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) * - CURRENT_DATE (date, DATE type) * - CURRENT_TIME (time, TIME type) * * @return string to call a variable with the current timestamp * @override */ public function getNowExpression($type = 'timestamp') { switch ($type) { case 'time': case 'date': case 'timestamp': default: return 'GETDATE()'; } } /** * return string to call a function to get a substring inside an SQL statement * * @return string to call a function to get a substring * @override */ public function getSubstringExpression($value, $position, $length = null) { if ( ! is_null($length)) { return 'SUBSTRING(' . $value . ', ' . $position . ', ' . $length . ')'; } return 'SUBSTRING(' . $value . ', ' . $position . ', LEN(' . $value . ') - ' . $position . ' + 1)'; } /** * Returns string to concatenate two or more string parameters * * @param string $arg1 * @param string $arg2 * @param string $values... * @return string to concatenate two strings * @override */ public function getConcatExpression() { $args = func_get_args(); return '(' . implode(' + ', $args) . ')'; } /** * Returns global unique identifier * * @return string to get global unique identifier * @override */ public function getGuidExpression() { 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; } public function getShowDatabasesSql() { return 'SHOW DATABASES'; } 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 ' . $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 ' . $name; } public function getSetTransactionIsolationSql($level) { return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSql($level); } /** * @override */ 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 */ public function getClobTypeDeclarationSql(array $field) { return '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; } /** * @override */ public function getDateTimeTypeDeclarationSql(array $fieldDeclaration) { return 'CHAR(' . strlen('YYYY-MM-DD HH:MM:SS') . ')'; } /** * @override */ public function getDateTypeDeclarationSql(array $fieldDeclaration) { return 'CHAR(' . strlen('YYYY-MM-DD') . ')'; } /** * @override */ public function getTimeTypeDeclarationSql(array $fieldDeclaration) { return 'CHAR(' . strlen('HH:MM:SS') . ')'; } /** * @override */ public function getBooleanTypeDeclarationSql(array $field) { return 'BIT'; } /** * Get the platform name for this instance * * @return string */ public function getName() { return 'mssql'; } /** * Adds an adapter-specific LIMIT clause to the SELECT statement. * * @param string $query * @param mixed $limit * @param mixed $offset * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html * @return string */ public function modifyLimitQuery($query, $limit, $offset = null) { if ($limit > 0) { $count = intval($limit); $offset = intval($offset); if ($offset < 0) { throw new Doctrine_Connection_Exception("LIMIT argument offset=$offset is not valid"); } $orderby = stristr($query, 'ORDER BY'); if ($orderby !== false) { // Ticket #1835: Fix for ORDER BY alias // Ticket #2050: Fix for multiple ORDER BY clause $order = str_ireplace('ORDER BY', '', $orderby); $orders = explode(',', $order); for ($i = 0; $i < count($orders); $i++) { $sorts[$i] = (stripos($orders[$i], ' DESC') !== false) ? 'DESC' : 'ASC'; $orders[$i] = trim(preg_replace('/\s+(ASC|DESC)$/i', '', $orders[$i])); // find alias in query string $helperString = stristr($query, $orders[$i]); $fromClausePos = strpos($helperString, ' FROM '); $fieldsString = substr($helperString, 0, $fromClausePos + 1); $fieldArray = explode(',', $fieldsString); $fieldArray = array_shift($fieldArray); $aux2 = preg_split('/ as /i', $fieldArray); $aliases[$i] = trim(end($aux2)); } } // Ticket #1259: Fix for limit-subquery in MSSQL $selectRegExp = 'SELECT\s+'; $selectReplace = 'SELECT '; if (preg_match('/^SELECT(\s+)DISTINCT/i', $query)) { $selectRegExp .= 'DISTINCT\s+'; $selectReplace .= 'DISTINCT '; } $query = preg_replace('/^'.$selectRegExp.'/i', $selectReplace . 'TOP ' . ($count + $offset) . ' ', $query); $query = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $query . ') AS ' . 'inner_tbl'; if ($orderby !== false) { $query .= ' ORDER BY '; for ($i = 0, $l = count($orders); $i < $l; $i++) { if ($i > 0) { // not first order clause $query .= ', '; } $query .= 'inner_tbl' . '.' . $aliases[$i] . ' '; $query .= (stripos($sorts[$i], 'ASC') !== false) ? 'DESC' : 'ASC'; } } $query .= ') AS ' . 'outer_tbl'; if ($orderby !== false) { $query .= ' ORDER BY '; for ($i = 0, $l = count($orders); $i < $l; $i++) { if ($i > 0) { // not first order clause $query .= ', '; } $query .= 'outer_tbl' . '.' . $aliases[$i] . ' ' . $sorts[$i]; } } } return $query; } /** * Get the insert sql for an empty insert statement * * @param string $tableName * @param string $identifierColumnName * @return string $sql */ public function getEmptyIdentityInsertSql($quotedTableName, $quotedIdentifierColumnName) { return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; } /** * @inheritdoc */ public function getTruncateTableSql($tableName, $cascade = false) { return 'TRUNCATE TABLE '.$tableName; } }