Skip to content

Commit a5cd096

Browse files
authored
[codex] Fix MySQL SET expression parser gaps (#54)
* fix MySQL SET expression parser gaps * fix clang build for local transaction savepoints
1 parent 3cfc581 commit a5cd096

10 files changed

Lines changed: 417 additions & 14 deletions

File tree

include/sql_engine/local_txn.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <unordered_map>
1313
#include <cstring>
1414
#include <memory>
15+
#include <algorithm>
1516

1617
namespace sql_engine {
1718

include/sql_parser/emitter.h

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class Emitter {
122122
case NodeType::NODE_ARRAY_CONSTRUCTOR: emit_array_constructor(node); break;
123123
case NodeType::NODE_ARRAY_SUBSCRIPT: emit_array_subscript(node); break;
124124
case NodeType::NODE_FIELD_ACCESS: emit_field_access(node); break;
125-
case NodeType::NODE_SUBQUERY: emit_value(node); break;
125+
case NodeType::NODE_SUBQUERY: emit_subquery(node); break;
126126

127127
// ---- Leaf nodes (emit value directly) ----
128128
case NodeType::NODE_PLACEHOLDER:
@@ -1061,10 +1061,28 @@ class Emitter {
10611061
}
10621062

10631063
void emit_unary_op(const AstNode* node) {
1064+
const AstNode* child = node->first_child;
1065+
if (is_not_operator(node) && child) {
1066+
if (child->type == NodeType::NODE_IN_LIST) {
1067+
emit_not_in_list(child);
1068+
return;
1069+
}
1070+
if (child->type == NodeType::NODE_BETWEEN) {
1071+
emit_not_between(child);
1072+
return;
1073+
}
1074+
if (child->type == NodeType::NODE_BINARY_OP &&
1075+
(child->value().equals_ci("LIKE", 4) ||
1076+
child->value().equals_ci("REGEXP", 6))) {
1077+
emit_not_binary_op(child);
1078+
return;
1079+
}
1080+
}
1081+
10641082
emit_value(node);
10651083
// Add space for keyword operators like NOT, no space for - or +
10661084
if (node->value_len > 1) sb_.append_char(' ');
1067-
if (node->first_child) emit_node(node->first_child);
1085+
if (child) emit_node(child);
10681086
}
10691087

10701088
void emit_function_call(const AstNode* node) {
@@ -1102,13 +1120,61 @@ class Emitter {
11021120

11031121
void emit_in_list(const AstNode* node) {
11041122
const AstNode* expr = node->first_child;
1123+
const AstNode* first_val = expr ? expr->next_sibling : nullptr;
11051124
if (expr) emit_node(expr);
1125+
if (first_val && first_val->type == NodeType::NODE_SUBQUERY &&
1126+
!first_val->next_sibling) {
1127+
sb_.append(" IN ");
1128+
emit_node(first_val);
1129+
return;
1130+
}
11061131
sb_.append(" IN (");
11071132
if (mode_ == EmitMode::DIGEST) {
11081133
sb_.append_char('?');
11091134
} else {
11101135
bool first = true;
1111-
for (const AstNode* val = expr ? expr->next_sibling : nullptr; val; val = val->next_sibling) {
1136+
for (const AstNode* val = first_val; val; val = val->next_sibling) {
1137+
if (!first) sb_.append(", ");
1138+
first = false;
1139+
emit_node(val);
1140+
}
1141+
}
1142+
sb_.append_char(')');
1143+
}
1144+
1145+
void emit_subquery(const AstNode* node) {
1146+
if (node->flags == 1) {
1147+
sb_.append("EXISTS ");
1148+
}
1149+
sb_.append_char('(');
1150+
if (node->first_child) {
1151+
emit_node(node->first_child);
1152+
} else {
1153+
emit_value(node);
1154+
}
1155+
sb_.append_char(')');
1156+
}
1157+
1158+
bool is_not_operator(const AstNode* node) const {
1159+
return node && node->value().equals_ci("NOT", 3);
1160+
}
1161+
1162+
void emit_not_in_list(const AstNode* node) {
1163+
const AstNode* expr = node->first_child;
1164+
const AstNode* first_val = expr ? expr->next_sibling : nullptr;
1165+
if (expr) emit_node(expr);
1166+
if (first_val && first_val->type == NodeType::NODE_SUBQUERY &&
1167+
!first_val->next_sibling) {
1168+
sb_.append(" NOT IN ");
1169+
emit_node(first_val);
1170+
return;
1171+
}
1172+
sb_.append(" NOT IN (");
1173+
if (mode_ == EmitMode::DIGEST) {
1174+
sb_.append_char('?');
1175+
} else {
1176+
bool first = true;
1177+
for (const AstNode* val = first_val; val; val = val->next_sibling) {
11121178
if (!first) sb_.append(", ");
11131179
first = false;
11141180
emit_node(val);
@@ -1117,6 +1183,27 @@ class Emitter {
11171183
sb_.append_char(')');
11181184
}
11191185

1186+
void emit_not_between(const AstNode* node) {
1187+
const AstNode* expr = node->first_child;
1188+
const AstNode* low = expr ? expr->next_sibling : nullptr;
1189+
const AstNode* high = low ? low->next_sibling : nullptr;
1190+
if (expr) emit_node(expr);
1191+
sb_.append(" NOT BETWEEN ");
1192+
if (low) emit_node(low);
1193+
sb_.append(" AND ");
1194+
if (high) emit_node(high);
1195+
}
1196+
1197+
void emit_not_binary_op(const AstNode* node) {
1198+
const AstNode* left = node->first_child;
1199+
const AstNode* right = left ? left->next_sibling : nullptr;
1200+
if (left) emit_node(left);
1201+
sb_.append(" NOT ");
1202+
emit_value(node);
1203+
sb_.append_char(' ');
1204+
if (right) emit_node(right);
1205+
}
1206+
11201207
void emit_case_when(const AstNode* node) {
11211208
sb_.append("CASE ");
11221209
for (const AstNode* child = node->first_child; child; child = child->next_sibling) {

include/sql_parser/expression_parser.h

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ namespace sql_parser {
1313
enum class Precedence : uint8_t {
1414
NONE = 0,
1515
OR, // OR
16+
XOR, // XOR
1617
AND, // AND
1718
NOT, // NOT (prefix)
1819
COMPARISON, // =, <, >, <=, >=, !=, <>, IS, LIKE, IN, BETWEEN
20+
BIT_OR, // |
21+
BIT_XOR, // ^
22+
BIT_AND, // &
23+
SHIFT, // <<, >>
1924
ADDITION, // +, -
2025
MULTIPLICATION,// *, /, %
2126
UNARY, // - (prefix), NOT
@@ -77,7 +82,12 @@ class ExpressionParser {
7782
if (tok_.peek().type == TokenType::TK_RPAREN) tok_.skip();
7883
} else {
7984
// Legacy: skip to matching paren
80-
skip_to_matching_paren();
85+
const char* start = tok_.peek().text.ptr;
86+
const char* end = skip_to_matching_paren();
87+
if (start && end && end >= start) {
88+
node->set_value(StringRef{start,
89+
static_cast<uint32_t>(end - start)});
90+
}
8191
}
8292
return node;
8393
}
@@ -112,6 +122,12 @@ class ExpressionParser {
112122
tok_.skip();
113123
return make_node(arena_, NodeType::NODE_IDENTIFIER, t.text);
114124
}
125+
case TokenType::TK_INTERVAL: {
126+
return parse_interval_literal(t);
127+
}
128+
case TokenType::TK_ALL: {
129+
return parse_quantified_subquery(t);
130+
}
115131
case TokenType::TK_ASTERISK: {
116132
tok_.skip();
117133
return make_node(arena_, NodeType::NODE_ASTERISK, t.text);
@@ -336,6 +352,7 @@ class ExpressionParser {
336352
static Precedence infix_precedence(TokenType type) {
337353
switch (type) {
338354
case TokenType::TK_OR: return Precedence::OR;
355+
case TokenType::TK_XOR: return Precedence::XOR;
339356
case TokenType::TK_AND: return Precedence::AND;
340357
case TokenType::TK_NOT: return Precedence::COMPARISON; // NOT IN/BETWEEN/LIKE
341358
case TokenType::TK_EQUAL:
@@ -344,15 +361,25 @@ class ExpressionParser {
344361
case TokenType::TK_GREATER:
345362
case TokenType::TK_LESS_EQUAL:
346363
case TokenType::TK_GREATER_EQUAL:
364+
case TokenType::TK_REGEXP:
365+
case TokenType::TK_SOUNDS:
366+
case TokenType::TK_MEMBER:
347367
case TokenType::TK_LIKE: return Precedence::COMPARISON;
348368
case TokenType::TK_IS: return Precedence::COMPARISON;
349369
case TokenType::TK_IN: return Precedence::COMPARISON;
350370
case TokenType::TK_BETWEEN: return Precedence::COMPARISON;
371+
case TokenType::TK_PIPE: return Precedence::BIT_OR;
372+
case TokenType::TK_CARET: return Precedence::BIT_XOR;
373+
case TokenType::TK_AMPERSAND: return Precedence::BIT_AND;
374+
case TokenType::TK_SHIFT_LEFT:
375+
case TokenType::TK_SHIFT_RIGHT: return Precedence::SHIFT;
351376
case TokenType::TK_PLUS:
352377
case TokenType::TK_MINUS: return Precedence::ADDITION;
353378
case TokenType::TK_ASTERISK:
354379
case TokenType::TK_SLASH:
355-
case TokenType::TK_PERCENT: return Precedence::MULTIPLICATION;
380+
case TokenType::TK_PERCENT:
381+
case TokenType::TK_DIV:
382+
case TokenType::TK_MOD: return Precedence::MULTIPLICATION;
356383
case TokenType::TK_DOUBLE_PIPE: return Precedence::ADDITION; // string concat
357384
default: return Precedence::NONE;
358385
}
@@ -363,7 +390,7 @@ class ExpressionParser {
363390

364391
switch (op.type) {
365392
case TokenType::TK_NOT: {
366-
// NOT IN / NOT BETWEEN / NOT LIKE — compound negated infix
393+
// NOT IN / NOT BETWEEN / NOT LIKE / NOT REGEXP — compound negated infix
367394
Token actual_op = tok_.peek();
368395
if (actual_op.type == TokenType::TK_IN) {
369396
tok_.skip();
@@ -380,7 +407,8 @@ class ExpressionParser {
380407
not_node->add_child(between_node);
381408
return not_node;
382409
}
383-
if (actual_op.type == TokenType::TK_LIKE) {
410+
if (actual_op.type == TokenType::TK_LIKE ||
411+
actual_op.type == TokenType::TK_REGEXP) {
384412
tok_.skip();
385413
AstNode* right = parse(prec);
386414
AstNode* like_node = make_node(arena_, NodeType::NODE_BINARY_OP, actual_op.text);
@@ -422,6 +450,10 @@ class ExpressionParser {
422450
return parse_in(left);
423451
case TokenType::TK_BETWEEN:
424452
return parse_between(left);
453+
case TokenType::TK_SOUNDS:
454+
return parse_sounds_like(left, op, prec);
455+
case TokenType::TK_MEMBER:
456+
return parse_member_of(left, op, prec);
425457
default: {
426458
// Standard binary operator
427459
AstNode* right = parse(prec);
@@ -459,6 +491,54 @@ class ExpressionParser {
459491
return node;
460492
}
461493

494+
AstNode* parse_sounds_like(AstNode* left, const Token& sounds, Precedence prec) {
495+
Token like = tok_.peek();
496+
StringRef op_text = sounds.text;
497+
if (like.type == TokenType::TK_LIKE) {
498+
tok_.skip();
499+
op_text = StringRef{sounds.text.ptr,
500+
static_cast<uint32_t>((like.text.ptr + like.text.len) - sounds.text.ptr)};
501+
}
502+
AstNode* right = parse(prec);
503+
AstNode* node = make_node(arena_, NodeType::NODE_BINARY_OP, op_text);
504+
node->add_child(left);
505+
if (right) node->add_child(right);
506+
return node;
507+
}
508+
509+
AstNode* parse_member_of(AstNode* left, const Token& member, Precedence prec) {
510+
Token of = tok_.peek();
511+
StringRef op_text = member.text;
512+
if (of.type == TokenType::TK_OF) {
513+
tok_.skip();
514+
op_text = StringRef{member.text.ptr,
515+
static_cast<uint32_t>((of.text.ptr + of.text.len) - member.text.ptr)};
516+
}
517+
518+
AstNode* right = nullptr;
519+
if (tok_.peek().type == TokenType::TK_LPAREN) {
520+
tok_.skip();
521+
AstNode* tuple = make_node(arena_, NodeType::NODE_TUPLE);
522+
if (tok_.peek().type != TokenType::TK_RPAREN) {
523+
while (true) {
524+
AstNode* elem = parse();
525+
if (elem) tuple->add_child(elem);
526+
if (tok_.peek().type == TokenType::TK_COMMA) tok_.skip();
527+
else break;
528+
}
529+
}
530+
if (tok_.peek().type == TokenType::TK_RPAREN) tok_.skip();
531+
right = tuple;
532+
} else {
533+
right = parse(prec);
534+
}
535+
536+
AstNode* node = make_node(arena_, NodeType::NODE_BINARY_OP, op_text);
537+
node->add_child(left);
538+
if (right) node->add_child(right);
539+
return node;
540+
}
541+
462542
// BETWEEN low AND high
463543
AstNode* parse_between(AstNode* left) {
464544
AstNode* node = make_node(arena_, NodeType::NODE_BETWEEN);
@@ -473,6 +553,38 @@ class ExpressionParser {
473553
return node;
474554
}
475555

556+
AstNode* parse_interval_literal(const Token& interval) {
557+
tok_.skip();
558+
Token amount = tok_.next_token();
559+
if (amount.type == TokenType::TK_EOF || amount.type == TokenType::TK_ERROR) {
560+
return make_node(arena_, NodeType::NODE_IDENTIFIER, interval.text);
561+
}
562+
Token unit = tok_.next_token();
563+
if (unit.type == TokenType::TK_EOF || unit.type == TokenType::TK_ERROR) {
564+
StringRef span{interval.text.ptr,
565+
static_cast<uint32_t>((amount.text.ptr + amount.text.len) - interval.text.ptr)};
566+
return make_node(arena_, NodeType::NODE_IDENTIFIER, span);
567+
}
568+
StringRef span{interval.text.ptr,
569+
static_cast<uint32_t>((unit.text.ptr + unit.text.len) - interval.text.ptr)};
570+
return make_node(arena_, NodeType::NODE_IDENTIFIER, span);
571+
}
572+
573+
AstNode* parse_quantified_subquery(const Token& quantifier) {
574+
tok_.skip();
575+
if (tok_.peek().type == TokenType::TK_LPAREN) {
576+
tok_.skip();
577+
if (tok_.peek().type == TokenType::TK_SELECT) {
578+
const char* close = skip_to_matching_paren();
579+
const char* end = close ? close + 1 : tok_.input_end();
580+
return make_node(arena_, NodeType::NODE_IDENTIFIER,
581+
StringRef{quantifier.text.ptr,
582+
static_cast<uint32_t>(end - quantifier.text.ptr)});
583+
}
584+
}
585+
return make_node(arena_, NodeType::NODE_IDENTIFIER, quantifier.text);
586+
}
587+
476588
// CASE [expr] WHEN ... THEN ... [ELSE ...] END
477589
AstNode* parse_case() {
478590
AstNode* node = make_node(arena_, NodeType::NODE_CASE_WHEN);
@@ -607,14 +719,18 @@ class ExpressionParser {
607719
}
608720

609721
// Skip tokens until matching closing paren (handles nesting)
610-
void skip_to_matching_paren() {
722+
const char* skip_to_matching_paren() {
611723
int depth = 1;
612724
while (depth > 0) {
613725
Token t = tok_.next_token();
614726
if (t.type == TokenType::TK_LPAREN) ++depth;
615-
else if (t.type == TokenType::TK_RPAREN) --depth;
727+
else if (t.type == TokenType::TK_RPAREN) {
728+
--depth;
729+
if (depth == 0) return t.text.ptr;
730+
}
616731
else if (t.type == TokenType::TK_EOF) break;
617732
}
733+
return tok_.input_end();
618734
}
619735

620736
// Some keywords can appear as identifiers in expression context
@@ -698,6 +814,8 @@ class ExpressionParser {
698814
case TokenType::TK_PARTITION:
699815
case TokenType::TK_RECURSIVE:
700816
case TokenType::TK_REPLACE:
817+
case TokenType::TK_DIV:
818+
case TokenType::TK_MOD:
701819
return true;
702820
default:
703821
return false;

0 commit comments

Comments
 (0)