@@ -2512,10 +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 );
25172515 case 'queryExpression ' :
25182516 return $ this ->translate_query_expression ( $ node );
2517+ case 'querySpecification ' :
2518+ return $ this ->translate_query_specification ( $ node );
25192519 case 'qualifiedIdentifier ' :
25202520 case 'tableRefWithWildcard ' :
25212521 $ parts = $ node ->get_descendant_nodes ( 'identifier ' );
@@ -2893,66 +2893,6 @@ private function translate_qualified_identifier(
28932893 return implode ( '. ' , $ parts );
28942894 }
28952895
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-
29562896 /**
29572897 * Translate a MySQL query expression to SQLite.
29582898 *
@@ -2975,6 +2915,8 @@ private function translate_query_expression( WP_Parser_Node $node ): string {
29752915 /*
29762916 * When the ORDER BY clause is present, we need to disambiguate the item
29772917 * list and make sure they don't cause an "ambiguous column name" error.
2918+ *
2919+ * @see WP_SQLite_Driver::disambiguate_item()
29782920 */
29792921 $ disambiguated_order_list = array ();
29802922 $ order_clause = $ node ->get_first_child_node ( 'orderClause ' );
@@ -3026,6 +2968,92 @@ private function translate_query_expression( WP_Parser_Node $node ): string {
30262968 return $ this ->translate_sequence ( $ node ->get_children () );
30272969 }
30282970
2971+ /**
2972+ * Translate a MySQL query specification node to SQLite.
2973+ *
2974+ * @param WP_Parser_Node $node The "querySpecification" AST node.
2975+ * @return string The translated value.
2976+ * @throws WP_SQLite_Driver_Exception When the translation fails.
2977+ * @return string|null
2978+ */
2979+ private function translate_query_specification ( WP_Parser_Node $ node ): string {
2980+ $ group_by = $ node ->get_first_child_node ( 'groupByClause ' );
2981+ $ having = $ node ->get_first_child_node ( 'havingClause ' );
2982+
2983+ /*
2984+ * When the GROUP BY or HAVING clause is present, we need to disambiguate
2985+ * the items to ensure they don't cause an "ambiguous column name" error.
2986+ *
2987+ * @see WP_SQLite_Driver::disambiguate_item()
2988+ */
2989+ $ group_by_clause = null ;
2990+ $ having_clause = null ;
2991+ if ( $ group_by || $ having ) {
2992+ // Build a SELECT list disambiguation map for both GROUP BY and HAVING.
2993+ $ select_item_list = $ node ->get_first_child_node ( 'selectItemList ' );
2994+ $ disambiguation_map = $ this ->create_select_item_disambiguation_map ( $ select_item_list );
2995+
2996+ // Disambiguate the GROUP BY clause column references.
2997+ $ disambiguated_group_by_list = array ();
2998+ if ( $ group_by ) {
2999+ /*
3000+ * [GRAMMAR]
3001+ * groupByClause: GROUP_SYMBOL BY_SYMBOL orderList olapOption?
3002+ */
3003+ $ group_by_list = $ group_by ->get_first_child_node ( 'orderList ' );
3004+ foreach ( $ group_by_list ->get_child_nodes () as $ group_by_item ) {
3005+ $ group_by_expr = $ group_by_item ->get_first_child_node ( 'expr ' );
3006+ $ disambiguated_item = $ this ->disambiguate_item ( $ disambiguation_map , $ group_by_expr );
3007+ $ disambiguated_group_by_list [] = $ disambiguated_item ?? $ this ->translate ( $ group_by_expr );
3008+ }
3009+ $ group_by_clause = 'GROUP BY ' . implode ( ', ' , $ disambiguated_group_by_list );
3010+ }
3011+
3012+ // Disambiguate the HAVING clause column references.
3013+ $ disambiguated_having_list = array ();
3014+ if ( $ having ) {
3015+ /*
3016+ * [GRAMMAR]
3017+ * havingClause: HAVING_SYMBOL expr
3018+ */
3019+ $ having_expr = $ having ->get_first_child_node ();
3020+ $ having_expr_children = $ having_expr ->get_children ();
3021+ foreach ( $ having_expr_children as $ having_item ) {
3022+ if ( $ having_item instanceof WP_Parser_Node ) {
3023+ $ disambiguated_item = $ this ->disambiguate_item ( $ disambiguation_map , $ having_item );
3024+ $ disambiguated_having_list [] = $ disambiguated_item ?? $ this ->translate ( $ having_item );
3025+ } else {
3026+ $ disambiguated_having_list [] = $ this ->translate ( $ having_item );
3027+ }
3028+ }
3029+ $ having_clause = 'HAVING ' . implode ( ' ' , $ disambiguated_having_list );
3030+ }
3031+
3032+ // Translate the query specification, replacing the ORDER BY/HAVING
3033+ // items with the ones that were disambiguated using the SELECT list.
3034+ $ parts = array ();
3035+ foreach ( $ node ->get_children () as $ child ) {
3036+ if ( $ child instanceof WP_Parser_Node && 'groupByClause ' === $ child ->rule_name ) {
3037+ $ parts [] = $ group_by_clause ;
3038+ } elseif ( $ child instanceof WP_Parser_Node && 'havingClause ' === $ child ->rule_name ) {
3039+ // SQLite doesn't allow using the "HAVING" clause without "GROUP BY".
3040+ // In such cases, let's prefix the "HAVING" clause with "GROUP BY 1".
3041+ if ( ! $ group_by ) {
3042+ $ parts [] = 'GROUP BY 1 ' ;
3043+ }
3044+ $ parts [] = $ having_clause ;
3045+ } else {
3046+ $ part = $ this ->translate ( $ child );
3047+ if ( null !== $ part ) {
3048+ $ parts [] = $ part ;
3049+ }
3050+ }
3051+ }
3052+ return implode ( ' ' , $ parts );
3053+ }
3054+ return $ this ->translate_sequence ( $ node ->get_children () );
3055+ }
3056+
30293057 /**
30303058 * Translate a MySQL simple expression to SQLite.
30313059 *
0 commit comments