Skip to content

Commit 946320a

Browse files
committed
Implement disambiguation for unqualified columns in GROUP BY and HAVING
1 parent 9c3afe6 commit 946320a

File tree

3 files changed

+346
-18
lines changed

3 files changed

+346
-18
lines changed

tests/WP_SQLite_Driver_Tests.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6466,6 +6466,116 @@ public function testSelectOrderByAmbiguousColumnErrorWithoutSelectList(): void {
64666466
$this->assertQuery( 'SELECT 1 FROM t1 JOIN t2 ON t2.id = t1.id ORDER BY name' );
64676467
}
64686468

6469+
public function testSelectGroupByAmbiguousColumnResolution(): void {
6470+
$this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' );
6471+
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
6472+
$this->assertQuery( 'INSERT INTO t1 (id, name) VALUES (1, "A"), (2, "A")' );
6473+
$this->assertQuery( 'INSERT INTO t2 (id, name) VALUES (1, "B1"), (2, "B2")' );
6474+
6475+
// The "name" column will be resolved to "t1.name" as per the SELECT item.
6476+
$result = $this->assertQuery( 'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name' );
6477+
$this->assertEquals(
6478+
array( (object) array( 'name' => 'A' ) ),
6479+
$result
6480+
);
6481+
6482+
// The "name" column will be resolved to "t2.name" as per the SELECT item.
6483+
$result = $this->assertQuery( 'SELECT t2.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name' );
6484+
$this->assertEquals(
6485+
array(
6486+
(object) array( 'name' => 'B1' ),
6487+
(object) array( 'name' => 'B2' ),
6488+
),
6489+
$result
6490+
);
6491+
6492+
// Parenthesized column reference can be used in both SELECT and GROUP BY lists.
6493+
$result = $this->assertQuery( 'SELECT (t1.name) FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY (((name)))' );
6494+
$this->assertEquals(
6495+
array( (object) array( '(t1.name)' => 'A' ) ),
6496+
$result
6497+
);
6498+
6499+
/*
6500+
* The following query fails with "ambiguous column name" in MySQL, but
6501+
* in SQLite, it works. It's OK to keep this difference as MySQL behaves
6502+
* rather strangely in this case:
6503+
*
6504+
* 1) This is OK in MySQL:
6505+
* SELECT t1.name AS col, 123 AS col ... GROUP BY col
6506+
* 2) This fails in MySQL:
6507+
* SELECT t1.name AS col, t2.name AS col ... GROUP BY col
6508+
*/
6509+
$this->assertQuery( 'SELECT t1.name AS col, t2.name AS col FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY col' );
6510+
}
6511+
6512+
public function testSelectGroupByAmbiguousColumnError(): void {
6513+
$this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' );
6514+
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
6515+
6516+
$this->expectException( 'WP_SQLite_Driver_Exception' );
6517+
$this->expectExceptionMessage( 'ambiguous column name: name' );
6518+
$this->assertQuery( 'SELECT t1.name, t2.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name' );
6519+
}
6520+
6521+
public function testSelectGroupByAmbiguousColumnErrorWithoutSelectList(): void {
6522+
$this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' );
6523+
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
6524+
6525+
$this->expectException( 'WP_SQLite_Driver_Exception' );
6526+
$this->expectExceptionMessage( 'ambiguous column name: name' );
6527+
$this->assertQuery( 'SELECT 1 FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name' );
6528+
}
6529+
6530+
public function testSelectHavingAmbiguousColumnResolution(): void {
6531+
$this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' );
6532+
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
6533+
$this->assertQuery( 'INSERT INTO t1 (id, name) VALUES (1, "A"), (2, "A")' );
6534+
$this->assertQuery( 'INSERT INTO t2 (id, name) VALUES (1, "B1"), (2, "B2")' );
6535+
6536+
// The "name" column will be resolved to "t1.name" as per the SELECT item.
6537+
$result = $this->assertQuery( 'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name' );
6538+
$this->assertEquals( array(), $result );
6539+
6540+
// The "name" column will be resolved to "t2.name" as per the SELECT item.
6541+
$result = $this->assertQuery( 'SELECT t2.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name' );
6542+
$this->assertEquals( array(), $result );
6543+
6544+
// Parenthesized column reference can be used in both SELECT and GROUP BY lists.
6545+
$result = $this->assertQuery( 'SELECT (t1.name) FROM t1 JOIN t2 ON t2.id = t1.id HAVING (((name)))' );
6546+
$this->assertEquals( array(), $result );
6547+
6548+
/*
6549+
* The following query fails with "ambiguous column name" in MySQL, but
6550+
* in SQLite, it works. It's OK to keep this difference as MySQL behaves
6551+
* rather strangely in this case:
6552+
*
6553+
* 1) This is OK in MySQL:
6554+
* SELECT t1.name AS col, 123 AS col ... HAVING col
6555+
* 2) This fails in MySQL:
6556+
* SELECT t1.name AS col, t2.name AS col ... HAVING col
6557+
*/
6558+
$this->assertQuery( 'SELECT t1.name AS col, t2.name AS col FROM t1 JOIN t2 ON t2.id = t1.id HAVING col' );
6559+
}
6560+
6561+
public function testSelectHavingAmbiguousColumnError(): void {
6562+
$this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' );
6563+
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
6564+
6565+
$this->expectException( 'WP_SQLite_Driver_Exception' );
6566+
$this->expectExceptionMessage( 'ambiguous column name: name' );
6567+
$this->assertQuery( 'SELECT t1.name, t2.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name' );
6568+
}
6569+
6570+
public function testSelectHavingAmbiguousColumnErrorWithoutSelectList(): void {
6571+
$this->assertQuery( 'CREATE TABLE t1 (id INT, name TEXT)' );
6572+
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
6573+
6574+
$this->expectException( 'WP_SQLite_Driver_Exception' );
6575+
$this->expectExceptionMessage( 'ambiguous column name: name' );
6576+
$this->assertQuery( 'SELECT 1 FROM t1 JOIN t2 ON t2.id = t1.id HAVING name' );
6577+
}
6578+
64696579
public function testRollbackNonExistentTransactionSavepoint(): void {
64706580
$this->expectException( 'WP_SQLite_Driver_Exception' );
64716581
$this->expectExceptionMessage( 'no such savepoint: sp1' );

tests/WP_SQLite_Driver_Translation_Tests.php

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,8 +1406,180 @@ public function testSelectOrderByAmbiguousColumnResolution(): void {
14061406

14071407
// When the ORDER BY item uses an alias, there is no ambiguity.
14081408
$this->assertQuery(
1409-
'SELECT `t1`.`name` AS `t1_name` FROM `t1` JOIN `t2` ON `t2`.`t1_id` = `t1`.`id` ORDER BY `t1_name` DESC',
1410-
'SELECT t1.name AS t1_name FROM t1 JOIN t2 ON t2.t1_id = t1.id ORDER BY `t1_name` DESC'
1409+
'SELECT `t1`.`name` AS `t1_name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` ORDER BY `t1_name` DESC',
1410+
'SELECT t1.name AS t1_name FROM t1 JOIN t2 ON t2.id = t1.id ORDER BY `t1_name` DESC'
1411+
);
1412+
}
1413+
1414+
public function testSelectGroupByAmbiguousColumnResolution(): void {
1415+
$this->driver->query( 'CREATE TABLE t1 (id INT, name TEXT)' );
1416+
$this->driver->query( 'CREATE TABLE t2 (id INT, name TEXT)' );
1417+
1418+
// Ambiguous column in GROUP BY clause is disambiguated by the SELECT item list.
1419+
$this->assertQuery(
1420+
'SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1`.`name`',
1421+
'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name'
1422+
);
1423+
1424+
// Multiple ambiguous columns in GROUP BY clause are also disambiguated.
1425+
$this->assertQuery(
1426+
'SELECT `t1`.`id` , `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1`.`id`, `t1`.`name`',
1427+
'SELECT t1.id, t1.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY id, name'
1428+
);
1429+
1430+
// The disambiguation works with subqueries.
1431+
$this->assertQuery(
1432+
'SELECT `name` FROM ( SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1`.`name` ) GROUP BY `name`',
1433+
'SELECT name FROM (SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name) GROUP BY name'
1434+
);
1435+
1436+
// The disambiguation works in both root and subquery contexts at the same time.
1437+
$this->assertQuery(
1438+
'SELECT `ta`.`name` FROM ( SELECT `t2`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t2`.`name` ) `ta` GROUP BY `ta`.`name`',
1439+
'SELECT ta.name FROM (SELECT t2.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name) ta GROUP BY name'
1440+
);
1441+
1442+
// When the SELECT item is nested in a simple parentheses expression, the disambiguation still works.
1443+
$this->assertQuery(
1444+
'SELECT ( ( ( `t1`.`name` ) ) ) AS `(((t1.name)))` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1`.`name`',
1445+
'SELECT (((t1.name))) FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name'
1446+
);
1447+
1448+
// When the GROUP BY item is nested in a simple parentheses expression, the disambiguation still works.
1449+
$this->assertQuery(
1450+
'SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1`.`name`',
1451+
'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY (((name)))'
1452+
);
1453+
1454+
// When the SELECT item is nested in a complex expression, the column is not disambiguated (like in MySQL).
1455+
$this->assertQuery(
1456+
"SELECT (`t1`.`name` || 'test') AS `CONCAT(t1.name, 'test')` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `name`",
1457+
"SELECT CONCAT(t1.name, 'test') FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name"
1458+
);
1459+
1460+
// When the GROUP BY item is nested in a complex expression, the column is not disambiguated (like in MySQL).
1461+
$this->assertQuery(
1462+
"SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY ( `name` || 'test' )",
1463+
"SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY (name || 'test')"
1464+
);
1465+
1466+
// When the SELECT list item uses an alias, the column is not disambiguated (like in MySQL).
1467+
$this->assertQuery(
1468+
'SELECT `t1`.`name` AS `t1_name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `name`',
1469+
'SELECT t1.name AS t1_name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name'
1470+
);
1471+
1472+
// When the SELECT item list is ambiguous, the GROUP BY column is not disambiguated (like in MySQL).
1473+
$this->assertQuery(
1474+
'SELECT `t1`.`name` , `t2`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `name`',
1475+
'SELECT t1.name, t2.name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY name'
1476+
);
1477+
1478+
/*
1479+
* The following edge case behaves differently than in MySQL.
1480+
* This seems to be due to a quirk in SQLite, where the behavior of the
1481+
* GROUP BY clause is different from the ORDER BY clause:
1482+
* - ORDER BY: SQLite will pick the first column or alias for sorting.
1483+
* - GROUP BY: SQLite will fail with an "ambiguous column name" error.
1484+
*
1485+
* @TODO: We can consider fixing this more correctly.
1486+
*/
1487+
$this->assertQuery(
1488+
"SELECT `t1`.`name` , 'test' AS `name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1`.`name`, `name`",
1489+
"SELECT t1.name, 'test' AS `name` FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY t1.name, name"
1490+
);
1491+
1492+
// When the GROUP BY item uses an alias, there is no ambiguity.
1493+
$this->assertQuery(
1494+
'SELECT `t1`.`name` AS `t1_name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY `t1_name`',
1495+
'SELECT t1.name AS t1_name FROM t1 JOIN t2 ON t2.id = t1.id GROUP BY `t1_name`'
1496+
);
1497+
}
1498+
1499+
public function testSelectHavingAmbiguousColumnResolution(): void {
1500+
$this->driver->query( 'CREATE TABLE t1 (id INT, name TEXT)' );
1501+
$this->driver->query( 'CREATE TABLE t2 (id INT, name TEXT)' );
1502+
1503+
// Ambiguous column in HAVING clause is disambiguated by the SELECT item list.
1504+
$this->assertQuery(
1505+
'SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t1`.`name`',
1506+
'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name'
1507+
);
1508+
1509+
// Multiple ambiguous columns in HAVING clause are also disambiguated (AND).
1510+
$this->assertQuery(
1511+
'SELECT `t1`.`id` , `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t1`.`id` AND `t1`.`name`',
1512+
'SELECT t1.id, t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING id AND name'
1513+
);
1514+
1515+
// Multiple ambiguous columns in HAVING clause are also disambiguated (OR).
1516+
$this->assertQuery(
1517+
'SELECT `t1`.`id` , `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t1`.`id` OR `t1`.`name`',
1518+
'SELECT t1.id, t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING id OR name'
1519+
);
1520+
1521+
// The disambiguation works with subqueries.
1522+
$this->assertQuery(
1523+
'SELECT `name` FROM ( SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t1`.`name` ) GROUP BY 1 HAVING `name`',
1524+
'SELECT name FROM (SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name) HAVING name'
1525+
);
1526+
1527+
// The disambiguation works in both root and subquery contexts at the same time.
1528+
$this->assertQuery(
1529+
'SELECT `ta`.`name` FROM ( SELECT `t2`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t2`.`name` ) `ta` GROUP BY 1 HAVING `ta`.`name`',
1530+
'SELECT ta.name FROM (SELECT t2.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name) ta HAVING name'
1531+
);
1532+
1533+
// When the HAVING item is nested in a simple parentheses expression, the disambiguation still works.
1534+
$this->assertQuery(
1535+
'SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t1`.`name`',
1536+
'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING (((name)))'
1537+
);
1538+
1539+
// When the SELECT item is nested in a complex expression, the column is not disambiguated (like in MySQL).
1540+
$this->assertQuery(
1541+
"SELECT (`t1`.`name` || 'test') AS `CONCAT(t1.name, 'test')` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `name`",
1542+
"SELECT CONCAT(t1.name, 'test') FROM t1 JOIN t2 ON t2.id = t1.id HAVING name"
1543+
);
1544+
1545+
// When the HAVING item is used in an aggregate function, the column is not disambiguated (like in MySQL).
1546+
$this->assertQuery(
1547+
'SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING COUNT ( `name` ) > 1',
1548+
'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING COUNT(name) > 1'
1549+
);
1550+
1551+
/*
1552+
* The following edge case behaves differently than in MySQL.
1553+
* This seems to be due to a quirk in SQLite, where the behavior of the
1554+
* HAVING clause is different from the ORDER BY clause:
1555+
* - ORDER BY: SQLite will pick the first column or alias for sorting.
1556+
* - HAVING: SQLite will fail with an "ambiguous column name" error.
1557+
*
1558+
* @TODO: We can consider fixing this more correctly.
1559+
*/
1560+
$this->assertQuery(
1561+
"SELECT `t1`.`name` , 'test' AS `name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `name`",
1562+
"SELECT t1.name, 'test' AS `name` FROM t1 JOIN t2 ON t2.id = t1.id HAVING name"
1563+
);
1564+
1565+
// When the HAVING item uses an alias, there is no ambiguity.
1566+
$this->assertQuery(
1567+
'SELECT `t1`.`name` AS `t1_name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `t1_name`',
1568+
'SELECT t1.name AS t1_name FROM t1 JOIN t2 ON t2.id = t1.id HAVING `t1_name`'
1569+
);
1570+
1571+
/*
1572+
* The following edge case should actually be disambiguated, as it is in MySQL.
1573+
* THe HAVING clause behaves strangely in MySQL:
1574+
* - HAVING COUNT(name), HAVING SUM(name), etc. are not disambiguated.
1575+
* - HAVING name = 1, HAVING (name = (name + 1)), etc. are disambiguated.
1576+
* - With HAVING, MySQL seems to only recognize the columns listed in the SELECT clause.
1577+
*
1578+
* @TODO: We can consider fixing this more correctly.
1579+
*/
1580+
$this->assertQuery(
1581+
'SELECT `t1`.`name` FROM `t1` JOIN `t2` ON `t2`.`id` = `t1`.`id` GROUP BY 1 HAVING `name` = 1',
1582+
'SELECT t1.name FROM t1 JOIN t2 ON t2.id = t1.id HAVING name = 1'
14111583
);
14121584
}
14131585

wp-includes/sqlite-ast/class-wp-sqlite-driver.php

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,24 +2512,10 @@ private function translate( $node ): ?string {
25122512

25132513
$rule_name = $node->rule_name;
25142514
switch ( $rule_name ) {
2515+
case 'querySpecification':
2516+
return $this->translate_query_specification( $node );
25152517
case 'queryExpression':
25162518
return $this->translate_query_expression( $node );
2517-
case 'querySpecification':
2518-
// Translate "HAVING ..." without "GROUP BY ..." to "GROUP BY 1 HAVING ...".
2519-
if ( $node->has_child_node( 'havingClause' ) && ! $node->has_child_node( 'groupByClause' ) ) {
2520-
$parts = array();
2521-
foreach ( $node->get_children() as $child ) {
2522-
if ( $child instanceof WP_Parser_Node && 'havingClause' === $child->rule_name ) {
2523-
$parts[] = 'GROUP BY 1';
2524-
}
2525-
$part = $this->translate( $child );
2526-
if ( null !== $part ) {
2527-
$parts[] = $part;
2528-
}
2529-
}
2530-
return implode( ' ', $parts );
2531-
}
2532-
return $this->translate_sequence( $node->get_children() );
25332519
case 'qualifiedIdentifier':
25342520
case 'tableRefWithWildcard':
25352521
$parts = $node->get_descendant_nodes( 'identifier' );
@@ -2907,6 +2893,66 @@ private function translate_qualified_identifier(
29072893
return implode( '.', $parts );
29082894
}
29092895

2896+
private function translate_query_specification( WP_Parser_Node $node ): string {
2897+
$group_by = $node->get_first_child_node( 'groupByClause' );
2898+
$having = $node->get_first_child_node( 'havingClause' );
2899+
2900+
$group_by_clause = null;
2901+
$having_clause = null;
2902+
if ( $group_by || $having ) {
2903+
$select_item_list = $node->get_first_child_node( 'selectItemList' );
2904+
$disambiguation_map = $this->create_select_item_disambiguation_map( $select_item_list );
2905+
2906+
// Disambiguate the GROUP BY clause column references.
2907+
$disambiguated_group_by_list = array();
2908+
if ( $group_by ) {
2909+
$group_by_list = $group_by->get_first_child_node( 'orderList' );
2910+
foreach ( $group_by_list->get_child_nodes() as $group_by_item ) {
2911+
$group_by_expr = $group_by_item->get_first_child_node( 'expr' );
2912+
$disambiguated_item = $this->disambiguate_item( $disambiguation_map, $group_by_expr );
2913+
$disambiguated_group_by_list[] = $disambiguated_item ?? $this->translate( $group_by_expr );
2914+
}
2915+
$group_by_clause = 'GROUP BY ' . implode( ', ', $disambiguated_group_by_list );
2916+
}
2917+
2918+
// Disambiguate the HAVING clause column references.
2919+
$disambiguated_having_list = array();
2920+
if ( $having ) {
2921+
$having_expr = $having->get_first_child_node();
2922+
$having_expr_children = $having_expr->get_children();
2923+
foreach ( $having_expr_children as $having_item ) {
2924+
if ( $having_item instanceof WP_Parser_Node ) {
2925+
$disambiguated_item = $this->disambiguate_item( $disambiguation_map, $having_item );
2926+
$disambiguated_having_list[] = $disambiguated_item ?? $this->translate( $having_item );
2927+
} else {
2928+
$disambiguated_having_list[] = $this->translate( $having_item );
2929+
}
2930+
}
2931+
$having_clause = 'HAVING ' . implode( ' ', $disambiguated_having_list );
2932+
}
2933+
2934+
$parts = array();
2935+
foreach ( $node->get_children() as $child ) {
2936+
if ( $child instanceof WP_Parser_Node && 'groupByClause' === $child->rule_name ) {
2937+
$parts[] = $group_by_clause;
2938+
} elseif ( $child instanceof WP_Parser_Node && 'havingClause' === $child->rule_name ) {
2939+
// Translate "HAVING ..." without "GROUP BY ..." to "GROUP BY 1 HAVING ...".
2940+
if ( ! $group_by ) {
2941+
$parts[] = 'GROUP BY 1';
2942+
}
2943+
$parts[] = $having_clause;
2944+
} else {
2945+
$part = $this->translate( $child );
2946+
if ( null !== $part ) {
2947+
$parts[] = $part;
2948+
}
2949+
}
2950+
}
2951+
return implode( ' ', $parts );
2952+
}
2953+
return $this->translate_sequence( $node->get_children() );
2954+
}
2955+
29102956
/**
29112957
* Translate a MySQL query expression to SQLite.
29122958
*

0 commit comments

Comments
 (0)