@@ -2365,6 +2365,8 @@ private function translate( $node ): ?string {
23652365 throw $ this ->new_not_supported_exception (
23662366 sprintf ( 'data type: %s ' , $ child ->get_value () )
23672367 );
2368+ case 'selectItem ' :
2369+ return $ this ->translate_select_item ( $ node );
23682370 case 'fromClause ' :
23692371 // FROM DUAL is MySQL-specific syntax that means "FROM no tables"
23702372 // and it is equivalent to omitting the FROM clause entirely.
@@ -2411,19 +2413,7 @@ private function translate( $node ): ?string {
24112413 // @TODO: Emulate more system variables, or use reasonable defaults.
24122414 // See: https://dev.mysql.com/doc/refman/8.4/en/server-system-variable-reference.html
24132415 // See: https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html
2414-
2415- // TODO: Original name should come from the original MySQL input,
2416- // exactly as it was written by the user, and not translated.
2417-
2418- // TODO: The '% AS %' syntax is compatible with SELECT lists only.
2419- // We need to translate it differently when used as a value.
2420- return sprintf (
2421- '%s AS %s ' ,
2422- $ value ,
2423- $ this ->quote_sqlite_identifier (
2424- '@@ ' . ( $ type_token ? "{$ type_token ->get_value ()}. " : '' ) . $ original_name
2425- )
2426- );
2416+ return $ value ;
24272417 case 'castType ' :
24282418 // Translate "CAST(... AS BINARY)" to "CAST(... AS BLOB)".
24292419 if ( $ node ->has_child_token ( WP_MySQL_Lexer::BINARY_SYMBOL ) ) {
@@ -2880,15 +2870,11 @@ private function translate_function_call( WP_Parser_Node $node ): string {
28802870 case 'CONCAT ' :
28812871 return '( ' . implode ( ' || ' , $ args ) . ') ' ;
28822872 case 'FOUND_ROWS ' :
2883- // @TODO: The following implementation with an alias assumes
2884- // that the function is used in the SELECT field list.
2885- // For compatibility with more complex use cases, it may
2886- // be better to register it as a custom SQLite function.
28872873 $ found_rows = $ this ->last_sql_calc_found_rows ;
28882874 if ( null === $ found_rows && is_array ( $ this ->last_result ) ) {
28892875 $ found_rows = count ( $ this ->last_result );
28902876 }
2891- return sprintf ( " (SELECT %d) AS 'FOUND_ROWS()' " , $ found_rows ) ;
2877+ return $ found_rows ;
28922878 default :
28932879 return $ this ->translate_sequence ( $ node ->get_children () );
28942880 }
@@ -2959,6 +2945,72 @@ private function translate_datetime_literal( string $value ): string {
29592945 return $ value ;
29602946 }
29612947
2948+ /**
2949+ * Translate a select item to SQLite.
2950+ *
2951+ * In some cases, an explicit alias will be added to the select item, so that
2952+ * the returned column name is always the same as it would be in MySQL.
2953+ *
2954+ * @param WP_Parser_Node $node The "selectItem" AST node.
2955+ * @return string The translated value.
2956+ */
2957+ public function translate_select_item ( WP_Parser_Node $ node ): string {
2958+ /*
2959+ * First, let's translate the select item subtree.
2960+ *
2961+ * [GRAMMAR]
2962+ * selectItem: tableWild | (expr selectAlias?)
2963+ */
2964+ $ item = $ this ->translate_sequence ( $ node ->get_children () );
2965+
2966+ // A table wildcard (e.g., "SELECT *, t.*, ...") never has an alias.
2967+ if ( $ node ->has_child_node ( 'tableWild ' ) ) {
2968+ return $ item ;
2969+ }
2970+
2971+ // When an explicit alias is provided, we can use it as is.
2972+ $ alias = $ node ->get_first_child_node ( 'selectAlias ' );
2973+ if ( $ alias ) {
2974+ return $ item ;
2975+ }
2976+
2977+ /*
2978+ * When the select item contains only a column definition, we need to use
2979+ * it without change, so that the returned column name reflects the real
2980+ * column name in all cases, including when using a fully qualified name.
2981+ *
2982+ * For example, for "SELECT t.id", the column name in the result set will
2983+ * only be "id", not "t.id", as it may appear based on the original query.
2984+ *
2985+ * In this case, SQLite uses the same logic as MySQL, so using the value
2986+ * as is without adding an explicit alias will produce the correct result.
2987+ */
2988+ $ column_ref = $ node ->get_first_descendant_node ( 'columnRef ' );
2989+ $ is_column_ref = $ column_ref && $ item === $ this ->translate ( $ column_ref );
2990+ if ( $ is_column_ref ) {
2991+ return $ item ;
2992+ }
2993+
2994+ /*
2995+ * When the select item has no explicit alias, we need to ensure that the
2996+ * returned column name is equivalent to what MySQL infers from the input.
2997+ *
2998+ * For example, if we translate "CONCAT('a', 'b')" to "('a' || 'b')", we
2999+ * need to use the original "CONCAT('a', 'b')" string as the column name.
3000+ * To achieve this, the select item will be translated as follows:
3001+ *
3002+ * SELECT CONCAT('a', 'b') -> SELECT ('a' || 'b') AS `CONCAT('a', 'b')`
3003+ */
3004+ $ raw_alias = substr ( $ this ->last_mysql_query , $ node ->get_start (), $ node ->get_length () );
3005+ $ alias = $ this ->quote_sqlite_identifier ( $ raw_alias );
3006+ if ( $ alias === $ item || $ raw_alias === $ item ) {
3007+ // For the simple case of selecting only columns ("SELECT id FROM t"),
3008+ // let's avoid unnecessary aliases ("SELECT `id` AS `id` FROM t").
3009+ return $ item ;
3010+ }
3011+ return sprintf ( '%s AS %s ' , $ item , $ alias );
3012+ }
3013+
29623014 /**
29633015 * Recreate an existing table using data in the information schema.
29643016 *
0 commit comments