Skip to content

Commit

Permalink
Merge pull request #52 from moufmouf/union_raw_queries
Browse files Browse the repository at this point in the history
findFromRawSql does not support UNION queries
  • Loading branch information
moufmouf authored Nov 22, 2017
2 parents f774a43 + d6d256f commit 4f6e3ec
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 27 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
107 changes: 81 additions & 26 deletions src/QueryFactory/FindObjectsFromRawSqlQueryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,14 +21,6 @@ class FindObjectsFromRawSqlQueryFactory implements QueryFactory
* @var Schema
*/
private $schema;
/**
* @var string
*/
private $sql;
/**
* @var string
*/
private $sqlCount;
/**
* @var string
*/
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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;
}
Expand Down Expand Up @@ -204,13 +242,24 @@ 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'],
'sub_tree' => [
[
'expr_type' => 'colref',
'base_expr' => '*',
'sub_tree' => false
]
],
'delim' => false,
]];

return $parsedSql;
}

private function generateGroupedSqlCount($parsedSql)
Expand All @@ -219,7 +268,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',
Expand All @@ -236,7 +288,10 @@ private function generateWrappedSqlCount($parsedSql)
return [
'SELECT' => [[
'expr_type' => 'aggregate_function',
'alias' => false,
'alias' => [
'as' => true,
'name' => 'cnt',
],
'base_expr' => 'COUNT',
'sub_tree' => [
[
Expand Down
32 changes: 32 additions & 0 deletions tests/Dao/TestCountryDao.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,38 @@ 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 = <<<SQL
SELECT country.*
FROM country
WHERE country.id = 1
UNION
SELECT country.*
FROM country
WHERE country.id = 2
SQL;

return $this->findFromRawSql($sql);
}

/**
* @return CountryBean[]|Result
*/
public function getCountriesUsingSimpleQuery()
{
$sql = <<<SQL
SELECT country.*
FROM country
WHERE country.id = 1
SQL;

return $this->findFromRawSql($sql);
Expand Down
24 changes: 24 additions & 0 deletions tests/TDBMDaoGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,30 @@ 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
*/
public function testFindFromRawSqlWithSimpleQuery()
{
$countryDao = new TestCountryDao($this->tdbmService);
$countries = $countryDao->getCountriesUsingSimpleQuery();

$this->assertCount(1, $countries);
$this->assertEquals(1, $countries[0]->getId());
}

/**
* @depends testDaoGeneration
*/
Expand Down

0 comments on commit 4f6e3ec

Please sign in to comment.