diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index ee92654082d..df990d3a31f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -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) diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index 15f9b08fffa..8ccfa725ee3 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -166,29 +166,6 @@ public function loadTableSchema($name) } } - /** - * 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. diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index 8ebc714f46b..4c23a1bcd7f 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -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'); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index a4cb39ed18b..5dc3cbeefa7 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -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 @@ public function testBooleanValues() $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 @@ public static function tableName() { 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(), + ]; + } +} + diff --git a/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php b/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php index 9654c794802..5f6c403bab0 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php @@ -47,14 +47,19 @@ public function getExpectedColumns() $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 @@ public function testGetPDOType() [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], ];