From 5107ebf5c944e62692182feab3359f1cc1fc3423 Mon Sep 17 00:00:00 2001 From: Mahamat GUIHINI Date: Tue, 23 May 2023 22:16:31 +0000 Subject: [PATCH 1/2] Adding ORDER support for UNION queries (#86) --- src/SQLParser/Query/StatementFactory.php | 10 +++- src/SQLParser/Query/Union.php | 62 ++++++++++++++++++++---- tests/Mouf/Database/MagicQueryTest.php | 32 +++++++++++- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/SQLParser/Query/StatementFactory.php b/src/SQLParser/Query/StatementFactory.php index e5cebaf..ff50022 100644 --- a/src/SQLParser/Query/StatementFactory.php +++ b/src/SQLParser/Query/StatementFactory.php @@ -96,7 +96,15 @@ public static function toObject(array $desc) /** @var Select[] $selects */ $selects = array_map([self::class, 'toObject'], $desc['UNION']); - return new Union($selects); + $union = new Union($selects); + + if (isset($desc['0']) && isset($desc['0']['ORDER'])) { + $order = NodeFactory::mapArrayToNodeObjectList($desc['0']['ORDER']); + $order = NodeFactory::simplify($order); + $union->setOrder($order); + } + + return $union; } else { throw new \BadMethodCallException('Unknown query'); } diff --git a/src/SQLParser/Query/Union.php b/src/SQLParser/Query/Union.php index 255ecf3..f92cf25 100644 --- a/src/SQLParser/Query/Union.php +++ b/src/SQLParser/Query/Union.php @@ -34,6 +34,29 @@ public function __construct(array $selects) $this->selects = $selects; } + /** @var NodeInterface[]|NodeInterface */ + private $order; + + /** + * Returns the list of order statements. + * + * @return NodeInterface[]|NodeInterface + */ + public function getOrder() + { + return $this->order; + } + + /** + * Sets the list of order statements. + * + * @param NodeInterface[]|NodeInterface $order + */ + public function setOrder($order): void + { + $this->order = $order; + } + /** * @param MoufManager $moufManager * @@ -43,6 +66,7 @@ public function toInstanceDescriptor(MoufManager $moufManager) { $instanceDescriptor = $moufManager->createInstance(get_called_class()); $instanceDescriptor->getProperty('selects')->setValue(NodeFactory::nodeToInstanceDescriptor($this->selects, $moufManager)); + $instanceDescriptor->getProperty('order')->setValue(NodeFactory::nodeToInstanceDescriptor($this->order, $moufManager)); return $instanceDescriptor; } @@ -59,6 +83,7 @@ public function overwriteInstanceDescriptor($name, MoufManager $moufManager) //$name = $moufManager->findInstanceName($this); $instanceDescriptor = $moufManager->getInstanceDescriptor($name); $instanceDescriptor->getProperty('selects')->setValue(NodeFactory::nodeToInstanceDescriptor($this->selects, $moufManager)); + $instanceDescriptor->getProperty('order')->setValue(NodeFactory::nodeToInstanceDescriptor($this->order, $moufManager)); return $instanceDescriptor; } @@ -80,7 +105,14 @@ public function toSql(array $parameters, AbstractPlatform $platform, int $indent return $select->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); }, $this->selects); - $sql = implode(' UNION ', $selectsSql); + $sql = '(' . implode(') UNION (', $selectsSql) . ')'; + + if (!empty($this->order)) { + $order = NodeFactory::toSql($this->order, $platform, $parameters, ',', false, $indent + 2, $conditionsMode, $extrapolateParameters); + if ($order) { + $sql .= "\nORDER BY ".$order; + } + } return $sql; } @@ -99,27 +131,37 @@ public function walk(VisitorInterface $visitor) } if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) { $this->walkChildren($this->selects, $visitor); + $this->walkChildren($this->order, $visitor); } return $visitor->leaveNode($node); } /** - * @param array $children + * @param array|NodeInterface|null $children * @param VisitorInterface $visitor */ - private function walkChildren(array &$children, VisitorInterface $visitor): void + private function walkChildren(&$children, VisitorInterface $visitor): void { if ($children) { - foreach ($children as $key => $operand) { - if ($operand) { - $result2 = $operand->walk($visitor); - if ($result2 === NodeTraverser::REMOVE_NODE) { - unset($children[$key]); - } elseif ($result2 instanceof NodeInterface) { - $children[$key] = $result2; + if (is_array($children)) { + foreach ($children as $key => $operand) { + if ($operand) { + $result2 = $operand->walk($visitor); + if ($result2 === NodeTraverser::REMOVE_NODE) { + unset($children[$key]); + } elseif ($result2 instanceof NodeInterface) { + $children[$key] = $result2; + } } } + } else { + $result2 = $children->walk($visitor); + if ($result2 === NodeTraverser::REMOVE_NODE) { + $children = null; + } elseif ($result2 instanceof NodeInterface) { + $children = $result2; + } } } } diff --git a/tests/Mouf/Database/MagicQueryTest.php b/tests/Mouf/Database/MagicQueryTest.php index 3c5e789..7aef086 100644 --- a/tests/Mouf/Database/MagicQueryTest.php +++ b/tests/Mouf/Database/MagicQueryTest.php @@ -186,7 +186,7 @@ public function testStandardSelect() $this->assertEquals('SELECT COUNT(DISTINCT a, b) FROM users', self::simplifySql($magicQuery->build($sql))); $sql = 'SELECT a FROM users UNION SELECT a FROM users'; - $this->assertEquals('SELECT a FROM users UNION SELECT a FROM users', self::simplifySql($magicQuery->build($sql))); + $this->assertEquals('(SELECT a FROM users) UNION (SELECT a FROM users)', self::simplifySql($magicQuery->build($sql))); $sql = 'SELECT a FROM users u, users u2'; $this->assertEquals('SELECT a FROM users u CROSS JOIN users u2', self::simplifySql($magicQuery->build($sql))); @@ -478,4 +478,34 @@ public function testPrepareStatementCache(): void $this->assertEquals('SELECT * FROM users WHERE 0 <> 0', self::simplifySql($magicQuery->buildPreparedStatement('SELECT * FROM users WHERE id IN :users', ['users' => []]))); $this->assertEquals('SELECT * FROM users WHERE id IN (:users)', self::simplifySql($magicQuery->buildPreparedStatement('SELECT * FROM users WHERE id IN :users', ['users' => [1]]))); } + + public function testUnionAndOrderBy(): void + { + $magicQuery = new MagicQuery(null, new ArrayCache()); + + $query = '(SELECT id FROM users) UNION (SELECT id FROM users) ORDER BY users.id ASC'; + + $this->assertEquals( + '(SELECT id FROM users) UNION (SELECT id FROM users) ORDER BY users.id ASC', + self::simplifySql($magicQuery->buildPreparedStatement($query)) + ); + + $query = '(SELECT id FROM users) UNION (SELECT id FROM users ORDER BY users.id ASC)'; + $this->assertEquals( + '(SELECT id FROM users) UNION (SELECT id FROM users ORDER BY users.id ASC)', + self::simplifySql($magicQuery->buildPreparedStatement($query)) + ); + + $query = '(SELECT id FROM users ORDER BY users.id ASC) UNION (SELECT id FROM users)'; + $this->assertEquals( + '(SELECT id FROM users ORDER BY users.id ASC) UNION (SELECT id FROM users)', + self::simplifySql($magicQuery->buildPreparedStatement($query)) + ); + + $query = 'SELECT id FROM users UNION SELECT id FROM users ORDER BY users.id ASC'; + $this->assertEquals( + '(SELECT id FROM users) UNION (SELECT id FROM users) ORDER BY users.id ASC', + self::simplifySql($magicQuery->buildPreparedStatement($query)) + ); + } } From 677c031c64cf9221deb3a5599c891af0a1e6ae86 Mon Sep 17 00:00:00 2001 From: Max PRUDHOMME Date: Fri, 1 Mar 2024 18:36:58 +0100 Subject: [PATCH 2/2] Add support for UNION ALL and UNION DISTINCT (#89) The code now supports UNION ALL and UNION DISTINCT statements in addition to the standard UNION. The MagicQueryTest.php file has been updated with added test cases to reflect these changes. Union.php and StatementFactory.php were modified to handle the additional logic required for the new union types. --- src/SQLParser/Query/StatementFactory.php | 15 ++++++++++----- src/SQLParser/Query/Union.php | 12 ++++++++++-- tests/Mouf/Database/MagicQueryTest.php | 6 ++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/SQLParser/Query/StatementFactory.php b/src/SQLParser/Query/StatementFactory.php index ff50022..9447f17 100644 --- a/src/SQLParser/Query/StatementFactory.php +++ b/src/SQLParser/Query/StatementFactory.php @@ -92,11 +92,16 @@ public static function toObject(array $desc) } return $select; - } elseif (isset($desc['UNION'])) { + } + // UNION and UNION DISTINCT have similar behavior + if (isset($desc['UNION']) || isset($desc['UNION ALL']) || isset($desc['UNION DISTINCT'])) { + $isUnionAll = isset($desc['UNION ALL']); + $unionStatement = $desc['UNION'] ?? ($desc['UNION ALL'] ?? $desc['UNION DISTINCT']); + /** @var Select[] $selects */ - $selects = array_map([self::class, 'toObject'], $desc['UNION']); + $selects = array_map([self::class, 'toObject'], $unionStatement); - $union = new Union($selects); + $union = new Union($selects, $isUnionAll); if (isset($desc['0']) && isset($desc['0']['ORDER'])) { $order = NodeFactory::mapArrayToNodeObjectList($desc['0']['ORDER']); @@ -105,9 +110,9 @@ public static function toObject(array $desc) } return $union; - } else { - throw new \BadMethodCallException('Unknown query'); } + + throw new \BadMethodCallException('Unknown query'); } /** diff --git a/src/SQLParser/Query/Union.php b/src/SQLParser/Query/Union.php index f92cf25..5478c5a 100644 --- a/src/SQLParser/Query/Union.php +++ b/src/SQLParser/Query/Union.php @@ -25,13 +25,19 @@ class Union implements StatementInterface, NodeInterface */ private $selects; + /** + * @var bool + */ + private $isUnionAll; + /** * Union constructor. * @param Select[] $selects */ - public function __construct(array $selects) + public function __construct(array $selects, bool $isUnionAll) { $this->selects = $selects; + $this->isUnionAll = $isUnionAll; } /** @var NodeInterface[]|NodeInterface */ @@ -105,7 +111,9 @@ public function toSql(array $parameters, AbstractPlatform $platform, int $indent return $select->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); }, $this->selects); - $sql = '(' . implode(') UNION (', $selectsSql) . ')'; + $unionStatement = $this->isUnionAll ? 'UNION ALL' : 'UNION'; + + $sql = '(' . implode(') ' . $unionStatement . ' (', $selectsSql) . ')'; if (!empty($this->order)) { $order = NodeFactory::toSql($this->order, $platform, $parameters, ',', false, $indent + 2, $conditionsMode, $extrapolateParameters); diff --git a/tests/Mouf/Database/MagicQueryTest.php b/tests/Mouf/Database/MagicQueryTest.php index 7aef086..64b1967 100644 --- a/tests/Mouf/Database/MagicQueryTest.php +++ b/tests/Mouf/Database/MagicQueryTest.php @@ -188,6 +188,12 @@ public function testStandardSelect() $sql = 'SELECT a FROM users UNION SELECT a FROM users'; $this->assertEquals('(SELECT a FROM users) UNION (SELECT a FROM users)', self::simplifySql($magicQuery->build($sql))); + $sql = 'SELECT a FROM users UNION ALL SELECT a FROM users'; + $this->assertEquals('(SELECT a FROM users) UNION ALL (SELECT a FROM users)', self::simplifySql($magicQuery->build($sql))); + + $sql = 'SELECT a FROM users UNION DISTINCT SELECT a FROM users'; + $this->assertEquals('(SELECT a FROM users) UNION (SELECT a FROM users)', self::simplifySql($magicQuery->build($sql))); + $sql = 'SELECT a FROM users u, users u2'; $this->assertEquals('SELECT a FROM users u CROSS JOIN users u2', self::simplifySql($magicQuery->build($sql)));