diff --git a/common/framework/db.php b/common/framework/db.php index ee789f824..f5662a6fe 100644 --- a/common/framework/db.php +++ b/common/framework/db.php @@ -408,7 +408,7 @@ class DB // Get the COUNT(*) query string and parameters. try { - $query_string = $query->getQueryString($this->_prefix, $args, [], true); + $query_string = $query->getQueryString($this->_prefix, $args, [], 1); $query_params = $query->getQueryParams(); } catch (Exceptions\QueryError $e) diff --git a/common/framework/parsers/dbquery/query.php b/common/framework/parsers/dbquery/query.php index 4393b0325..71dfd800d 100644 --- a/common/framework/parsers/dbquery/query.php +++ b/common/framework/parsers/dbquery/query.php @@ -50,10 +50,10 @@ class Query extends VariableBase * @param string $prefix * @param array $args * @param array $column_list - * @param bool $count_only + * @param int $count_only * @return string */ - public function getQueryString(string $prefix = '', array $args, array $column_list = [], bool $count_only = false): string + public function getQueryString(string $prefix = '', array $args, array $column_list = [], int $count_only = 0): string { // Save the query information. $this->_prefix = $prefix; @@ -110,10 +110,10 @@ class Query extends VariableBase /** * Generate a SELECT query string. * - * @param bool $count_only + * @param int $count_only * @return string */ - protected function _getSelectQueryString(bool $count_only = false): string + protected function _getSelectQueryString(int $count_only = 0): string { // Initialize the query string. $result = 'SELECT'; @@ -133,18 +133,23 @@ class Query extends VariableBase { if ($column instanceof self) { - $subquery = $column->getQueryString($this->_prefix, $this->_args); + $has_subquery_columns = true; + $subquery_count_only = $count_only ? $count_only + 1 : 0; + $subquery = $column->getQueryString($this->_prefix, $this->_args, [], $subquery_count_only); foreach ($column->getQueryParams() as $param) { $this->_params[] = $param; } $columns[] = sprintf('(%s) AS %s', $subquery, self::quoteName($column->alias)); - $has_subquery_columns = true; } elseif ($column->is_expression && !$column->is_wildcard) { $columns[] = $column->name . ($column->alias ? (' AS ' . self::quoteName($column->alias)) : ''); } + elseif ($column->is_wildcard && $count_only >= 1 && !$this->select_distinct) + { + $columns[] = '1'; + } else { $columns[] = self::quoteName($column->name) . ($column->alias ? (' AS ' . self::quoteName($column->alias)) : ''); @@ -154,19 +159,12 @@ class Query extends VariableBase } // Replace the column list if this is a count-only query. - if ($count_only) + if ($count_only == 1) { $count_wrap = ($this->groupby || $this->select_distinct || $has_subquery_columns || preg_match('/\bDISTINCT\b/i', $column_list)); if ($count_wrap) { - if ($column_list === '*' || preg_match('/\\.\\*/', $column_list)) - { - $result .= ' 1'; - } - else - { - $result .= ($this->select_distinct ? ' DISTINCT ' : ' ') . $column_list; - } + $result .= ($this->select_distinct ? ' DISTINCT ' : ' ') . $column_list; } else { @@ -234,13 +232,13 @@ class Query extends VariableBase } // Compose the ORDER BY clause. - if ($this->navigation && count($this->navigation->orderby) && !$count_only) + if ($this->navigation && count($this->navigation->orderby) && ($count_only != 1 || $count_wrap)) { $result .= ' ORDER BY ' . $this->_arrangeOrderBy($this->navigation); } // Compose the LIMIT/OFFSET clause. - if ($this->navigation && $this->navigation->list_count && !$count_only) + if ($this->navigation && $this->navigation->list_count && ($count_only != 1 || $count_wrap)) { $result .= ' LIMIT ' . $this->_arrangeLimitOffset($this->navigation); } diff --git a/tests/_data/dbquery/selectSubqueryTest2.xml b/tests/_data/dbquery/selectSubqueryTest2.xml index b3f4c40d4..68385e5ae 100644 --- a/tests/_data/dbquery/selectSubqueryTest2.xml +++ b/tests/_data/dbquery/selectSubqueryTest2.xml @@ -9,6 +9,8 @@ + + diff --git a/tests/unit/framework/parsers/DBQueryParserTest.php b/tests/unit/framework/parsers/DBQueryParserTest.php index 5566aa61f..d4b1a5f48 100644 --- a/tests/unit/framework/parsers/DBQueryParserTest.php +++ b/tests/unit/framework/parsers/DBQueryParserTest.php @@ -56,6 +56,14 @@ class DBQueryParserTest extends \Codeception\TestCase\Test 'WHERE `member_srl` IN (?) AND (`regdate` >= ? OR `status` = ?) ' . 'ORDER BY `list_order` ASC LIMIT 40, 20', $sql); $this->assertEquals(['1234', '20200707120000', 'PUBLIC'], $params); + + $sql = $query->getQueryString('rx_', $args, [], 1); + $params = $query->getQueryParams(); + + $this->assertEquals('SELECT COUNT(*) AS `count` FROM (SELECT DISTINCT * FROM `rx_documents` AS `documents` ' . + 'WHERE `member_srl` IN (?) AND (`regdate` >= ? OR `status` = ?) ' . + 'ORDER BY `list_order` ASC LIMIT 40, 20) AS `subquery`', $sql); + $this->assertEquals(['1234', '20200707120000', 'PUBLIC'], $params); } public function testSelectWithExpressions() @@ -220,18 +228,34 @@ class DBQueryParserTest extends \Codeception\TestCase\Test $this->assertTrue($query->tables['member'] instanceof Rhymix\Framework\Parsers\DBQuery\Table); $this->assertEquals(2, count($query->columns)); $this->assertTrue($query->columns[0] instanceof Rhymix\Framework\Parsers\DBQuery\ColumnRead); + $this->assertTrue($query->columns[0]->is_expression); + $this->assertTrue($query->columns[0]->is_wildcard); $this->assertTrue($query->columns[1] instanceof Rhymix\Framework\Parsers\DBQuery\Query); $this->assertTrue($query->columns[1]->tables['documents'] instanceof Rhymix\Framework\Parsers\DBQuery\Table); $this->assertTrue($query->columns[1]->columns[0] instanceof Rhymix\Framework\Parsers\DBQuery\ColumnRead); - $this->assertTrue($query->columns[1]->columns[0]->is_expression); + $this->assertFalse($query->columns[1]->columns[0]->is_expression); $this->assertFalse($query->columns[1]->columns[0]->is_wildcard); + $this->assertTrue($query->columns[1]->columns[1]->is_expression); + $this->assertTrue($query->columns[1]->columns[1]->is_wildcard); + $this->assertTrue($query->columns[1]->columns[2]->is_expression); + $this->assertFalse($query->columns[1]->columns[2]->is_wildcard); $sql = $query->getQueryString('rx_', []); $params = $query->getQueryParams(); - $this->assertEquals('SELECT `member`.*, (SELECT COUNT(*) AS `count` FROM `rx_documents` AS `documents` WHERE `member`.`member_srl` = `documents`.`member_srl`) AS `document_count` ' . + $this->assertEquals('SELECT `member`.*, (SELECT `documents`.`document_srl`, `documents`.*, COUNT(*) AS `count` FROM `rx_documents` AS `documents` ' . + 'WHERE `member`.`member_srl` = `documents`.`member_srl`) AS `document_count` ' . 'FROM `rx_member` AS `member`', $sql); $this->assertEquals([], $params); + + // Test count-only query (#1575) + $sql = $query->getQueryString('rx_', [], [], 1); + $params = $query->getQueryParams(); + + $this->assertEquals('SELECT COUNT(*) AS `count` FROM (SELECT 1, (SELECT `documents`.`document_srl`, 1, COUNT(*) AS `count` FROM `rx_documents` AS `documents` ' . + 'WHERE `member`.`member_srl` = `documents`.`member_srl`) AS `document_count` ' . + 'FROM `rx_member` AS `member`) AS `subquery`', $sql); + $this->assertEquals([], $params); } public function testSubquery3()