Skip to content

Commit 1e504bf

Browse files
committed
Allow UNION ALL in subqueries
1 parent 7f50e11 commit 1e504bf

File tree

3 files changed

+60
-37
lines changed

3 files changed

+60
-37
lines changed

src/Parser/SQLParser.php

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -165,27 +165,6 @@ private static function parseImpl(string $sql)
165165
case 'SELECT':
166166
$select = new SelectParser(0, $tokens, $sql);
167167
list($pointer, $query) = $select->parse();
168-
if (\array_key_exists($pointer, $tokens)) {
169-
$next = $tokens[$pointer] ?? null;
170-
$val = $next ? $next->value : 'null';
171-
while ($next !== null
172-
&& ($next->value === 'UNION' || $next->value === 'INTERSECT' || $next->value === 'EXCEPT')
173-
) {
174-
$type = $next->value;
175-
if ($next->value === 'UNION') {
176-
$next_plus = $tokens[$pointer + 1];
177-
if ($next_plus->value === 'ALL') {
178-
$type = 'UNION_ALL';
179-
$pointer++;
180-
}
181-
}
182-
$pointer++;
183-
$select = new SelectParser($pointer, $tokens, $sql);
184-
list($pointer, $q) = $select->parse();
185-
$query->addMultiQuery($type, $q);
186-
$next = $tokens[$pointer] ?? null;
187-
}
188-
}
189168
return $query;
190169
case 'UPDATE':
191170
$update = new UpdateParser($tokens, $sql);

src/Parser/SelectParser.php

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function __construct(int $pointer, array $tokens, string $sql)
4949
}
5050

5151
/**
52-
* @return array{0:int, 1:SelectQuery}
52+
* @return array{int, SelectQuery}
5353
*/
5454
public function parse()
5555
{
@@ -70,6 +70,35 @@ public function parse()
7070
throw new SQLFakeParseException("Parser error: expected SELECT");
7171
}
7272

73+
$query = $this->parseMainSelect();
74+
75+
if (\array_key_exists($this->pointer, $this->tokens)) {
76+
$next = $this->tokens[$this->pointer] ?? null;
77+
$val = $next ? $next->value : 'null';
78+
while ($next !== null
79+
&& ($next->value === 'UNION' || $next->value === 'INTERSECT' || $next->value === 'EXCEPT')
80+
) {
81+
$type = $next->value;
82+
if ($next->value === 'UNION') {
83+
$next_plus = $this->tokens[$this->pointer + 1];
84+
if ($next_plus->value === 'ALL') {
85+
$type = 'UNION_ALL';
86+
$this->pointer++;
87+
}
88+
}
89+
$this->pointer++;
90+
$select = new SelectParser($this->pointer, $this->tokens, $this->sql);
91+
list($this->pointer, $q) = $select->parse();
92+
$query->addMultiQuery($type, $q);
93+
$next = $this->tokens[$this->pointer] ?? null;
94+
}
95+
}
96+
97+
return [$this->pointer, $query];
98+
}
99+
100+
private function parseMainSelect() : SelectQuery
101+
{
73102
$query = new SelectQuery($this->sql);
74103
$this->pointer++;
75104

@@ -125,7 +154,7 @@ public function parse()
125154
if ($this->pointer !== $count - 1) {
126155
throw new SQLFakeParseException("Unexpected tokens after semicolon");
127156
}
128-
return [$this->pointer, $query];
157+
return $query;
129158
} else {
130159
throw new SQLFakeParseException("Unexpected {$token->value}");
131160
}
@@ -196,7 +225,7 @@ public function parse()
196225
case 'UNION':
197226
case 'EXCEPT':
198227
case 'INTERSECT':
199-
return [$this->pointer, $query];
228+
return $query;
200229
break;
201230
default:
202231
throw new SQLFakeParseException("Unexpected {$token->value}");
@@ -236,8 +265,10 @@ public function parse()
236265
}
237266
break;
238267
}
268+
239269
$this->pointer++;
240270
}
241-
return [$this->pointer, $query];
271+
272+
return $query;
242273
}
243274
}

tests/SelectParseTest.php

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public function testSimpleParse()
77
{
88
$query = 'SELECT `foo` FROM `bar` WHERE `id` = 1';
99

10-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($query);
10+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($query);
1111

1212
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
1313
}
@@ -16,7 +16,7 @@ public function testCast()
1616
{
1717
$query = 'SELECT CAST(1 + 2 AS UNSIGNED) as `a`';
1818

19-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($query);
19+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($query);
2020

2121
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
2222

@@ -32,7 +32,7 @@ public function testAddFunctionResults()
3232
{
3333
$query = 'SELECT IFNULL(`a`.`b`, 0) + ISNULL(`a`.`c`)';
3434

35-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($query);
35+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($query);
3636

3737
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
3838

@@ -52,7 +52,7 @@ public function testSubqueryCalculation()
5252
{
5353
$query = 'SELECT (SELECT 2) + (SELECT 3) as `a`';
5454

55-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($query);
55+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($query);
5656

5757
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
5858

@@ -68,7 +68,7 @@ public function testFunctionOperatorPrecedence()
6868
{
6969
$sql = 'SELECT SUM(`a`.`foo` - IFNULL(`b`.`bar`, 0) - `c`.`baz`) as `a`';
7070

71-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
71+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
7272

7373
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
7474

@@ -95,7 +95,7 @@ public function testFunctionOperatorPrecedence()
9595
public function testWrappedSubquery()
9696
{
9797
$sql = 'SELECT * FROM (((SELECT 5))) AS all_parts';
98-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
98+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
9999

100100
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
101101
}
@@ -104,7 +104,7 @@ public function testCaseWhenNotExists()
104104
{
105105
$sql = "SELECT CASE WHEN NOT EXISTS (SELECT * FROM `bar`) THEN 'BAZ' ELSE NULL END FROM `bam`";
106106

107-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
107+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
108108

109109
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
110110
}
@@ -113,7 +113,7 @@ public function testInterval()
113113
{
114114
$sql = 'SELECT DATE_ADD(\'2008-01-02\', INTERVAL 31 DAY)';
115115

116-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
116+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
117117

118118
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
119119
}
@@ -123,7 +123,7 @@ public function testUnionInSubquery()
123123
$sql = "SELECT *
124124
FROM ((SELECT * FROM `c`) UNION ALL (SELECT * FROM `d`)) AS `bar`";
125125

126-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
126+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
127127

128128
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
129129
}
@@ -135,7 +135,7 @@ public function testAndNotExists()
135135
WHERE `a` > 0
136136
AND NOT EXISTS (SELECT * FROM `bar`)";
137137

138-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
138+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
139139

140140
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
141141
}
@@ -145,7 +145,7 @@ public function testSumCaseMultiplied()
145145
$sql = "SELECT SUM((CASE WHEN `a`.`b` THEN 1 ELSE 0 END) * (CASE WHEN `a`.`c` THEN 1 ELSE 0 END))
146146
FROM `foo`";
147147

148-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
148+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
149149

150150
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
151151
}
@@ -154,8 +154,21 @@ public function testBadAs()
154154
{
155155
$sql = "SELECT (@refund_date := `ordered_transactions`.`refund_date`) as refund_date FROM `foo`";
156156

157-
$select_query = \Vimeo\MysqlEngine\Parser\SqlParser::parse($sql);
157+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
158158

159159
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
160160
}
161+
162+
public function testParseComplexJoin()
163+
{
164+
$sql = "SELECT * FROM (SELECT * FROM `foo` UNION ALL SELECT * FROM `bar`) AS `baz`";
165+
166+
$select_query = \Vimeo\MysqlEngine\Parser\SQLParser::parse($sql);
167+
168+
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\SelectQuery::class, $select_query);
169+
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\FromClause::class, $select_query->fromClause);
170+
$this->assertCount(1, $select_query->fromClause->tables);
171+
$this->assertInstanceOf(\Vimeo\MysqlEngine\Query\Expression\SubqueryExpression::class, $select_query->fromClause->tables[0]['subquery']);
172+
$this->assertNotEmpty($select_query->fromClause->tables[0]['subquery']->query->multiQueries);
173+
}
161174
}

0 commit comments

Comments
 (0)