diff --git a/src/SQLParser/Query/StatementFactory.php b/src/SQLParser/Query/StatementFactory.php index e5cebaf..9447f17 100644 --- a/src/SQLParser/Query/StatementFactory.php +++ b/src/SQLParser/Query/StatementFactory.php @@ -92,14 +92,27 @@ 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); - return new Union($selects); - } else { - throw new \BadMethodCallException('Unknown query'); + $union = new Union($selects, $isUnionAll); + + if (isset($desc['0']) && isset($desc['0']['ORDER'])) { + $order = NodeFactory::mapArrayToNodeObjectList($desc['0']['ORDER']); + $order = NodeFactory::simplify($order); + $union->setOrder($order); + } + + return $union; } + + throw new \BadMethodCallException('Unknown query'); } /** diff --git a/src/SQLParser/Query/Union.php b/src/SQLParser/Query/Union.php index 255ecf3..5478c5a 100644 --- a/src/SQLParser/Query/Union.php +++ b/src/SQLParser/Query/Union.php @@ -25,13 +25,42 @@ 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 */ + 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; } /** @@ -43,6 +72,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 +89,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 +111,16 @@ 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); + if ($order) { + $sql .= "\nORDER BY ".$order; + } + } return $sql; } @@ -99,27 +139,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..64b1967 100644 --- a/tests/Mouf/Database/MagicQueryTest.php +++ b/tests/Mouf/Database/MagicQueryTest.php @@ -186,7 +186,13 @@ 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 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))); @@ -478,4 +484,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)) + ); + } }