Commit 2bfbde37 authored by Benjamin Eberlei's avatar Benjamin Eberlei

Merge remote branch 'juokaz/sqlsrv-driver' into sqlsrv

parents 454d1e0b 43048d56
<?php
/*
* $Id$
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Driver\PDOMsSql;
/**
* The PDO-based MsSql driver.
*
* @since 2.0
*/
class Driver implements \Doctrine\DBAL\Driver
{
public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
{
return new Connection(
$this->_constructPdoDsn($params),
$username,
$password,
$driverOptions
);
}
/**
* Constructs the MsSql PDO DSN.
*
* @return string The DSN.
*/
private function _constructPdoDsn(array $params)
{
// TODO: This might need to be revisted once we have access to a mssql server
$dsn = 'mssql:';
if (isset($params['host'])) {
$dsn .= 'host=' . $params['host'] . ';';
}
if (isset($params['port'])) {
$dsn .= 'port=' . $params['port'] . ';';
}
if (isset($params['dbname'])) {
$dsn .= 'dbname=' . $params['dbname'] . ';';
}
return $dsn;
}
public function getDatabasePlatform()
{
return new \Doctrine\DBAL\Platforms\MsSqlPlatform();
}
public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
{
return new \Doctrine\DBAL\Schema\MsSqlSchemaManager($conn);
}
public function getName()
{
return 'pdo_mssql';
}
public function getDatabase(\Doctrine\DBAL\Connection $conn)
{
$params = $conn->getParams();
return $params['dbname'];
}
}
\ No newline at end of file
......@@ -19,42 +19,27 @@
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Driver\PDOMsSql;
namespace Doctrine\DBAL\Driver\PDOSqlsrv;
/**
* MsSql Connection implementation.
* Sqlsrv Connection implementation.
*
* @since 2.0
*/
class Connection extends \PDO implements \Doctrine\DBAL\Driver\Connection
class Connection extends \Doctrine\DBAL\Driver\PDOConnection implements \Doctrine\DBAL\Driver\Connection
{
/**
* Performs the rollback.
*
* @override
*/
public function rollback()
public function quote($value, $type=\PDO::PARAM_STR)
{
$this->exec('ROLLBACK TRANSACTION');
}
/**
* Performs the commit.
*
* @override
*/
public function commit()
{
$this->exec('COMMIT TRANSACTION');
}
/**
* Begins a database transaction.
*
* @override
*/
public function beginTransaction()
{
$this->exec('BEGIN TRANSACTION');
$val = parent::quote($value, $type);
// Fix for a driver version terminating all values with null byte
if (strpos($val, "\0") !== false) {
$val = substr($val, 0, -1);
}
return $val;
}
}
\ No newline at end of file
......@@ -30,7 +30,7 @@ class Driver implements \Doctrine\DBAL\Driver
{
public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
{
return new \Doctrine\DBAL\Driver\PDOConnection(
return new Connection(
$this->_constructPdoDsn($params),
$username,
$password,
......
......@@ -40,7 +40,6 @@ final class DriverManager
'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver',
'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver',
'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver',
'pdo_mssql' => 'Doctrine\DBAL\Driver\PDOMsSql\Driver',
'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver',
'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver',
'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver',
......@@ -62,7 +61,7 @@ final class DriverManager
* pdo_sqlite
* pdo_pgsql
* pdo_oracle
* pdo_mssql
* pdo_sqlsrv
*
* OR 'driverClass' that contains the full class name (with namespace) of the
* driver class to instantiate.
......
......@@ -93,19 +93,15 @@ class MsSqlPlatform extends AbstractPlatform
*/
public function getDropDatabaseSQL($name)
{
// @todo do we really need to force drop?
return 'ALTER DATABASE [' . $name . ']
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE ' . $name . ';';
return 'DROP DATABASE ' . $name;
}
/**
* @override
*/
public function quoteIdentifier($str)
public function supportsCreateDropDatabase()
{
return '[' . $str . ']';
return false;
}
/**
......@@ -144,9 +140,9 @@ DROP DATABASE ' . $name . ';';
}
return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
ALTER TABLE " . $this->quoteIdentifier($table) . " DROP CONSTRAINT " . $this->quoteIdentifier($index) . "
ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . "
ELSE
DROP INDEX " . $this->quoteIdentifier($index) . " ON " . $this->quoteIdentifier($table);
DROP INDEX " . $index . " ON " . $table;
}
}
......@@ -155,27 +151,22 @@ DROP DATABASE ' . $name . ';';
*/
public function getCreateTableSQL(Table $table, $createFlags=self::CREATE_INDEXES)
{
$sql = parent::getCreateTableSQL($table, $createFlags);
$primary = array();
foreach ($table->getIndexes() AS $index) {
/* @var $index Index */
if ($index->isPrimary()) {
$primary = $index->getColumns();
}
}
if (count($primary) === 1) {
foreach ($table->getForeignKeys() AS $definition) {
$columns = $definition->getLocalColumns();
if (count($columns) === 1 && in_array($columns[0], $primary)) {
$sql[0] = str_replace(' IDENTITY', '', $sql[0]);
}
}
}
return $sql;
// Foreign keys cannot be identity columns at the same time
foreach ($table->getForeignKeys() AS $definition) {
$columns = $definition->getLocalColumns();
if (count($columns) != 1)
continue;
foreach ($table->getIndexes() AS $index) {
/* @var $index Index */
if ($index->isPrimary() && in_array($columns[0], $index->getColumns())) {
$table->getColumn($columns[0])->setAutoincrement(false);
}
}
}
return parent::getCreateTableSQL($table, $createFlags);
}
/**
......@@ -183,6 +174,14 @@ DROP DATABASE ' . $name . ';';
*/
protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
{
// @todo does other code breaks because of this?
// foce primary keys to be not null
foreach ($columns as &$column) {
if (isset($column['primary']) && $column['primary']) {
$column['notnull'] = true;
}
}
$columnListSql = $this->getColumnDeclarationListSQL($columns);
if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) {
......@@ -219,6 +218,53 @@ DROP DATABASE ' . $name . ';';
return $sql;
}
/**
* @override
*/
public function getUniqueConstraintDeclarationSQL($name, Index $index)
{
$constraint = parent::getUniqueConstraintDeclarationSQL($name, $index);
$constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
return $constraint;
}
/**
* @override
*/
public function getCreateIndexSQL(Index $index, $table)
{
$constraint = parent::getCreateIndexSQL($index, $table);
if ($index->isUnique()) {
$constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
}
return $constraint;
}
/**
* Extend unique key constraint with required filters
*
* @param string $sql
* @param Index $index
* @return string
*/
private function _appendUniqueConstraintDefinition($sql, Index $index)
{
$fields = array();
foreach ($index->getColumns() as $field => $definition) {
if (!is_array($definition)) {
$field = $definition;
}
$fields[] = $field . ' IS NOT NULL';
}
return $sql . ' WHERE ' . implode(' AND ', $fields);
}
/**
* @override
......@@ -393,19 +439,38 @@ DROP DATABASE ' . $name . ';';
*/
public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
{
// @todo
$trimFn = '';
$trimChar = ($char != false) ? (', ' . $char) : '';
if ($pos == self::TRIM_LEADING) {
$trimFn = 'LTRIM';
} else if ($pos == self::TRIM_TRAILING) {
$trimFn = 'RTRIM';
} else {
return 'LTRIM(RTRIM(' . $str . '))';
}
return $trimFn . '(' . $str . ')';
if (!$char) {
if ($pos == self::TRIM_LEADING) {
$trimFn = 'LTRIM';
} else if ($pos == self::TRIM_TRAILING) {
$trimFn = 'RTRIM';
} else {
return 'LTRIM(RTRIM(' . $str . '))';
}
return $trimFn . '(' . $str . ')';
} else {
/** Original query used to get those expressions
declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x';
declare @pat varchar(10) = '%[^' + @trim_char + ']%';
select @c as string
, @trim_char as trim_char
, stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading
, reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing
, reverse(stuff(reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null)), 1, patindex(@pat, reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null))) - 1, null)) as trim_both;
*/
$pattern = "'%[^' + $char + ']%'";
if ($pos == self::TRIM_LEADING) {
return 'stuff(' . $str . ', 1, patindex(' . $pattern .', ' . $str . ') - 1, null)';
} else if ($pos == self::TRIM_TRAILING) {
return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern .', reverse(' . $str . ')) - 1, null))';
} else {
return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern .', ' . $str . ') - 1, null)), 1, patindex(' . $pattern .', reverse(stuff(' . $str . ', 1, patindex(' . $pattern .', ' . $str . ') - 1, null))) - 1, null))';
}
}
}
/**
......@@ -650,6 +715,9 @@ DROP DATABASE ' . $name . ';';
return 'mssql';
}
/**
* @override
*/
protected function initializeDoctrineTypeMappings()
{
$this->doctrineTypeMapping = array(
......@@ -716,4 +784,28 @@ DROP DATABASE ' . $name . ';';
{
return 'ROLLBACK TRANSACTION ' . $savepoint;
}
/**
* @override
*/
public function appendLockHint($fromClause, $lockMode)
{
// @todo coorect
if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
return $fromClause . ' WITH (tablockx)';
} else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
return $fromClause . ' WITH (tablockx)';
}
else {
return $fromClause;
}
}
/**
* @override
*/
public function getForUpdateSQL()
{
return ' ';
}
}
......@@ -24,7 +24,7 @@ class MsSqlPlatformTest extends AbstractPlatformTestCase
{
return array(
'CREATE TABLE test (foo NVARCHAR(255) DEFAULT NULL, bar NVARCHAR(255) DEFAULT NULL)',
'CREATE UNIQUE INDEX test_foo_bar_uniq ON test (foo, bar)'
'CREATE UNIQUE INDEX test_foo_bar_uniq ON test (foo, bar) WHERE foo IS NOT NULL AND bar IS NOT NULL'
);
}
......@@ -67,12 +67,7 @@ class MsSqlPlatformTest extends AbstractPlatformTestCase
public function testGeneratesDDLSnippets()
{
$dropDatabaseExpectation = <<<DDB
ALTER DATABASE [foobar]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE foobar;
DDB;
$dropDatabaseExpectation = 'DROP DATABASE foobar';
$this->assertEquals('SHOW DATABASES', $this->_platform->getShowDatabasesSQL());
$this->assertEquals('CREATE DATABASE foobar', $this->_platform->getCreateDatabaseSQL('foobar'));
......@@ -138,7 +133,7 @@ DDB;
public function getGenerateUniqueIndexSql()
{
return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
return 'CREATE UNIQUE INDEX index_name ON test (test, test2) WHERE test IS NOT NULL AND test2 IS NOT NULL';
}
public function getGenerateForeignKeySql()
......
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