@@ -80,6 +80,21 @@ class Emitter {
8080 case NodeType::NODE_DELETE_STMT: emit_delete_stmt (node); break ;
8181 case NodeType::NODE_DELETE_USING_CLAUSE: emit_delete_using (node); break ;
8282
83+ // ---- EXPLAIN / DESCRIBE ----
84+ case NodeType::NODE_EXPLAIN_STMT: emit_explain_stmt (node); break ;
85+ case NodeType::NODE_EXPLAIN_OPTIONS: emit_explain_options (node); break ;
86+ case NodeType::NODE_EXPLAIN_FORMAT: emit_explain_format (node); break ;
87+
88+ // ---- CALL ----
89+ case NodeType::NODE_CALL_STMT: emit_call_stmt (node); break ;
90+
91+ // ---- DO ----
92+ case NodeType::NODE_DO_STMT: emit_do_stmt (node); break ;
93+
94+ // ---- LOAD DATA ----
95+ case NodeType::NODE_LOAD_DATA_STMT: emit_load_data_stmt (node); break ;
96+ case NodeType::NODE_LOAD_DATA_OPTIONS: /* emitted inline */ break ;
97+
8398 // ---- UPDATE statement ----
8499 case NodeType::NODE_UPDATE_STMT: emit_update_stmt (node); break ;
85100 case NodeType::NODE_UPDATE_SET_CLAUSE: emit_update_set_clause (node); break ;
@@ -820,6 +835,180 @@ class Emitter {
820835 }
821836 }
822837
838+ // ---- EXPLAIN / DESCRIBE ----
839+
840+ void emit_explain_stmt (const AstNode* node) {
841+ // Check if there's an inner statement or options (EXPLAIN) vs bare table ref (DESCRIBE)
842+ bool has_inner_stmt = false ;
843+ bool has_options = false ;
844+ for (const AstNode* c = node->first_child ; c; c = c->next_sibling ) {
845+ if (c->type == NodeType::NODE_SELECT_STMT ||
846+ c->type == NodeType::NODE_INSERT_STMT ||
847+ c->type == NodeType::NODE_UPDATE_STMT ||
848+ c->type == NodeType::NODE_DELETE_STMT ||
849+ c->type == NodeType::NODE_COMPOUND_QUERY) {
850+ has_inner_stmt = true ;
851+ }
852+ if (c->type == NodeType::NODE_EXPLAIN_OPTIONS) {
853+ has_options = true ;
854+ }
855+ }
856+
857+ if (!has_inner_stmt && !has_options) {
858+ sb_.append (" DESCRIBE" );
859+ } else {
860+ sb_.append (" EXPLAIN" );
861+ }
862+
863+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
864+ sb_.append_char (' ' );
865+ emit_node (child);
866+ }
867+ }
868+
869+ void emit_explain_options (const AstNode* node) {
870+ bool first = true ;
871+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
872+ if (!first) sb_.append_char (' ' );
873+ first = false ;
874+ emit_node (child);
875+ }
876+ }
877+
878+ void emit_explain_format (const AstNode* node) {
879+ sb_.append (" FORMAT = " );
880+ emit_value (node);
881+ }
882+
883+ // ---- CALL ----
884+
885+ void emit_call_stmt (const AstNode* node) {
886+ sb_.append (" CALL " );
887+ const AstNode* name = node->first_child ;
888+ if (!name) return ;
889+
890+ emit_node (name);
891+ sb_.append_char (' (' );
892+
893+ bool first = true ;
894+ for (const AstNode* arg = name->next_sibling ; arg; arg = arg->next_sibling ) {
895+ if (!first) sb_.append (" , " );
896+ first = false ;
897+ emit_node (arg);
898+ }
899+ sb_.append_char (' )' );
900+ }
901+
902+ // ---- DO ----
903+
904+ void emit_do_stmt (const AstNode* node) {
905+ sb_.append (" DO " );
906+ bool first = true ;
907+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
908+ if (!first) sb_.append (" , " );
909+ first = false ;
910+ emit_node (child);
911+ }
912+ }
913+
914+ // ---- LOAD DATA ----
915+
916+ void emit_load_data_stmt (const AstNode* node) {
917+ sb_.append (" LOAD DATA" );
918+
919+ // First pass: emit options that come before INFILE (LOW_PRIORITY/CONCURRENT, LOCAL)
920+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
921+ if (child->type == NodeType::NODE_LOAD_DATA_OPTIONS) {
922+ for (const AstNode* opt = child->first_child ; opt; opt = opt->next_sibling ) {
923+ if (opt->type == NodeType::NODE_IDENTIFIER) {
924+ StringRef val = opt->value ();
925+ if (val.equals_ci (" LOW_PRIORITY" , 12 )) {
926+ sb_.append (" LOW_PRIORITY" );
927+ } else if (val.equals_ci (" CONCURRENT" , 10 )) {
928+ sb_.append (" CONCURRENT" );
929+ } else if (val.equals_ci (" LOCAL" , 5 )) {
930+ sb_.append (" LOCAL" );
931+ }
932+ }
933+ }
934+ }
935+ }
936+
937+ // INFILE 'filename'
938+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
939+ if (child->type == NodeType::NODE_LITERAL_STRING) {
940+ sb_.append (" INFILE " );
941+ emit_string_literal (child);
942+ break ;
943+ }
944+ }
945+
946+ // REPLACE/IGNORE between filename and INTO TABLE
947+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
948+ if (child->type == NodeType::NODE_LOAD_DATA_OPTIONS) {
949+ for (const AstNode* opt = child->first_child ; opt; opt = opt->next_sibling ) {
950+ if (opt->type == NodeType::NODE_IDENTIFIER) {
951+ StringRef val = opt->value ();
952+ if (val.equals_ci (" REPLACE" , 7 )) {
953+ sb_.append (" REPLACE" );
954+ } else if (val.equals_ci (" IGNORE" , 6 )) {
955+ sb_.append (" IGNORE" );
956+ }
957+ }
958+ }
959+ }
960+ }
961+
962+ // INTO TABLE table_name
963+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
964+ if (child->type == NodeType::NODE_TABLE_REF) {
965+ sb_.append (" INTO TABLE " );
966+ emit_node (child);
967+ break ;
968+ }
969+ }
970+
971+ // FIELDS options
972+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
973+ if (child->type == NodeType::NODE_LOAD_DATA_OPTIONS) {
974+ bool fields_started = false ;
975+ for (const AstNode* opt = child->first_child ; opt; opt = opt->next_sibling ) {
976+ if (opt->type == NodeType::NODE_IDENTIFIER) {
977+ StringRef val = opt->value ();
978+ if (val.equals_ci (" TERMINATED" , 10 )) {
979+ if (!fields_started) { sb_.append (" FIELDS" ); fields_started = true ; }
980+ sb_.append (" TERMINATED BY " );
981+ if (opt->first_child ) emit_string_literal (opt->first_child );
982+ } else if (val.equals_ci (" ENCLOSED" , 8 )) {
983+ if (!fields_started) { sb_.append (" FIELDS" ); fields_started = true ; }
984+ sb_.append (" ENCLOSED BY " );
985+ if (opt->first_child ) emit_string_literal (opt->first_child );
986+ } else if (val.equals_ci (" ESCAPED" , 7 )) {
987+ if (!fields_started) { sb_.append (" FIELDS" ); fields_started = true ; }
988+ sb_.append (" ESCAPED BY " );
989+ if (opt->first_child ) emit_string_literal (opt->first_child );
990+ }
991+ }
992+ }
993+ }
994+ }
995+
996+ // Column list
997+ bool has_cols = false ;
998+ for (const AstNode* child = node->first_child ; child; child = child->next_sibling ) {
999+ if (child->type == NodeType::NODE_COLUMN_REF) {
1000+ if (!has_cols) {
1001+ sb_.append (" (" );
1002+ has_cols = true ;
1003+ } else {
1004+ sb_.append (" , " );
1005+ }
1006+ emit_node (child);
1007+ }
1008+ }
1009+ if (has_cols) sb_.append_char (' )' );
1010+ }
1011+
8231012 // ---- Compound query ----
8241013
8251014 void emit_compound_query (const AstNode* node) {
0 commit comments