Commit 11fd2c9a authored by Marco Pivetta's avatar Marco Pivetta

Merge branch 'hotfix/#636-backport-#625-pgsql-boolean-conversion' into 2.4

parents 38db41d8 339aa795
......@@ -20,5 +20,4 @@ before_script:
- sh -c "if [ '$DB' = 'mysqli' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- composer --prefer-source --dev install
script: phpunit --configuration tests/travis/$DB.travis.xml
script: ./vendor/bin/phpunit --configuration tests/travis/$DB.travis.xml
......@@ -2214,13 +2214,13 @@ abstract class AbstractPlatform
}
/**
* Some platforms need the boolean values to be converted.
* Note: if the input is not a boolean the original input might be returned.
*
* The default conversion in this implementation converts to integers (false => 0, true => 1).
* There are two contexts when converting booleans: Literals and Prepared Statements.
* This method should handle the literal case
*
* @param mixed $item
*
* @return mixed
* @param mixed $item A boolean or an array of them.
* @return mixed A boolean database value or an array of them.
*/
public function convertBooleans($item)
{
......@@ -2230,13 +2230,42 @@ abstract class AbstractPlatform
$item[$k] = (int) $value;
}
}
} else if (is_bool($item)) {
} elseif (is_bool($item)) {
$item = (int) $item;
}
return $item;
}
/**
* Some platforms have boolean literals that needs to be correctly converted
*
* The default conversion tries to convert value into bool "(bool)$item"
*
* @param mixed $item
*
* @return bool|null
*/
public function convertFromBoolean($item)
{
return null === $item ? null : (bool)$item;
}
/**
* This method should handle the prepared statements case. When there is no
* distinction, it's OK to use the same method.
*
* Note: if the input is not a boolean the original input might be returned.
*
* @param mixed $item A boolean or an array of them.
* @return mixed A boolean database value or an array of them.
*/
public function convertBooleansToDatabaseValue($item)
{
return $this->convertBooleans($item);
}
/**
* Returns the SQL specific for the platform to get the current date.
*
......
......@@ -50,6 +50,28 @@ class PostgreSqlPlatform extends AbstractPlatform
$this->useBooleanTrueFalseStrings = (bool)$flag;
}
/**
* @var array PostgreSQL booleans literals
*/
private $booleanLiterals = array(
'true' => array(
't',
'true',
'y',
'yes',
'on',
'1'
),
'false' => array(
'f',
'false',
'n',
'no',
'off',
'0'
)
);
/**
* {@inheritDoc}
*/
......@@ -573,6 +595,71 @@ class PostgreSqlPlatform extends AbstractPlatform
return $sql;
}
/**
* Converts a single boolean value.
*
* First converts the value to its native PHP boolean type
* and passes it to the given callback function to be reconverted
* into any custom representation.
*
* @param mixed $value The value to convert.
* @param callable $callback The callback function to use for converting the real boolean value.
*
* @return mixed
*/
private function convertSingleBooleanValue($value, $callback)
{
if (null === $value) {
return $callback(false);
}
if (is_bool($value) || is_numeric($value)) {
return $callback($value ? true : false);
}
if (!is_string($value)) {
return $callback(true);
}
/**
* Better safe than sorry: http://php.net/in_array#106319
*/
if (in_array(trim(strtolower($value)), $this->booleanLiterals['false'], true)) {
return $callback(false);
}
if (in_array(trim(strtolower($value)), $this->booleanLiterals['true'], true)) {
return $callback(true);
}
throw new \UnexpectedValueException("Unrecognized boolean literal '${value}'");
}
/**
* Converts one or multiple boolean values.
*
* First converts the value(s) to their native PHP boolean type
* and passes them to the given callback function to be reconverted
* into any custom representation.
*
* @param mixed $item The value(s) to convert.
* @param callable $callback The callback function to use for converting the real boolean value(s).
*
* @return mixed
*/
private function doConvertBooleans($item, $callback)
{
if (is_array($item)) {
foreach ($item as $key => $value) {
$item[$key] = $this->convertSingleBooleanValue($value, $callback);
}
return $item;
}
return $this->convertSingleBooleanValue($item, $callback);
}
/**
* {@inheritDoc}
*
......@@ -584,19 +671,41 @@ class PostgreSqlPlatform extends AbstractPlatform
return parent::convertBooleans($item);
}
if (is_array($item)) {
foreach ($item as $key => $value) {
if (is_bool($value) || is_numeric($item)) {
$item[$key] = ($value) ? 'true' : 'false';
return $this->doConvertBooleans(
$item,
function ($boolean) {
return true === $boolean ? 'true' : 'false';
}
);
}
} else {
if (is_bool($item) || is_numeric($item)) {
$item = ($item) ? 'true' : 'false';
/**
* {@inheritDoc}
*/
public function convertBooleansToDatabaseValue($item)
{
if ( ! $this->useBooleanTrueFalseStrings) {
return parent::convertBooleansToDatabaseValue($item);
}
return $this->doConvertBooleans(
$item,
function ($boolean) {
return (int) $boolean;
}
);
}
/**
* {@inheritDoc}
*/
public function convertFromBoolean($item)
{
if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) {
return false;
}
return $item;
return parent::convertFromBoolean($item);
}
/**
......
......@@ -167,7 +167,7 @@ class SqliteSchemaManager extends AbstractSchemaManager
$stmt = $this->_conn->executeQuery( "PRAGMA TABLE_INFO ('$tableName')" );
$indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC);
foreach($indexArray as $indexColumnRow) {
if($indexColumnRow['pk'] == "1") {
if($indexColumnRow['pk'] !== "0") {
$indexBuffer[] = array(
'key_name' => 'primary',
'primary' => true,
......
......@@ -41,7 +41,7 @@ class BooleanType extends Type
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return $platform->convertBooleans($value);
return $platform->convertBooleansToDatabaseValue($value);
}
/**
......@@ -49,7 +49,7 @@ class BooleanType extends Type
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return (null === $value) ? null : (bool) $value;
return $platform->convertFromBoolean($value);
}
/**
......
<?php
namespace Doctrine\Tests\DBAL\Functional\Ticket;
use Doctrine\DBAL\DBALException;
use PDO;
/**
* @group DBAL-630
*/
class DBAL630Test extends \Doctrine\Tests\DbalFunctionalTestCase
{
private $running = false;
protected function setUp()
{
parent::setUp();
$platform = $this->_conn->getDatabasePlatform()->getName();
if (!in_array($platform, array('postgresql'))) {
$this->markTestSkipped('Currently restricted to PostgreSQL');
}
try {
$this->_conn->exec('CREATE TABLE dbal630 (id SERIAL, bool_col BOOLEAN NOT NULL);');
} catch (DBALException $e) {
}
$this->running = true;
}
protected function tearDown()
{
if ($this->running) {
$this->_conn->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT is deprecated in php 5.6. PDO::ATTR_EMULATE_PREPARES should
// be used instead. so should only it be set when it is supported.
if (PHP_VERSION_ID < 50600) {
$this->_conn->getWrappedConnection()->setAttribute(
PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT,
false
);
}
}
}
public function testBooleanConversionSqlLiteral()
{
$this->_conn->executeUpdate('INSERT INTO dbal630 (bool_col) VALUES(false)');
$id = $this->_conn->lastInsertId('dbal630_id_seq');
$this->assertNotEmpty($id);
$row = $this->_conn->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', array($id));
$this->assertFalse($row['bool_col']);
}
public function testBooleanConversionBoolParamRealPrepares()
{
$this->_conn->executeUpdate('INSERT INTO dbal630 (bool_col) VALUES(?)', array('false'), array(PDO::PARAM_BOOL));
$id = $this->_conn->lastInsertId('dbal630_id_seq');
$this->assertNotEmpty($id);
$row = $this->_conn->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', array($id));
$this->assertFalse($row['bool_col']);
}
public function testBooleanConversionBoolParamEmulatedPrepares()
{
$this->_conn->getWrappedConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
// PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT is deprecated in php 5.6. PDO::ATTR_EMULATE_PREPARES should
// be used instead. so should only it be set when it is supported.
if (PHP_VERSION_ID < 50600) {
$this->_conn->getWrappedConnection()->setAttribute(PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT, true);
}
$platform = $this->_conn->getDatabasePlatform();
$stmt = $this->_conn->prepare('INSERT INTO dbal630 (bool_col) VALUES(?)');
$stmt->bindValue(1, $platform->convertBooleansToDatabaseValue('false'), PDO::PARAM_BOOL);
$stmt->execute();
$id = $this->_conn->lastInsertId('dbal630_id_seq');
$this->assertNotEmpty($id);
$row = $this->_conn->fetchAssoc('SELECT bool_col FROM dbal630 WHERE id = ?', array($id));
$this->assertFalse($row['bool_col']);
}
}
......@@ -296,25 +296,118 @@ class PostgreSqlPlatformTest extends AbstractPlatformTestCase
/**
* @group DBAL-457
* @dataProvider pgBooleanProvider
*
* @param string $databaseValue
* @param string $preparedStatementValue
* @param integer $integerValue
* @param boolean $booleanValue
*/
public function testConvertBooleanAsStrings()
{
public function testConvertBooleanAsLiteralStrings(
$databaseValue,
$preparedStatementValue,
$integerValue,
$booleanValue
) {
$platform = $this->createPlatform();
$this->assertEquals('true', $platform->convertBooleans(true));
$this->assertEquals('false', $platform->convertBooleans(false));
$this->assertEquals($preparedStatementValue, $platform->convertBooleans($databaseValue));
}
/**
* @group DBAL-457
*/
public function testConvertBooleanAsIntegers()
public function testConvertBooleanAsLiteralIntegers()
{
$platform = $this->createPlatform();
$platform->setUseBooleanTrueFalseStrings(false);
$this->assertEquals(1, $platform->convertBooleans(true));
$this->assertEquals(1, $platform->convertBooleans('1'));
$this->assertEquals(0, $platform->convertBooleans(false));
$this->assertEquals(0, $platform->convertBooleans('0'));
}
/**
* @group DBAL-630
* @dataProvider pgBooleanProvider
*
* @param string $databaseValue
* @param string $preparedStatementValue
* @param integer $integerValue
* @param boolean $booleanValue
*/
public function testConvertBooleanAsDatabaseValueStrings(
$databaseValue,
$preparedStatementValue,
$integerValue,
$booleanValue
) {
$platform = $this->createPlatform();
$this->assertSame($integerValue, $platform->convertBooleansToDatabaseValue($booleanValue));
}
/**
* @group DBAL-630
*/
public function testConvertBooleanAsDatabaseValueIntegers()
{
$platform = $this->createPlatform();
$platform->setUseBooleanTrueFalseStrings(false);
$this->assertEquals('1', $platform->convertBooleans(true));
$this->assertEquals('0', $platform->convertBooleans(false));
$this->assertSame(1, $platform->convertBooleansToDatabaseValue(true));
$this->assertSame(0, $platform->convertBooleansToDatabaseValue(false));
}
/**
* @dataProvider pgBooleanProvider
*
* @param string $databaseValue
* @param string $prepareStatementValue
* @param integer $integerValue
* @param boolean $booleanValue
*/
public function testConvertFromBoolean($databaseValue, $prepareStatementValue, $integerValue, $booleanValue)
{
$platform = $this->createPlatform();
$this->assertSame($booleanValue, $platform->convertFromBoolean($databaseValue));
}
/**
* @expectedException UnexpectedValueException
* @expectedExceptionMessage Unrecognized boolean literal 'my-bool'
*/
public function testThrowsExceptionWithInvalidBooleanLiteral()
{
$platform = $this->createPlatform()->convertBooleansToDatabaseValue("my-bool");
}
/**
* PostgreSQL boolean strings provider
* @return array
*/
public function pgBooleanProvider()
{
return array(
// Database value, prepared statement value, boolean integer value, boolean value.
array(true, 'true', 1, true),
array('t', 'true', 1, true),
array('true', 'true', 1, true),
array('y', 'true', 1, true),
array('yes', 'true', 1, true),
array('on', 'true', 1, true),
array('1', 'true', 1, true),
array(false, 'false', 0, false),
array('f', 'false', 0, false),
array('false', 'false', 0, false),
array('n', 'false', 0, false),
array('no', 'false', 0, false),
array('off', 'false', 0, false),
array('0', 'false', 0, false),
);
}
public function testAlterDecimalPrecisionScale()
......
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