From 0bb127c9d6c97011cb0865fa9adf13537f713788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 17 Nov 2017 16:11:36 +0100 Subject: [PATCH 1/3] Adding failing tests --- tests/Dao/TestCountryDao.php | 18 ++++++++++++++++++ tests/TDBMDaoGeneratorTest.php | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/Dao/TestCountryDao.php b/tests/Dao/TestCountryDao.php index b9abebba..a4d3f7a2 100644 --- a/tests/Dao/TestCountryDao.php +++ b/tests/Dao/TestCountryDao.php @@ -22,6 +22,24 @@ public function getCountriesByUserCount() LEFT JOIN users ON users.country_id = country.id GROUP BY country.id ORDER BY COUNT(users.id) DESC +SQL; + + return $this->findFromRawSql($sql); + } + + /** + * @return CountryBean[]|Result + */ + public function getCountriesUsingUnion() + { + $sql = <<findFromRawSql($sql); diff --git a/tests/TDBMDaoGeneratorTest.php b/tests/TDBMDaoGeneratorTest.php index 2c2fac88..574ff667 100644 --- a/tests/TDBMDaoGeneratorTest.php +++ b/tests/TDBMDaoGeneratorTest.php @@ -684,6 +684,18 @@ public function testFindFromRawSqlOrderByUserCount() } } + /** + * @depends testDaoGeneration + */ + public function testFindFromRawSqlWithUnion() + { + $countryDao = new TestCountryDao($this->tdbmService); + $countries = $countryDao->getCountriesUsingUnion(); + + $this->assertCount(2, $countries); + $this->assertEquals(1, $countries[0]->getId()); + } + /** * @depends testDaoGeneration */ From ae5687ebd56081e0ae2cfb4da5e9158c8b8f8bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 22 Nov 2017 11:32:28 +0100 Subject: [PATCH 2/3] Adding support for UNION queries in findFromRawSql --- composer.json | 2 +- .../FindObjectsFromRawSqlQueryFactory.php | 99 ++++++++++++++----- 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 814349bf..1543bcd3 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ ], "require" : { "php" : ">=7.1", - "mouf/magic-query" : "^1.2.4", + "mouf/magic-query" : "^1.2.8", "mouf/schema-analyzer": "^1.1.4", "doctrine/dbal": "^2.6.1", "psr/log": "~1.0", diff --git a/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php b/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php index 9b4810fa..79797c24 100644 --- a/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php +++ b/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php @@ -2,11 +2,9 @@ namespace TheCodingMachine\TDBM\QueryFactory; -use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Schema; use TheCodingMachine\TDBM\TDBMException; use TheCodingMachine\TDBM\TDBMService; -use TheCodingMachine\TDBM\UncheckedOrderBy; use PHPSQLParser\PHPSQLCreator; use PHPSQLParser\PHPSQLParser; @@ -23,14 +21,6 @@ class FindObjectsFromRawSqlQueryFactory implements QueryFactory * @var Schema */ private $schema; - /** - * @var string - */ - private $sql; - /** - * @var string - */ - private $sqlCount; /** * @var string */ @@ -58,13 +48,11 @@ class FindObjectsFromRawSqlQueryFactory implements QueryFactory */ public function __construct(TDBMService $tdbmService, Schema $schema, string $mainTable, string $sql, string $sqlCount = null) { - $this->sql = $sql; - $this->sqlCount = $sqlCount; $this->tdbmService = $tdbmService; $this->schema = $schema; $this->mainTable = $mainTable; - $this->compute(); + [$this->processedSql, $this->processedSqlCount, $this->columnDescriptors] = $this->compute($sql, $sqlCount); } public function sort($orderBy) @@ -87,25 +75,75 @@ public function getColumnDescriptors(): array return $this->columnDescriptors; } - protected function compute() + private function compute(string $sql, ?string $sqlCount) { $parser = new PHPSQLParser(); + $parsedSql = $parser->parse($sql); + + if (isset($parsedSql['SELECT'])) { + [$processedSql, $processedSqlCount, $columnDescriptors] = $this->processParsedSelectQuery($parsedSql, $sqlCount); + } elseif (isset($parsedSql['UNION'])) { + [$processedSql, $processedSqlCount, $columnDescriptors] = $this->processParsedUnionQuery($parsedSql, $sqlCount); + } else { + throw new TDBMException('Unable to analyze query "'.$sql.'"'); + } + + return [$processedSql, $processedSqlCount, $columnDescriptors]; + } + + private function processParsedUnionQuery(array $parsedSql, ?string $sqlCount): array + { + $selects = $parsedSql['UNION']; + + $parsedSqlList = []; + $columnDescriptors = []; + + foreach ($selects as $select) { + [$selectProcessedSql, $selectProcessedCountSql, $columnDescriptors] = $this->processParsedSelectQuery($select, ''); + + // Let's reparse the returned SQL (not the most efficient way of doing things) + $parser = new PHPSQLParser(); + $parsedSql = $parser->parse($selectProcessedSql); + + $parsedSqlList[] = $parsedSql; + } + + // Let's rebuild the UNION query + $query = ['UNION' => $parsedSqlList]; + + // The count is the SUM of the count of the UNIONs + $countQuery = $this->generateWrappedSqlCount($query); + $generator = new PHPSQLCreator(); - $parsedSql = $parser->parse($this->sql); + $processedSql = $generator->create($query); + $processedSqlCount = $generator->create($countQuery); + + return [$processedSql, $sqlCount ?? $processedSqlCount, $columnDescriptors]; + } + + /** + * @param array $parsedSql + * @param null|string $sqlCount + * @return mixed[] + */ + private function processParsedSelectQuery(array $parsedSql, ?string $sqlCount): array + { // 1: let's reformat the SELECT and construct our columns list($select, $columnDescriptors) = $this->formatSelect($parsedSql['SELECT']); + $generator = new PHPSQLCreator(); $parsedSql['SELECT'] = $select; - $this->processedSql = $generator->create($parsedSql); - $this->columnDescriptors = $columnDescriptors; + $processedSql = $generator->create($parsedSql); // 2: let's compute the count query if needed - if ($this->sqlCount === null) { + if ($sqlCount === null) { $parsedSqlCount = $this->generateParsedSqlCount($parsedSql); - $this->processedSqlCount = $generator->create($parsedSqlCount); + $processedSqlCount = $generator->create($parsedSqlCount); } else { - $this->processedSqlCount = $this->sqlCount; + $processedSqlCount = $sqlCount; } + + return [$processedSql, $processedSqlCount, $columnDescriptors]; } private function formatSelect($baseSelect) @@ -125,7 +163,7 @@ private function formatSelect($baseSelect) } $noQuotes = $entry['no_quotes']; - if ($noQuotes['delim'] != '.' || count($noQuotes['parts']) !== 2) { + if ($noQuotes['delim'] !== '.' || count($noQuotes['parts']) !== 2) { $formattedSelect[] = $entry; continue; } @@ -204,13 +242,18 @@ private function generateParsedSqlCount($parsedSql) private function generateSimpleSqlCount($parsedSql) { - return [[ + $parsedSql['SELECT'] = [[ 'expr_type' => 'aggregate_function', - 'alias' => false, + 'alias' => [ + 'as' => true, + 'name' => 'cnt', + ], 'base_expr' => 'COUNT', 'sub_tree' => $parsedSql['SELECT'], 'delim' => false, ]]; + + return $parsedSql; } private function generateGroupedSqlCount($parsedSql) @@ -219,7 +262,10 @@ private function generateGroupedSqlCount($parsedSql) unset($parsedSql['GROUP']); $parsedSql['SELECT'] = [[ 'expr_type' => 'aggregate_function', - 'alias' => false, + 'alias' => [ + 'as' => true, + 'name' => 'cnt', + ], 'base_expr' => 'COUNT', 'sub_tree' => array_merge([[ 'expr_type' => 'reserved', @@ -236,7 +282,10 @@ private function generateWrappedSqlCount($parsedSql) return [ 'SELECT' => [[ 'expr_type' => 'aggregate_function', - 'alias' => false, + 'alias' => [ + 'as' => true, + 'name' => 'cnt', + ], 'base_expr' => 'COUNT', 'sub_tree' => [ [ From d6d256f02b9c179b0e7b4e772a687462a5c1e17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 22 Nov 2017 12:04:03 +0100 Subject: [PATCH 3/3] Fixing findFromRawSql from simple SQL queries --- .../FindObjectsFromRawSqlQueryFactory.php | 8 +++++++- tests/Dao/TestCountryDao.php | 14 ++++++++++++++ tests/TDBMDaoGeneratorTest.php | 12 ++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php b/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php index 79797c24..1c1195ff 100644 --- a/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php +++ b/src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php @@ -249,7 +249,13 @@ private function generateSimpleSqlCount($parsedSql) 'name' => 'cnt', ], 'base_expr' => 'COUNT', - 'sub_tree' => $parsedSql['SELECT'], + 'sub_tree' => [ + [ + 'expr_type' => 'colref', + 'base_expr' => '*', + 'sub_tree' => false + ] + ], 'delim' => false, ]]; diff --git a/tests/Dao/TestCountryDao.php b/tests/Dao/TestCountryDao.php index a4d3f7a2..f550a710 100644 --- a/tests/Dao/TestCountryDao.php +++ b/tests/Dao/TestCountryDao.php @@ -40,6 +40,20 @@ public function getCountriesUsingUnion() SELECT country.* FROM country WHERE country.id = 2 +SQL; + + return $this->findFromRawSql($sql); + } + + /** + * @return CountryBean[]|Result + */ + public function getCountriesUsingSimpleQuery() + { + $sql = <<findFromRawSql($sql); diff --git a/tests/TDBMDaoGeneratorTest.php b/tests/TDBMDaoGeneratorTest.php index 574ff667..2b863150 100644 --- a/tests/TDBMDaoGeneratorTest.php +++ b/tests/TDBMDaoGeneratorTest.php @@ -696,6 +696,18 @@ public function testFindFromRawSqlWithUnion() $this->assertEquals(1, $countries[0]->getId()); } + /** + * @depends testDaoGeneration + */ + public function testFindFromRawSqlWithSimpleQuery() + { + $countryDao = new TestCountryDao($this->tdbmService); + $countries = $countryDao->getCountriesUsingSimpleQuery(); + + $this->assertCount(1, $countries); + $this->assertEquals(1, $countries[0]->getId()); + } + /** * @depends testDaoGeneration */