From e23f869c69c647e3d16dccca78056bdc51e1f339 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 27 Dec 2013 09:42:51 +0400 Subject: [PATCH 01/13] added postgresql features to reset seq/check integrity --- framework/yii/db/mysql/Schema.php | 2 +- framework/yii/db/pgsql/QueryBuilder.php | 61 +++++++++++++++++++++++++++++++++ framework/yii/db/pgsql/Schema.php | 2 +- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index a649d8a..d5258c1 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -280,7 +280,7 @@ class Schema extends \yii\db\Schema * @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. */ - protected function findTableNames($schema = '') + public function findTableNames($schema = '') { $sql = 'SHOW TABLES'; if ($schema !== '') { diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 09a620d..6107422 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -7,6 +7,7 @@ */ namespace yii\db\pgsql; +use yii\base\InvalidParamException; /** * QueryBuilder is the query builder for PostgreSQL databases. @@ -62,6 +63,66 @@ class QueryBuilder extends \yii\db\QueryBuilder } /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + $sequence='"'.$table->sequenceName.'"'; + + if(strpos($sequence,'.')!==false) + $sequence=str_replace('.','"."',$sequence); + + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value="(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1"; + } else { + $value = (int)$value; + } + return "SELECT SETVAL('$sequence',$value,false)"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'."); + } + } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $table the table name. Meaningless for MySQL. + * @param string $schema the schema of the tables. Meaningless for MySQL. + * @return string the SQL statement for checking integrity + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + $enable=$check ? 'ENABLE' : 'DISABLE'; + $tableNames=$this->db->schema->findTableNames($schema); + $command = ''; + + foreach($tableNames as $tableName) + { + $tableName='"'.$tableName.'"'; + if(strpos($tableName,'.')!==false) + $tableName=str_replace('.','"."',$tableName); + $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; "; + } + + #enable to have ability to alter several tables + $this->db->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true); + return $command; + } + + /** * Builds a SQL statement for changing the definition of a column. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. * @param string $column the name of the column to be changed. The name will be properly quoted by the method. diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 96889ab..59340c9 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -158,7 +158,7 @@ class Schema extends \yii\db\Schema * @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. */ - protected function findTableNames($schema = '') + public function findTableNames($schema = '') { if ($schema === '') { $schema = $this->defaultSchema; From 5c2c79e776126249079af8569dbfb4de35161869 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 27 Dec 2013 09:51:27 +0400 Subject: [PATCH 02/13] fixed sequence reset --- framework/yii/test/DbFixtureManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/test/DbFixtureManager.php b/framework/yii/test/DbFixtureManager.php index d78d28f..f657a40 100644 --- a/framework/yii/test/DbFixtureManager.php +++ b/framework/yii/test/DbFixtureManager.php @@ -146,6 +146,7 @@ class DbFixtureManager extends Component } $this->db->createCommand()->truncateTable($tableName)->execute(); + $this->db->createCommand()->resetSequence($tableName,1)->execute(); $fileName = $this->basePath . '/' . $tableName . '.php'; if (!is_file($fileName)) { From 65b543f68ff46e6c998a0d7a6c06f43773e44284 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 27 Dec 2013 11:04:29 +0400 Subject: [PATCH 03/13] fixed bug with forgotten param, fixed behavior for one table integrity --- framework/yii/db/Command.php | 5 +++-- framework/yii/db/pgsql/QueryBuilder.php | 8 ++++---- framework/yii/db/sqlite/Schema.php | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index 2075b2d..76b4269 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -743,12 +743,13 @@ class Command extends \yii\base\Component * @param boolean $check whether to turn on or off the integrity check. * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current * or default schema. + * @param string $table the table name. * @return Command the command object itself * @throws NotSupportedException if this is not supported by the underlying DBMS */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { - $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema); + $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table); return $this->setSql($sql); } } diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 6107422..190e548 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -99,14 +99,14 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * Builds a SQL statement for enabling or disabling integrity check. * @param boolean $check whether to turn on or off the integrity check. - * @param string $table the table name. Meaningless for MySQL. - * @param string $schema the schema of the tables. Meaningless for MySQL. + * @param string $schema the schema of the tables. + * @param string $table the table name. * @return string the SQL statement for checking integrity */ public function checkIntegrity($check = true, $schema = '', $table = '') { - $enable=$check ? 'ENABLE' : 'DISABLE'; - $tableNames=$this->db->schema->findTableNames($schema); + $enable = $check ? 'ENABLE' : 'DISABLE'; + $tableNames = $table ? [$table] : $this->db->schema->findTableNames($schema); $command = ''; foreach($tableNames as $tableName) diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php index 9f410b4..6548999 100644 --- a/framework/yii/db/sqlite/Schema.php +++ b/framework/yii/db/sqlite/Schema.php @@ -87,7 +87,7 @@ class Schema extends \yii\db\Schema * If not empty, the returned table names will be prefixed with the schema name. * @return array all table names in the database. */ - protected function findTableNames($schema = '') + public function findTableNames($schema = '') { $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'"; return $this->db->createCommand($sql)->queryColumn(); From 5220596ead737b3ae32679fd328c2e1f17159d89 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 27 Dec 2013 11:21:17 +0400 Subject: [PATCH 04/13] fix sphinx command signature --- extensions/yii/sphinx/Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/yii/sphinx/Command.php b/extensions/yii/sphinx/Command.php index ba802f5..9197b67 100644 --- a/extensions/yii/sphinx/Command.php +++ b/extensions/yii/sphinx/Command.php @@ -319,7 +319,7 @@ class Command extends \yii\db\Command /** * @inheritdoc */ - public function checkIntegrity($check = true, $schema = '') + public function checkIntegrity($check = true, $schema = '', $table = '') { throw new NotSupportedException('"' . __METHOD__ . '" is not supported.'); } From 55f62bfe9224f564b9adf3b2d00625d57ea1bf2b Mon Sep 17 00:00:00 2001 From: suralc Date: Fri, 27 Dec 2013 20:50:32 +0100 Subject: [PATCH 05/13] Modified extension guidlines --- docs/guide/extensions.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/guide/extensions.md b/docs/guide/extensions.md index 4f178ef..de7c9fa 100644 --- a/docs/guide/extensions.md +++ b/docs/guide/extensions.md @@ -17,9 +17,11 @@ using the following syntax: e.g. `[[name()]]`, `[[name\space\MyClass::name()]]`. ### Namespace and package names - Extension MUST use the type `yii2-extension` in `composer.json` file. -- Extension MUST NOT use `yii` or `yii2` in the composer package name or in the namespaces used in the package. +- Extension MUST NOT use `yiisoft` in the composer package name, the composer vendor name or in the namespaces used in the package. +- Additionally extensions MUST NOT use `yii` or `yii2` in their composer vendor name. +- Extension MUST NOT have a root-namespace named `\yii`, `\yii2` or `\yiisoft`. - Extension SHOULD use namespaces in this format `vendor-name\package` (all lowercase). -- Extension MAY use a `yii2-` prefix in the composer vendor name (URL). +- Extension MAY use `yii2-` in the composer package name (e.g `vendor\yii2-api-adapter` or `vendor\my-yii2-package` (URL). - Extension MAY use a `yii2-` prefix in the repository name (URL). ### Dependencies @@ -75,4 +77,4 @@ Authorization Testing your extension ---------------------- -- Extension SHOULD be testable with *PHPUnit*. \ No newline at end of file +- Extension SHOULD be testable with *PHPUnit*. From 6a9509ed8d4fcfb52de3ccee755bb97a9f769377 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 28 Dec 2013 00:04:18 +0400 Subject: [PATCH 06/13] improved checkIntegrity method --- framework/yii/db/pgsql/QueryBuilder.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 190e548..faa9323 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -106,15 +106,14 @@ class QueryBuilder extends \yii\db\QueryBuilder public function checkIntegrity($check = true, $schema = '', $table = '') { $enable = $check ? 'ENABLE' : 'DISABLE'; + $schema = $schema ? $schema : $this->db->schema->defaultSchema; $tableNames = $table ? [$table] : $this->db->schema->findTableNames($schema); $command = ''; foreach($tableNames as $tableName) { - $tableName='"'.$tableName.'"'; - if(strpos($tableName,'.')!==false) - $tableName=str_replace('.','"."',$tableName); - $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; "; + $tableName='"'.$schema.'"."'.$tableName.'"'; + $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; "; } #enable to have ability to alter several tables From 16801b9b3e5d32c7cb23c0b245d6d7ca2f9c57ae Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 28 Dec 2013 00:06:39 +0400 Subject: [PATCH 07/13] codestyle fix --- framework/yii/db/pgsql/QueryBuilder.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index faa9323..47255e6 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -78,8 +78,9 @@ class QueryBuilder extends \yii\db\QueryBuilder if ($table !== null && $table->sequenceName !== null) { $sequence='"'.$table->sequenceName.'"'; - if(strpos($sequence,'.')!==false) + if (strpos($sequence,'.')!==false) { $sequence=str_replace('.','"."',$sequence); + } $tableName = $this->db->quoteTableName($tableName); if ($value === null) { From 79ef473a3855e06c689a8c00f210e95805b66a01 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 28 Dec 2013 00:43:26 +0400 Subject: [PATCH 08/13] added changelog --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6bebff1..9c45ed6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -33,6 +33,7 @@ Yii Framework 2 Change Log - Enh #1611: Added `BaseActiveRecord::markAttributeDirty()` (qiangxue) - Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue) - Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue) +- Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo) - Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) From fdca106ece0fea43e6c463c0780d0edfad9b2f49 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 27 Dec 2013 15:53:45 -0500 Subject: [PATCH 09/13] code style fix. --- framework/yii/db/pgsql/QueryBuilder.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 47255e6..f1ac032 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -7,6 +7,7 @@ */ namespace yii\db\pgsql; + use yii\base\InvalidParamException; /** @@ -76,16 +77,16 @@ class QueryBuilder extends \yii\db\QueryBuilder { $table = $this->db->getTableSchema($tableName); if ($table !== null && $table->sequenceName !== null) { - $sequence='"'.$table->sequenceName.'"'; - - if (strpos($sequence,'.')!==false) { - $sequence=str_replace('.','"."',$sequence); + $sequence = '"' . $table->sequenceName . '"'; + + if (strpos($sequence, '.') !== false) { + $sequence = str_replace('.', '"."', $sequence); } - + $tableName = $this->db->quoteTableName($tableName); if ($value === null) { $key = reset($table->primaryKey); - $value="(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1"; + $value = "(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1"; } else { $value = (int)$value; } @@ -111,9 +112,8 @@ class QueryBuilder extends \yii\db\QueryBuilder $tableNames = $table ? [$table] : $this->db->schema->findTableNames($schema); $command = ''; - foreach($tableNames as $tableName) - { - $tableName='"'.$schema.'"."'.$tableName.'"'; + foreach ($tableNames as $tableName) { + $tableName = '"' . $schema . '"."' . $tableName . '"'; $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; "; } From 4a552c88cff62c3d84f478e289e75dbf05bc1498 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 27 Dec 2013 16:01:39 -0500 Subject: [PATCH 10/13] Fixes #1650: Added Connection::pdoClass. --- framework/CHANGELOG.md | 1 + framework/yii/db/Connection.php | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b3c13e5..b7699ac 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -35,6 +35,7 @@ Yii Framework 2 Change Log - Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue) - Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue) - Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo) +- Enh #1645: Added `Connection::$pdoClass` property (Ragazzo) - Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index ef5be87..030e6c7 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -253,7 +253,10 @@ class Connection extends Component * @var Schema the database schema */ private $_schema; - + /** + * @var string Custom PDO wrapper class. If not set, it will use "PDO" or "yii\db\mssql\PDO" when MSSQL is used. + */ + public $pdoClass; /** * Returns a value indicating whether the DB connection is established. @@ -338,13 +341,17 @@ class Connection extends Component */ protected function createPdoInstance() { - $pdoClass = 'PDO'; - if (($pos = strpos($this->dsn, ':')) !== false) { - $driver = strtolower(substr($this->dsn, 0, $pos)); - if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { - $pdoClass = 'yii\db\mssql\PDO'; + $pdoClass = $this->pdoClass; + if ($pdoClass === null) { + $pdoClass = 'PDO'; + if (($pos = strpos($this->dsn, ':')) !== false) { + $driver = strtolower(substr($this->dsn, 0, $pos)); + if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') { + $pdoClass = 'yii\db\mssql\PDO'; + } } } + return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); } From 8af989a809a0231ba0bbc48545596cc9b9e61f7b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 27 Dec 2013 23:42:12 +0100 Subject: [PATCH 11/13] Fixed repo URL --- docs/internals/git-workflow.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md index 3386df6..fc8b275 100644 --- a/docs/internals/git-workflow.md +++ b/docs/internals/git-workflow.md @@ -18,10 +18,10 @@ then you must [setup your GIT installation to work with GitHub](http://help.gith ### 2. Add the main Yii repository as an additional git remote called "upstream" -Change to the directory where you cloned Yii, normally, "yii". Then enter the following command: +Change to the directory where you cloned Yii, normally, "yii2". Then enter the following command: ``` -git remote add upstream git://github.com/yiisoft/yii.git +git remote add upstream git://github.com/yiisoft/yii2.git ``` ### 3. Make sure there is an issue created for the thing you are working on. From eb4385d4c9f74910e8ad40b6fd4bb53001c2cfe8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 27 Dec 2013 20:31:48 -0500 Subject: [PATCH 12/13] improved error message of calling invalid scope method. --- framework/yii/db/ActiveQueryTrait.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/framework/yii/db/ActiveQueryTrait.php b/framework/yii/db/ActiveQueryTrait.php index c8ba773..79bac78 100644 --- a/framework/yii/db/ActiveQueryTrait.php +++ b/framework/yii/db/ActiveQueryTrait.php @@ -6,6 +6,7 @@ */ namespace yii\db; +use yii\base\InvalidCallException; /** * ActiveQueryTrait implements the common methods and properties for active record query classes. @@ -42,6 +43,10 @@ trait ActiveQueryTrait public function __call($name, $params) { if (method_exists($this->modelClass, $name)) { + $method = new \ReflectionMethod($this->modelClass, $name); + if (!$method->isStatic() || !$method->isPublic()) { + throw new InvalidCallException("The scope method \"{$this->modelClass}::$name()\" must be public and static."); + } array_unshift($params, $this); call_user_func_array([$this->modelClass, $name], $params); return $this; From 4b569f3e90eade353edb458c12183e6bac4c11e8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 27 Dec 2013 23:26:38 -0500 Subject: [PATCH 13/13] Fixed CSRF token masking issue. --- framework/yii/web/Request.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index aae2e3c..ee232f4 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1039,7 +1039,8 @@ class Request extends \yii\base\Request if ($this->_maskedCsrfToken === null) { $token = $this->getCsrfToken(); $mask = Security::generateRandomKey(self::CSRF_MASK_LENGTH); - $this->_maskedCsrfToken = base64_encode($mask . $this->xorTokens($token, $mask)); + // The + sign may be decoded as blank space later, which will fail the validation + $this->_maskedCsrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } return $this->_maskedCsrfToken; } @@ -1120,7 +1121,7 @@ class Request extends \yii\base\Request private function validateCsrfTokenInternal($token, $trueToken) { - $token = base64_decode($token); + $token = str_replace('.', '+', base64_decode($token)); $n = StringHelper::byteLength($token); if ($n <= self::CSRF_MASK_LENGTH) { return false;