Commit 03e17189 authored by Marco Pivetta's avatar Marco Pivetta Committed by GitHub

Merge pull request #2688 from jnvsor/isnull

Connection: Support IS NULL in update/delete criteria
parents 638cb41b f4437acb
...@@ -602,6 +602,36 @@ class Connection implements DriverConnection ...@@ -602,6 +602,36 @@ class Connection implements DriverConnection
return $this->_transactionNestingLevel > 0; return $this->_transactionNestingLevel > 0;
} }
/**
* Gathers conditions for an update or delete call.
*
* @param array $identifiers Input array of columns to values
*
* @return string[][] a triplet with:
* - the first key being the columns
* - the second key being the values
* - the third key being the conditions
*/
private function gatherConditions(array $identifiers)
{
$columns = [];
$values = [];
$conditions = [];
foreach ($identifiers as $columnName => $value) {
if (null === $value) {
$conditions[] = $this->getDatabasePlatform()->getIsNullExpression($columnName);
continue;
}
$columns[] = $columnName;
$values[] = $value;
$conditions[] = $columnName . ' = ?';
}
return [$columns, $values, $conditions];
}
/** /**
* Executes an SQL DELETE statement on a table. * Executes an SQL DELETE statement on a table.
* *
...@@ -621,20 +651,12 @@ class Connection implements DriverConnection ...@@ -621,20 +651,12 @@ class Connection implements DriverConnection
throw InvalidArgumentException::fromEmptyCriteria(); throw InvalidArgumentException::fromEmptyCriteria();
} }
$columnList = array(); list($columns, $values, $conditions) = $this->gatherConditions($identifier);
$criteria = array();
$paramValues = array();
foreach ($identifier as $columnName => $value) {
$columnList[] = $columnName;
$criteria[] = $columnName . ' = ?';
$paramValues[] = $value;
}
return $this->executeUpdate( return $this->executeUpdate(
'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $criteria), 'DELETE FROM ' . $tableExpression . ' WHERE ' . implode(' AND ', $conditions),
$paramValues, $values,
is_string(key($types)) ? $this->extractTypeValues($columnList, $types) : $types is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
); );
} }
...@@ -692,31 +714,28 @@ class Connection implements DriverConnection ...@@ -692,31 +714,28 @@ class Connection implements DriverConnection
*/ */
public function update($tableExpression, array $data, array $identifier, array $types = array()) public function update($tableExpression, array $data, array $identifier, array $types = array())
{ {
$columnList = array(); $setColumns = array();
$setValues = array();
$set = array(); $set = array();
$criteria = array();
$paramValues = array();
foreach ($data as $columnName => $value) { foreach ($data as $columnName => $value) {
$columnList[] = $columnName; $setColumns[] = $columnName;
$setValues[] = $value;
$set[] = $columnName . ' = ?'; $set[] = $columnName . ' = ?';
$paramValues[] = $value;
} }
foreach ($identifier as $columnName => $value) { list($conditionColumns, $conditionValues, $conditions) = $this->gatherConditions($identifier);
$columnList[] = $columnName; $columns = array_merge($setColumns, $conditionColumns);
$criteria[] = $columnName . ' = ?'; $values = array_merge($setValues, $conditionValues);
$paramValues[] = $value;
}
if (is_string(key($types))) { if (is_string(key($types))) {
$types = $this->extractTypeValues($columnList, $types); $types = $this->extractTypeValues($columns, $types);
} }
$sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set) $sql = 'UPDATE ' . $tableExpression . ' SET ' . implode(', ', $set)
. ' WHERE ' . implode(' AND ', $criteria); . ' WHERE ' . implode(' AND ', $conditions);
return $this->executeUpdate($sql, $paramValues, $types); return $this->executeUpdate($sql, $values, $types);
} }
/** /**
...@@ -736,21 +755,21 @@ class Connection implements DriverConnection ...@@ -736,21 +755,21 @@ class Connection implements DriverConnection
return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()'); return $this->executeUpdate('INSERT INTO ' . $tableExpression . ' ()' . ' VALUES ()');
} }
$columnList = array(); $columns = array();
$paramPlaceholders = array(); $values = array();
$paramValues = array(); $set = array();
foreach ($data as $columnName => $value) { foreach ($data as $columnName => $value) {
$columnList[] = $columnName; $columns[] = $columnName;
$paramPlaceholders[] = '?'; $values[] = $value;
$paramValues[] = $value; $set[] = '?';
} }
return $this->executeUpdate( return $this->executeUpdate(
'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columnList) . ')' . 'INSERT INTO ' . $tableExpression . ' (' . implode(', ', $columns) . ')' .
' VALUES (' . implode(', ', $paramPlaceholders) . ')', ' VALUES (' . implode(', ', $set) . ')',
$paramValues, $values,
is_string(key($types)) ? $this->extractTypeValues($columnList, $types) : $types is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types
); );
} }
......
...@@ -34,6 +34,22 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -34,6 +34,22 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
$this->_conn = \Doctrine\DBAL\DriverManager::getConnection($this->params); $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($this->params);
} }
public function getExecuteUpdateMockConnection()
{
$driverMock = $this->createMock(\Doctrine\DBAL\Driver::class);
$driverMock->expects($this->any())
->method('connect')
->will($this->returnValue(new DriverConnectionMock()));
$conn = $this->getMockBuilder(Connection::class)
->setMethods(['executeUpdate'])
->setConstructorArgs([['platform' => new Mocks\MockPlatform()], $driverMock])
->getMock();
return $conn;
}
public function testIsConnected() public function testIsConnected()
{ {
$this->assertFalse($this->_conn->isConnected()); $this->assertFalse($this->_conn->isConnected());
...@@ -285,16 +301,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -285,16 +301,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
public function testEmptyInsert() public function testEmptyInsert()
{ {
$driverMock = $this->createMock('Doctrine\DBAL\Driver'); $conn = $this->getExecuteUpdateMockConnection();
$driverMock->expects($this->any())
->method('connect')
->will($this->returnValue(new DriverConnectionMock()));
$conn = $this->getMockBuilder('Doctrine\DBAL\Connection')
->setMethods(array('executeUpdate'))
->setConstructorArgs(array(array('platform' => new Mocks\MockPlatform()), $driverMock))
->getMock();
$conn->expects($this->once()) $conn->expects($this->once())
->method('executeUpdate') ->method('executeUpdate')
...@@ -308,16 +315,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -308,16 +315,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
*/ */
public function testUpdateWithDifferentColumnsInDataAndIdentifiers() public function testUpdateWithDifferentColumnsInDataAndIdentifiers()
{ {
$driverMock = $this->createMock('Doctrine\DBAL\Driver'); $conn = $this->getExecuteUpdateMockConnection();
$driverMock->expects($this->any())
->method('connect')
->will($this->returnValue(new DriverConnectionMock()));
$conn = $this->getMockBuilder('Doctrine\DBAL\Connection')
->setMethods(array('executeUpdate'))
->setConstructorArgs(array(array('platform' => new Mocks\MockPlatform()), $driverMock))
->getMock();
$conn->expects($this->once()) $conn->expects($this->once())
->method('executeUpdate') ->method('executeUpdate')
...@@ -361,16 +359,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -361,16 +359,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
*/ */
public function testUpdateWithSameColumnInDataAndIdentifiers() public function testUpdateWithSameColumnInDataAndIdentifiers()
{ {
$driverMock = $this->createMock('Doctrine\DBAL\Driver'); $conn = $this->getExecuteUpdateMockConnection();
$driverMock->expects($this->any())
->method('connect')
->will($this->returnValue(new DriverConnectionMock()));
$conn = $this->getMockBuilder('Doctrine\DBAL\Connection')
->setMethods(array('executeUpdate'))
->setConstructorArgs(array(array('platform' => new Mocks\MockPlatform()), $driverMock))
->getMock();
$conn->expects($this->once()) $conn->expects($this->once())
->method('executeUpdate') ->method('executeUpdate')
...@@ -408,6 +397,80 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -408,6 +397,80 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
); );
} }
/**
* @group DBAL-2688
*/
public function testUpdateWithIsNull()
{
$conn = $this->getExecuteUpdateMockConnection();
$conn->expects($this->once())
->method('executeUpdate')
->with(
'UPDATE TestTable SET text = ?, is_edited = ? WHERE id IS NULL AND name = ?',
[
'some text',
null,
'foo',
],
[
'string',
'boolean',
'string',
]
);
$conn->update(
'TestTable',
[
'text' => 'some text',
'is_edited' => null,
],
[
'id' => null,
'name' => 'foo',
],
[
'text' => 'string',
'is_edited' => 'boolean',
'id' => 'integer',
'name' => 'string',
]
);
}
/**
* @group DBAL-2688
*/
public function testDeleteWithIsNull()
{
$conn = $this->getExecuteUpdateMockConnection();
$conn->expects($this->once())
->method('executeUpdate')
->with(
'DELETE FROM TestTable WHERE id IS NULL AND name = ?',
[
'foo',
],
[
'string',
]
);
$conn->delete(
'TestTable',
[
'id' => null,
'name' => 'foo',
],
[
'id' => 'integer',
'name' => 'string',
]
);
}
public function testFetchAssoc() public function testFetchAssoc()
{ {
$statement = 'SELECT * FROM foo WHERE bar = ?'; $statement = 'SELECT * FROM foo WHERE bar = ?';
......
...@@ -285,4 +285,44 @@ class WriteTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -285,4 +285,44 @@ class WriteTest extends \Doctrine\Tests\DbalFunctionalTestCase
} }
/**
* @group DBAL-2688
*/
public function testUpdateWhereIsNull()
{
$this->_conn->insert(
'write_table',
['test_int' => '30', 'test_string' => null],
['test_string' => 'string', 'test_int' => 'integer']
);
$data = $this->_conn->fetchAll('SELECT * FROM write_table WHERE test_int = 30');
$this->assertCount(1, $data);
$this->_conn->update('write_table', ['test_int' => 10], ['test_string' => null], ['test_string' => 'string', 'test_int' => 'integer']);
$data = $this->_conn->fetchAll('SELECT * FROM write_table WHERE test_int = 30');
$this->assertCount(0, $data);
}
public function testDeleteWhereIsNull()
{
$this->_conn->insert(
'write_table',
['test_int' => '30', 'test_string' => null],
['test_string' => 'string', 'test_int' => 'integer']
);
$data = $this->_conn->fetchAll('SELECT * FROM write_table WHERE test_int = 30');
$this->assertCount(1, $data);
$this->_conn->delete('write_table', ['test_string' => null], ['test_string' => 'string']);
$data = $this->_conn->fetchAll('SELECT * FROM write_table WHERE test_int = 30');
$this->assertCount(0, $data);
}
} }
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