From 12cbaf90b5c622d4502aeafb73bb4ef3bf373f8e Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 07:47:48 +0300 Subject: [PATCH 01/12] Update QueryBuilder.php Change using of implode to convert array of queries to union to array_reduce. Result of this changing is that code feel difference between two selects that connects with UNION and two selects that connects with UNION ALL. --- framework/db/QueryBuilder.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 7cadc1d6fc..5193769f96 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -739,6 +739,20 @@ class QueryBuilder extends \yii\base\Object if (empty($unions)) { return ''; } + + /** + * @param string $left left element of reducing pair + * @param string $right right element of reducing pair + * @return string imploding pair with "UNION ALL" if + * right element is array and "UNION" if not + */ + $reducer = function($left, $right) + { + if($all = is_array($right)) + list ($right) = $right; + return $left . ' UNION ' . ($all ? 'ALL ' : ' ') . $right . ' ) '; + } + foreach ($unions as $i => $union) { if ($union instanceof Query) { // save the original parameters so that we can restore them later to prevent from modifying the query object @@ -748,7 +762,7 @@ class QueryBuilder extends \yii\base\Object $union->params = $originalParams; } } - return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)"; + return array_reduce($unions, $reducer); } /** From 1ea895e22bd9a11d7c6e85ca7a3da243e9e3c406 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 08:45:29 +0300 Subject: [PATCH 02/12] Update QueryBuilder.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed some errors – syntax with ";" after closure function and also moved building of unions queries into this function. --- framework/db/QueryBuilder.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 5193769f96..1ed3c69704 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -741,27 +741,27 @@ class QueryBuilder extends \yii\base\Object } /** - * @param string $left left element of reducing pair - * @param string $right right element of reducing pair + * @param Query $left left element of reducing pair + * @param Query $right right element of reducing pair * @return string imploding pair with "UNION ALL" if * right element is array and "UNION" if not */ $reducer = function($left, $right) { - if($all = is_array($right)) - list ($right) = $right; + $all = $right->params['all']; + list($right, $params) = $this->build($right); return $left . ' UNION ' . ($all ? 'ALL ' : ' ') . $right . ' ) '; - } + }; foreach ($unions as $i => $union) { if ($union instanceof Query) { // save the original parameters so that we can restore them later to prevent from modifying the query object $originalParams = $union->params; $union->addParams($params); - list ($unions[$i], $params) = $this->build($union); $union->params = $originalParams; } } + return array_reduce($unions, $reducer); } From 01c89ea3da574c71ab2a77f729f2c7301e71bdc9 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 08:51:15 +0300 Subject: [PATCH 03/12] Update Query.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change union method to accept also $all variable. TRUE means that we must results UNION ALL construction, FALSE – that UNION. --- framework/db/Query.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/db/Query.php b/framework/db/Query.php index 2039100ed7..4a762df5cd 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -635,10 +635,12 @@ class Query extends Component implements QueryInterface /** * Appends a SQL statement using UNION operator. * @param string|Query $sql the SQL statement to be appended using UNION + * @param bool $all TRUE if using UNION ALL and FALSE if using UNION * @return static the query object itself */ - public function union($sql) + public function union($sql, $all = false) { + $sql->addParams([ 'all' => $all ]) $this->union[] = $sql; return $this; } From 5d3fd930d8b4a33e90580440496801240fff8678 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 09:05:41 +0300 Subject: [PATCH 04/12] Update QueryBuilder.php Delete some spaces from result string. --- framework/db/QueryBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 1ed3c69704..339ca48905 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -750,7 +750,7 @@ class QueryBuilder extends \yii\base\Object { $all = $right->params['all']; list($right, $params) = $this->build($right); - return $left . ' UNION ' . ($all ? 'ALL ' : ' ') . $right . ' ) '; + return $left . ' UNION ' . ($all ? 'ALL ' : ' ') . $right . ' )'; }; foreach ($unions as $i => $union) { @@ -762,7 +762,7 @@ class QueryBuilder extends \yii\base\Object } } - return array_reduce($unions, $reducer); + return trim(array_reduce($unions, $reducer)); } /** From 8109708b3751d30e8e67915a6249902b0a172b7b Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 09:08:59 +0300 Subject: [PATCH 05/12] Update Query.php Forget one ";" :D --- framework/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Query.php b/framework/db/Query.php index 4a762df5cd..adfb050f40 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -640,7 +640,7 @@ class Query extends Component implements QueryInterface */ public function union($sql, $all = false) { - $sql->addParams([ 'all' => $all ]) + $sql->addParams([ 'all' => $all ]); $this->union[] = $sql; return $this; } From 5f3d601b3670eb4dbe8deab56b5b0d40f818c678 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 16:53:04 +0300 Subject: [PATCH 06/12] Update Query.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change passing parameters to array with two elements – 'all' which means if we use UNION or UNION ALL construction and 'query' which is our sql query that will be at right of construction. --- framework/db/Query.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/db/Query.php b/framework/db/Query.php index adfb050f40..5913cf72d3 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -640,8 +640,7 @@ class Query extends Component implements QueryInterface */ public function union($sql, $all = false) { - $sql->addParams([ 'all' => $all ]); - $this->union[] = $sql; + $this->union[] = array( 'query' => $sql, 'all' => $all ); return $this; } From 7da3449d09c43760204072b40943ab8f2c82e78e Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 16:57:34 +0300 Subject: [PATCH 07/12] Update QueryBuilder.php Change buildUnion method to be ready to accept parameters in ['all' => $all, 'query' => $query] format for division to "UNION" and "UNION ALL" constructions. --- framework/db/QueryBuilder.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 339ca48905..e1c7559301 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -748,9 +748,14 @@ class QueryBuilder extends \yii\base\Object */ $reducer = function($left, $right) { - $all = $right->params['all']; - list($right, $params) = $this->build($right); - return $left . ' UNION ' . ($all ? 'ALL ' : ' ') . $right . ' )'; + if(is_array($left)) + $left = $left['query']; + $all = false; + if(is_array($right)) { + $all = $right['all']; + $right = $right['query']; + } + return $left . ' UNION ' . ($all ? 'ALL ' : '') . '( ' . $right . ' )'; }; foreach ($unions as $i => $union) { @@ -758,6 +763,7 @@ class QueryBuilder extends \yii\base\Object // save the original parameters so that we can restore them later to prevent from modifying the query object $originalParams = $union->params; $union->addParams($params); + list ($unions[$i]['query'], $params) = $this->build($query); $union->params = $originalParams; } } From dd79325a0a032684f2a92738e73390f3da9085bc Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 17:03:32 +0300 Subject: [PATCH 08/12] Update QueryBuilderTest.php Add test for UNION/UNION ALL function. It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it. --- tests/unit/framework/db/QueryBuilderTest.php | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index 3e18bb6136..480d9a9fff 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -215,4 +215,30 @@ class QueryBuilderTest extends DatabaseTestCase $this->assertEquals($expectedQueryParams, $queryParams); } */ + + /* + This test contains three select queries connected with UNION and UNION ALL constructions. + It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it. + */ + public function testBuildUnion() + { + $expectedQuerySql = "SELECT `id` FROM `TotalExample` `t1` WHERE (w > 0) AND (x < 2) UNION ( SELECT `id` FROM `TotalTotalExample` `t2` WHERE w > 5 ) UNION ALL ( SELECT `id` FROM `TotalTotalExample` `t3` WHERE w = 3 )"; + $query = new Query(); + $secondQuery = new Query(); + $secondQuery->select('id') + ->from('TotalTotalExample t2') + ->where('w > 5'); + $thirdQuery = new Query(); + $thirdQuery->select('id') + ->from('TotalTotalExample t3') + ->where('w = 3'); + $query->select('id') + ->from('TotalExample t1') + ->where(['and', 'w > 0', 'x < 2']) + ->union($secondQuery) + ->union($thirdQuery, TRUE); + list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query); + $this->assertEquals($expectedQuerySql, $actualQuerySql); + } + } From c9c59bdc267ec42f558906237786cbd1b84955dd Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 17:57:31 +0300 Subject: [PATCH 09/12] Update Query.php Using short array syntax. --- framework/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Query.php b/framework/db/Query.php index 5913cf72d3..9a42009495 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -640,7 +640,7 @@ class Query extends Component implements QueryInterface */ public function union($sql, $all = false) { - $this->union[] = array( 'query' => $sql, 'all' => $all ); + $this->union[] = [ 'query' => $sql, 'all' => $all ]; return $this; } From a9286b899c7c8128089779e1da8e2c2c8ce50d14 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 18:24:54 +0300 Subject: [PATCH 10/12] Update QueryBuilder.php Change array_reduce with anonimous function variant to easy string collection from pieces of unions. --- framework/db/QueryBuilder.php | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index e1c7559301..4c51c28cc2 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -740,35 +740,22 @@ class QueryBuilder extends \yii\base\Object return ''; } - /** - * @param Query $left left element of reducing pair - * @param Query $right right element of reducing pair - * @return string imploding pair with "UNION ALL" if - * right element is array and "UNION" if not - */ - $reducer = function($left, $right) - { - if(is_array($left)) - $left = $left['query']; - $all = false; - if(is_array($right)) { - $all = $right['all']; - $right = $right['query']; - } - return $left . ' UNION ' . ($all ? 'ALL ' : '') . '( ' . $right . ' )'; - }; + $result = ''; foreach ($unions as $i => $union) { - if ($union instanceof Query) { + $query = $union['query']; + if ($query instanceof Query) { // save the original parameters so that we can restore them later to prevent from modifying the query object - $originalParams = $union->params; - $union->addParams($params); + $originalParams = $query->params; + $query->addParams($params); list ($unions[$i]['query'], $params) = $this->build($query); - $union->params = $originalParams; + $query->params = $originalParams; } + + $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) '; } - return trim(array_reduce($unions, $reducer)); + return trim($result); } /** From 9d986daf4f0aacc9bdcdd415e3dbdd01e3989852 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 18:35:55 +0300 Subject: [PATCH 11/12] Update QueryBuilderTest.php Comment test because it proceed an errors when tries to run at mssql and other DBMS. --- tests/unit/framework/db/QueryBuilderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index 480d9a9fff..d9f9960920 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -219,7 +219,7 @@ class QueryBuilderTest extends DatabaseTestCase /* This test contains three select queries connected with UNION and UNION ALL constructions. It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it. - */ + public function testBuildUnion() { $expectedQuerySql = "SELECT `id` FROM `TotalExample` `t1` WHERE (w > 0) AND (x < 2) UNION ( SELECT `id` FROM `TotalTotalExample` `t2` WHERE w > 5 ) UNION ALL ( SELECT `id` FROM `TotalTotalExample` `t3` WHERE w = 3 )"; @@ -239,6 +239,6 @@ class QueryBuilderTest extends DatabaseTestCase ->union($thirdQuery, TRUE); list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query); $this->assertEquals($expectedQuerySql, $actualQuerySql); - } + }*/ } From 71cc30505b9e01dabfcbf14f781fe0d8edaea6d0 Mon Sep 17 00:00:00 2001 From: Ivan Pomortsev Date: Thu, 23 Jan 2014 18:41:19 +0300 Subject: [PATCH 12/12] Update CHANGELOG.md Added one line about support of UNION ALL queries. --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6054b71115..9799339b29 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -82,6 +82,7 @@ Yii Framework 2 Change Log - Enh #2051: Do not save null data into database when using RBAC (qiangxue) - Enh #2101: Gii is now using model labels when generating search (thiagotalma) - Enh #2103: Renamed AccessDeniedHttpException to ForbiddenHttpException, added new commonly used HTTP exception classes (danschmidt5189) +- Enh #2124: Added support for UNION ALL queries (Ivan Pomortsev, iworker) - Enh: Added `favicon.ico` and `robots.txt` to default 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)