@@ -2946,56 +2946,90 @@ private function translate_query_expression( WP_Parser_Node $node ): string {
29462946 $ order_list = $ order_clause ->get_first_child_node ( 'orderList ' );
29472947 $ select_item_list = $ node ->get_first_descendant_node ( 'selectItemList ' );
29482948
2949- // Get a list of column references used in the SELECT item list .
2950- $ select_column_refs = array ();
2949+ // Create a map of SELECT item column names to their qualified values .
2950+ $ disambiguation_map = array ();
29512951 foreach ( $ select_item_list ->get_child_nodes () as $ select_item ) {
2952- // When the SELECT item uses an alias, the column is not disambiguated.
2953- if ( $ select_item -> has_child_node ( ' selectAlias ' ) ) {
2954- continue ;
2955- }
2952+ /*
2953+ * [GRAMMAR]
2954+ * selectItem: tableWild | (expr selectAlias?)
2955+ */
29562956
2957+ // Skip when a "tableWild" node is used (no "expr" node).
29572958 $ select_item_expr = $ select_item ->get_first_child_node ( 'expr ' );
29582959 if ( ! $ select_item_expr ) {
29592960 continue ;
29602961 }
29612962
2962- $ select_item_expr = $ this ->unnest_parenthesized_expression ( $ select_item_expr );
2963- $ select_column_ref = $ select_item ->get_first_descendant_node ( 'columnRef ' );
2964- if (
2965- $ select_column_ref
2966- && $ this ->translate ( $ select_item_expr ) === $ this ->translate ( $ select_column_ref )
2967- ) {
2968- $ select_column_refs [] = $ select_column_ref ;
2963+ // A SELECT item alias always needs to be preserved as-is.
2964+ $ alias = $ select_item ->get_first_child_node ( 'selectAlias ' );
2965+ if ( $ alias ) {
2966+ $ alias_value = $ this ->translate ( $ alias ->get_first_child_node () );
2967+ $ disambiguation_map [ $ alias_value ] = array ( $ alias_value );
2968+ continue ;
2969+ }
2970+
2971+ // Skip when there is no column listed (no "columnRef" node).
2972+ $ select_column_ref = $ select_item_expr ->get_first_descendant_node ( 'columnRef ' );
2973+ if ( ! $ select_column_ref ) {
2974+ continue ;
2975+ }
2976+
2977+ // Skip when the column reference is not qualified (no "dotIdentifier" node).
2978+ $ dot_identifiers = $ select_column_ref ->get_descendant_nodes ( 'dotIdentifier ' );
2979+ if ( 0 === count ( $ dot_identifiers ) ) {
2980+ continue ;
2981+ }
2982+
2983+ // Support also parenthesized column references (e.g. "(t.id)").
2984+ $ select_item_expr = $ this ->unnest_parenthesized_expression ( $ select_item_expr );
2985+
2986+ // Consider only simple and parenthesized column references.
2987+ $ expr_value = $ this ->translate ( $ select_item_expr );
2988+ $ column_value = $ this ->translate ( $ select_column_ref );
2989+ if ( $ expr_value !== $ column_value ) {
2990+ continue ;
29692991 }
2992+
2993+ // The column name is the last "dotIdentifier" node.
2994+ $ key = $ this ->translate ( end ( $ dot_identifiers )->get_first_child_node () );
2995+
2996+ $ disambiguation_map [ $ key ] = $ disambiguation_map [ $ key ] ?? array ();
2997+ $ disambiguation_map [ $ key ][] = $ column_value ;
29702998 }
29712999
29723000 // For each ORDER BY item, try to find a corresponding SELECT item.
29733001 foreach ( $ order_list ->get_child_nodes () as $ order_item ) {
3002+ /*
3003+ * [GRAMMAR]
3004+ * orderExpression: expr direction?
3005+ */
29743006 $ order_expr = $ order_item ->get_first_child_node ( 'expr ' );
29753007 $ order_column_ref = $ order_expr ->get_first_descendant_node ( 'columnRef ' );
29763008
2977- $ select_item_matches = array ();
3009+ // Skip when there is no column in the ORDER BY item (no "columnRef" node),
3010+ // or when the item is already qualified (has a "dotIdentifier" node).
29783011 if (
2979- $ order_column_ref
2980- && $ this ->translate ( $ order_column_ref ) === $ this ->translate ( $ order_expr )
2981- && null === $ order_column_ref ->get_first_descendant_node ( 'dotIdentifier ' )
3012+ ! $ order_column_ref
3013+ || null !== $ order_column_ref ->get_first_descendant_node ( 'dotIdentifier ' )
29823014 ) {
2983- // Look for select items that match the column reference.
2984- foreach ( $ select_column_refs as $ select_column_ref ) {
2985- $ dot_identifiers = $ select_column_ref ->get_descendant_nodes ( 'dotIdentifier ' );
2986- if ( count ( $ dot_identifiers ) === 0 ) {
2987- continue ;
2988- }
3015+ $ disambiguated_order_list [] = $ this ->translate ( $ order_item );
3016+ continue ;
3017+ }
29893018
2990- $ last_dot_identifier = end ( $ dot_identifiers );
2991- $ select_column_name = $ this ->translate ( $ last_dot_identifier ->get_first_child_node () );
2992- $ order_column_name = $ this ->translate ( $ order_column_ref );
2993- if ( $ select_column_name === $ order_column_name ) {
2994- $ select_item_matches [] = $ this ->translate ( $ select_column_ref );
2995- }
2996- }
3019+ // Consider only simple and parenthesized order column references.
3020+ $ order_expr_value = $ this ->translate ( $ order_expr );
3021+ $ order_column_value = $ this ->translate ( $ order_column_ref );
3022+ if ( $ order_expr_value !== $ order_column_value ) {
3023+ $ disambiguated_order_list [] = $ this ->translate ( $ order_item );
3024+ continue ;
29973025 }
29983026
3027+ // Look for select items that match the column reference.
3028+ $ order_column_name = $ this ->translate ( $ order_column_ref );
3029+ $ select_item_matches = $ disambiguation_map [ $ order_column_name ] ?? array ();
3030+
3031+ // When we find exactly one SELECT item, we can disambiguate the
3032+ // reference. Otherwise, fall back to the original ORDER BY item.
29993033 if ( 1 === count ( $ select_item_matches ) ) {
30003034 $ direction = $ order_item ->get_first_child_node ( 'direction ' );
30013035 $ translated_order_item = sprintf (
0 commit comments