Commit 78b5c7db by Carsten Brandt

fixed boolean handling for PostgreSQL

- do not allow boolean values for integer columns - use native boolean type with boolean values - removed workaround that turned out to be wrong when schema and values are used correctly. Workaround resulted from wrong usage of boolean values before. fixes #4672
parent f562cbc0
......@@ -82,6 +82,7 @@ Yii Framework 2 Change Log
- Bug #4519: `yii\base\Model::isAttributeRequired()` should check if the `when` option of the validator is set (thiagotalma)
- Bug #4592: Fixed `yii help` command was listing incorrect action names for methods like `actionSayNO` (samdark)
- Bug #4654: Fixed issue with PostgreSQL and inserting boolean values with batch insert (cebe)
- Bug #4672: Fixed issue with PostgreSQL handling of boolean values in queries, dropped support for using boolean value for integer columns (cebe)
- Bug #4727: Fixed wrong Stylus definition in `\yii\web\AssetConverter` (samdark)
- Bug #4813: Fixed MSSQL schema that was getting incorrect info about constraints (samdark, SerjRamone, o-rey)
- Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark)
......
......@@ -167,29 +167,6 @@ class Schema extends \yii\db\Schema
}
/**
* Determines the PDO type for the given PHP data value.
* @param mixed $data the data whose PDO type is to be determined
* @return integer the PDO type
* @see http://www.php.net/manual/en/pdo.constants.php
*/
public function getPdoType($data)
{
// php type => PDO type
static $typeMap = [
// https://github.com/yiisoft/yii2/issues/1115
// Cast boolean to integer values to work around problems with PDO casting false to string '' https://bugs.php.net/bug.php?id=33876
'boolean' => \PDO::PARAM_INT,
'integer' => \PDO::PARAM_INT,
'string' => \PDO::PARAM_STR,
'resource' => \PDO::PARAM_LOB,
'NULL' => \PDO::PARAM_NULL,
];
$type = gettype($data);
return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
}
/**
* Returns all table names in the database.
* @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
......
......@@ -35,6 +35,7 @@ CREATE TABLE "customer" (
name varchar(128),
address text,
status integer DEFAULT 0,
bool_status boolean DEFAULT FALSE,
profile_id integer
);
......@@ -109,8 +110,8 @@ CREATE TABLE "type" (
blob_col bytea,
numeric_col decimal(5,2) DEFAULT '33.22',
time timestamp NOT NULL DEFAULT '2002-01-01 00:00:00',
bool_col smallint NOT NULL,
bool_col2 smallint DEFAULT '1',
bool_col boolean NOT NULL,
bool_col2 boolean DEFAULT TRUE,
ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
bit_col BIT(8) NOT NULL DEFAULT B'10000010'
);
......@@ -125,9 +126,9 @@ CREATE TABLE "bool_values" (
INSERT INTO "profile" (description) VALUES ('profile customer 1');
INSERT INTO "profile" (description) VALUES ('profile customer 3');
INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user1@example.com', 'user1', 'address1', 1, 1);
INSERT INTO "customer" (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1);
INSERT INTO "customer" (email, name, address, status, profile_id) VALUES ('user3@example.com', 'user3', 'address3', 2, 2);
INSERT INTO "customer" (email, name, address, status, bool_status, profile_id) VALUES ('user1@example.com', 'user1', 'address1', 1, true, 1);
INSERT INTO "customer" (email, name, address, status, bool_status) VALUES ('user2@example.com', 'user2', 'address2', 1, true);
INSERT INTO "customer" (email, name, address, status, bool_status, profile_id) VALUES ('user3@example.com', 'user3', 'address3', 2, false, 2);
INSERT INTO "category" (name) VALUES ('Books');
INSERT INTO "category" (name) VALUES ('Movies');
......
......@@ -2,8 +2,12 @@
namespace yiiunit\framework\db\pgsql;
use yii\behaviors\TimestampBehavior;
use yii\db\pgsql\Schema;
use yiiunit\data\ar\ActiveRecord;
use yiiunit\framework\ar\ActiveRecordTestTrait;
use yiiunit\framework\db\ActiveRecordTest;
use yiiunit\TestCase;
/**
* @group db
......@@ -13,6 +17,72 @@ class PostgreSQLActiveRecordTest extends ActiveRecordTest
{
protected $driverName = 'pgsql';
public function testBooleanAttribute()
{
/* @var $customerClass \yii\db\ActiveRecordInterface */
$customerClass = $this->getCustomerClass();
/* @var $this TestCase|ActiveRecordTestTrait */
$customer = new $customerClass();
$customer->name = 'boolean customer';
$customer->email = 'mail@example.com';
$customer->bool_status = false;
$customer->save(false);
$customer->refresh();
$this->assertSame(false, $customer->bool_status);
$customer->bool_status = true;
$customer->save(false);
$customer->refresh();
$this->assertSame(true, $customer->bool_status);
$customers = $customerClass::find()->where(['bool_status' => true])->all();
$this->assertEquals(3, count($customers));
$customers = $customerClass::find()->where(['bool_status' => false])->all();
$this->assertEquals(1, count($customers));
}
public function testFindAsArray()
{
/* @var $customerClass \yii\db\ActiveRecordInterface */
$customerClass = $this->getCustomerClass();
// asArray
$customer = $customerClass::find()->where(['id' => 2])->asArray()->one();
$this->assertEquals([
'id' => 2,
'email' => 'user2@example.com',
'name' => 'user2',
'address' => 'address2',
'status' => 1,
'profile_id' => null,
'bool_status' => true,
], $customer);
// find all asArray
$customers = $customerClass::find()->asArray()->all();
$this->assertEquals(3, count($customers));
$this->assertArrayHasKey('id', $customers[0]);
$this->assertArrayHasKey('name', $customers[0]);
$this->assertArrayHasKey('email', $customers[0]);
$this->assertArrayHasKey('address', $customers[0]);
$this->assertArrayHasKey('status', $customers[0]);
$this->assertArrayHasKey('bool_status', $customers[0]);
$this->assertArrayHasKey('id', $customers[1]);
$this->assertArrayHasKey('name', $customers[1]);
$this->assertArrayHasKey('email', $customers[1]);
$this->assertArrayHasKey('address', $customers[1]);
$this->assertArrayHasKey('status', $customers[1]);
$this->assertArrayHasKey('bool_status', $customers[1]);
$this->assertArrayHasKey('id', $customers[2]);
$this->assertArrayHasKey('name', $customers[2]);
$this->assertArrayHasKey('email', $customers[2]);
$this->assertArrayHasKey('address', $customers[2]);
$this->assertArrayHasKey('status', $customers[2]);
$this->assertArrayHasKey('bool_status', $customers[2]);
}
public function testBooleanValues()
{
......@@ -40,6 +110,42 @@ class PostgreSQLActiveRecordTest extends ActiveRecordTest
$this->assertSame(false, BoolAR::find()->where(['bool_col' => false])->one($db)->bool_col);
}
/**
* https://github.com/yiisoft/yii2/issues/4672
*/
public function testBooleanValues2()
{
$db = $this->getConnection();
$db->charset = 'utf8';
$db->createCommand("DROP TABLE IF EXISTS bool_user;")->execute();
$db->createCommand()->createTable('bool_user', [
'id' => Schema::TYPE_PK,
'username' => Schema::TYPE_STRING . ' NOT NULL',
'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL',
'password_hash' => Schema::TYPE_STRING . ' NOT NULL',
'password_reset_token' => Schema::TYPE_STRING,
'email' => Schema::TYPE_STRING . ' NOT NULL',
'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10',
'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10',
'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
])->execute();
$db->createCommand()->addColumn('bool_user', 'is_deleted', Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT FALSE')->execute();
$user = new UserAR();
$user->username = 'test';
$user->auth_key = 'test';
$user->password_hash = 'test';
$user->email = 'test@example.com';
$user->save(false);
$this->assertEquals(1, count(UserAR::find()->where(['is_deleted' => false])->all($db)));
$this->assertEquals(0, count(UserAR::find()->where(['is_deleted' => true])->all($db)));
$this->assertEquals(1, count(UserAR::find()->where(['is_deleted' => [true, false]])->all($db)));
}
public function testBooleanDefaultValues()
{
$model = new BoolAR();
......@@ -61,4 +167,24 @@ class BoolAR extends ActiveRecord
{
return 'bool_values';
}
}
\ No newline at end of file
}
class UserAR extends ActiveRecord
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
const ROLE_USER = 10;
public static function tableName()
{
return '{{%bool_user}}';
}
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
}
......@@ -47,14 +47,19 @@ class PostgreSQLSchemaTest extends SchemaTest
$columns['blob_col']['type'] = 'binary';
$columns['numeric_col']['dbType'] = 'numeric';
$columns['numeric_col']['size'] = null;
$columns['bool_col']['dbType'] = 'int2';
$columns['bool_col']['type'] = 'boolean';
$columns['bool_col']['phpType'] = 'boolean';
$columns['bool_col']['dbType'] = 'bool';
$columns['bool_col']['size'] = null;
$columns['bool_col']['precision'] = 16;
$columns['bool_col']['scale'] = 0;
$columns['bool_col2']['dbType'] = 'int2';
$columns['bool_col']['precision'] = null;
$columns['bool_col']['scale'] = null;
$columns['bool_col2']['type'] = 'boolean';
$columns['bool_col2']['phpType'] = 'boolean';
$columns['bool_col2']['dbType'] = 'bool';
$columns['bool_col2']['size'] = null;
$columns['bool_col2']['precision'] = 16;
$columns['bool_col2']['scale'] = 0;
$columns['bool_col2']['precision'] = null;
$columns['bool_col2']['scale'] = null;
$columns['bool_col2']['defaultValue'] = true;
$columns['ts_default']['defaultValue'] = new Expression('now()');
$columns['bit_col']['dbType'] = 'bit';
$columns['bit_col']['size'] = 8;
......@@ -71,8 +76,8 @@ class PostgreSQLSchemaTest extends SchemaTest
[0, \PDO::PARAM_INT],
[1, \PDO::PARAM_INT],
[1337, \PDO::PARAM_INT],
[true, \PDO::PARAM_INT],
[false, \PDO::PARAM_INT],
[true, \PDO::PARAM_BOOL],
[false, \PDO::PARAM_BOOL],
[$fp = fopen(__FILE__, 'rb'), \PDO::PARAM_LOB],
];
......
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