Skip to content

Commit 7c47ad5

Browse files
committed
Add emitter support for EXPLAIN, CALL, DO, LOAD DATA statements
Handles round-trip emission for all new node types including EXPLAIN options/format, CALL with qualified names and args, DO expression lists, and LOAD DATA with field/line options.
1 parent cc9a67a commit 7c47ad5

1 file changed

Lines changed: 189 additions & 0 deletions

File tree

include/sql_parser/emitter.h

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)