Skip to content

Commit 9bfd0a0

Browse files
committed
Adding new example parser
Also fixed mysql_parser.y that was incorectly handling subqueries
1 parent ba7f191 commit 9bfd0a0

4 files changed

Lines changed: 239 additions & 10 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
*.out
3232
*.app
3333

34+
mysql_stdin_parser_example
3435
set_mysql_example
3536
mysql_example
3637
pgsql_example

Makefile

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Makefile
22
CXX = g++
3-
LINKER = g++
3+
LINKER = g++
44

55
CXXFLAGS = -std=c++17 -Wall -g -O2
66
CPPFLAGS = -I$(PROJECT_ROOT)/include
@@ -37,6 +37,7 @@ MYSQL_TARGET_LIB_NAME = mysqlparser
3737
MYSQL_TARGET_LIB = $(PROJECT_ROOT)/lib$(MYSQL_TARGET_LIB_NAME).a
3838
MYSQL_EXAMPLE_EXE = $(PROJECT_ROOT)/mysql_example
3939
MYSQL_SET_EXAMPLE_EXE = $(PROJECT_ROOT)/set_mysql_example
40+
MYSQL_STDIN_EXAMPLE_EXE = $(PROJECT_ROOT)/mysql_stdin_parser_example
4041

4142
MYSQL_BISON_C_FILE = mysql_parser.tab.c
4243
MYSQL_BISON_H_FILE = mysql_parser.tab.h
@@ -52,6 +53,7 @@ MYSQL_LIB_OBJS = \
5253
$(MYSQL_PARSER_SRC_DIR)/mysql_parser.o # Renamed from mysql_sql_parser.o
5354
MYSQL_EXAMPLE_OBJS = $(PROJECT_ROOT)/examples/main_mysql_example.o
5455
MYSQL_SET_EXAMPLE_OBJS = $(PROJECT_ROOT)/examples/set_mysql_example.o
56+
MYSQL_STDIN_EXAMPLE_OBJS = $(PROJECT_ROOT)/examples/mysql_stdin_parser_example.o
5557

5658

5759
.PHONY: all clean examples pgsql mysql
@@ -61,7 +63,7 @@ all: pgsql mysql examples
6163
pgsql: $(PGSQL_TARGET_LIB)
6264
mysql: $(MYSQL_TARGET_LIB)
6365

64-
examples: $(PGSQL_EXAMPLE_EXE) $(MYSQL_EXAMPLE_EXE) $(MYSQL_SET_EXAMPLE_EXE)
66+
examples: $(PGSQL_EXAMPLE_EXE) $(MYSQL_EXAMPLE_EXE) $(MYSQL_SET_EXAMPLE_EXE) $(MYSQL_STDIN_EXAMPLE_EXE)
6567

6668
# --- PostgreSQL Rules ---
6769
$(PGSQL_TARGET_LIB): $(PGSQL_LIB_OBJS)
@@ -99,11 +101,16 @@ $(MYSQL_EXAMPLE_EXE): $(MYSQL_EXAMPLE_OBJS) $(MYSQL_TARGET_LIB)
99101
$(LINKER) $(CXXFLAGS) -o $@ $(MYSQL_EXAMPLE_OBJS) -L$(PROJECT_ROOT) -l$(MYSQL_TARGET_LIB_NAME)
100102
@echo "Created MySQL example $@"
101103

102-
# Rule for MySQL SET example executable <<< NEW
104+
# Rule for MySQL SET example executable
103105
$(MYSQL_SET_EXAMPLE_EXE): $(MYSQL_SET_EXAMPLE_OBJS) $(MYSQL_TARGET_LIB)
104106
$(LINKER) $(CXXFLAGS) -o $@ $(MYSQL_SET_EXAMPLE_OBJS) -L$(PROJECT_ROOT) -l$(MYSQL_TARGET_LIB_NAME)
105107
@echo "Created MySQL SET statement example $@"
106108

109+
# Rule for MySQL STDIN parser example executable
110+
$(MYSQL_STDIN_EXAMPLE_EXE): $(MYSQL_STDIN_EXAMPLE_OBJS) $(MYSQL_TARGET_LIB)
111+
$(LINKER) $(CXXFLAGS) -o $@ $(MYSQL_STDIN_EXAMPLE_OBJS) -L$(PROJECT_ROOT) -l$(MYSQL_TARGET_LIB_NAME)
112+
@echo "Created MySQL STDIN parser example $@"
113+
107114
$(MYSQL_BISON_H) $(MYSQL_BISON_C): $(MYSQL_PARSER_SRC_DIR)/mysql_parser.y $(MYSQL_PARSER_INCLUDE_DIR)/mysql_ast.h $(MYSQL_PARSER_INCLUDE_DIR)/mysql_parser.h
108115
cd $(MYSQL_PARSER_SRC_DIR) && bison -d -v --report=all -o $(MYSQL_BISON_C_FILE) --defines=$(MYSQL_BISON_H_FILE) mysql_parser.y
109116

@@ -122,14 +129,18 @@ $(MYSQL_PARSER_SRC_DIR)/mysql_parser.o: $(MYSQL_PARSER_SRC_DIR)/mysql_parser.cpp
122129
$(PROJECT_ROOT)/examples/main_mysql_example.o: $(PROJECT_ROOT)/examples/main_mysql_example.cpp $(MYSQL_PARSER_INCLUDE_DIR)/mysql_parser.h $(MYSQL_PARSER_INCLUDE_DIR)/mysql_ast.h
123130
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
124131

125-
# Rule for MySQL SET example main.o <<< NEW
132+
# Rule for MySQL SET example main.o
126133
$(PROJECT_ROOT)/examples/set_mysql_example.o: $(PROJECT_ROOT)/examples/set_mysql_example.cpp $(MYSQL_PARSER_INCLUDE_DIR)/mysql_parser.h $(MYSQL_PARSER_INCLUDE_DIR)/mysql_ast.h
127134
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
128135

136+
# Rule for MySQL STDIN parser example main.o
137+
$(PROJECT_ROOT)/examples/mysql_stdin_parser_example.o: $(PROJECT_ROOT)/examples/mysql_stdin_parser_example.cpp $(MYSQL_PARSER_INCLUDE_DIR)/mysql_parser.h $(MYSQL_PARSER_INCLUDE_DIR)/mysql_ast.h
138+
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
139+
129140

130141
clean:
131-
rm -f $(PGSQL_TARGET_LIB) $(PGSQL_EXAMPLE_EXE) $(MYSQL_TARGET_LIB) $(MYSQL_EXAMPLE_EXE) $(MYSQL_SET_EXAMPLE_EXE)
132-
rm -f $(PGSQL_LIB_OBJS) $(PGSQL_EXAMPLE_OBJS) $(MYSQL_LIB_OBJS) $(MYSQL_EXAMPLE_OBJS) $(MYSQL_SET_EXAMPLE_OBJS)
142+
rm -f $(PGSQL_TARGET_LIB) $(PGSQL_EXAMPLE_EXE) $(MYSQL_TARGET_LIB) $(MYSQL_EXAMPLE_EXE) $(MYSQL_SET_EXAMPLE_EXE) $(MYSQL_STDIN_EXAMPLE_EXE)
143+
rm -f $(PGSQL_LIB_OBJS) $(PGSQL_EXAMPLE_OBJS) $(MYSQL_LIB_OBJS) $(MYSQL_EXAMPLE_OBJS) $(MYSQL_SET_EXAMPLE_OBJS) $(MYSQL_STDIN_EXAMPLE_OBJS)
133144
rm -f $(PGSQL_BISON_C) $(PGSQL_BISON_H) $(PGSQL_FLEX_C)
134145
rm -f $(MYSQL_BISON_C) $(MYSQL_BISON_H) $(MYSQL_FLEX_C)
135146
rm -f $(PGSQL_PARSER_SRC_DIR)/pgsql_parser.output $(PGSQL_PARSER_SRC_DIR)/pgsql_parser.report
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#include "mysql_parser/mysql_parser.h" // Ensure this path is correct
2+
#include "mysql_parser/mysql_ast.h" // Ensure this path is correct
3+
#include <iostream>
4+
#include <vector>
5+
#include <string>
6+
#include <memory> // Required for std::unique_ptr
7+
#include <chrono> // Required for timing
8+
#include <iomanip> // Required for std::fixed and std::setprecision
9+
#include <sstream> // Required for std::stringstream
10+
#include <algorithm> // Required for std::all_of if used for whitespace check
11+
12+
// Function to parse command line arguments (same as before)
13+
void parse_arguments(int argc, char* argv[], int& iterations, bool& print_ast_first_iteration) {
14+
iterations = 1; // Default value
15+
print_ast_first_iteration = false; // Default value
16+
17+
for (int i = 1; i < argc; ++i) {
18+
std::string arg = argv[i];
19+
if (arg == "-i") {
20+
if (i + 1 < argc) {
21+
try {
22+
iterations = std::stoi(argv[++i]);
23+
if (iterations <= 0) {
24+
std::cerr << "Warning: Number of iterations must be positive. Using default (1)." << std::endl;
25+
iterations = 1;
26+
}
27+
} catch (const std::invalid_argument& ia) {
28+
std::cerr << "Warning: Invalid number for iterations. Using default (1)." << std::endl;
29+
iterations = 1;
30+
} catch (const std::out_of_range& oor) {
31+
std::cerr << "Warning: Iterations number out of range. Using default (1)." << std::endl;
32+
iterations = 1;
33+
}
34+
} else {
35+
std::cerr << "Warning: -i option requires one argument." << std::endl;
36+
}
37+
} else if (arg == "-v") {
38+
print_ast_first_iteration = true;
39+
} else {
40+
std::cerr << "Warning: Unknown argument: " << arg << std::endl;
41+
}
42+
}
43+
}
44+
45+
// Helper to trim whitespace from both ends of a string
46+
std::string trim_string(const std::string &s) {
47+
auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c){return std::isspace(c);});
48+
auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c){return std::isspace(c);}).base();
49+
return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback));
50+
}
51+
52+
// Helper to check if a line is effectively empty (contains only whitespace)
53+
bool is_line_empty(const std::string& s) {
54+
return std::all_of(s.begin(), s.end(), isspace);
55+
}
56+
57+
58+
int main(int argc, char* argv[]) {
59+
int iterations_count;
60+
bool verbose_ast_first_iteration;
61+
62+
parse_arguments(argc, argv, iterations_count, verbose_ast_first_iteration);
63+
64+
std::vector<std::string> all_queries;
65+
std::cout << "Reading SQL queries from standard input. Press Ctrl+D (Linux/macOS) or Ctrl+Z then Enter (Windows) to end input." << std::endl;
66+
std::cout << "Queries can be optionally terminated by ';'. An empty line also acts as a delimiter for multi-line queries." << std::endl;
67+
68+
std::string line;
69+
std::stringstream current_query_buffer;
70+
71+
while (std::getline(std::cin, line)) {
72+
bool line_is_effectively_empty = is_line_empty(line);
73+
74+
if (line_is_effectively_empty) {
75+
if (current_query_buffer.tellp() > 0) { // Check if buffer has content (tellp gives current put position)
76+
std::string query_candidate = current_query_buffer.str();
77+
current_query_buffer.str(""); // Clear buffer
78+
current_query_buffer.clear(); // Clear error flags
79+
80+
std::string final_query = trim_string(query_candidate);
81+
if (!final_query.empty()) {
82+
all_queries.push_back(final_query);
83+
}
84+
}
85+
} else {
86+
current_query_buffer << line << "\n"; // Append line and a newline
87+
88+
// Check if the non-empty line ends with a semicolon
89+
std::string trimmed_current_line = trim_string(line); // Trim the current line for semicolon check
90+
if (!trimmed_current_line.empty() && trimmed_current_line.back() == ';') {
91+
if (current_query_buffer.tellp() > 0) {
92+
std::string query_candidate = current_query_buffer.str();
93+
current_query_buffer.str("");
94+
current_query_buffer.clear();
95+
96+
std::string final_query = trim_string(query_candidate);
97+
if (!final_query.empty()) {
98+
all_queries.push_back(final_query);
99+
}
100+
}
101+
}
102+
}
103+
}
104+
// Add any remaining content in the buffer as the last query (EOF)
105+
if (current_query_buffer.tellp() > 0) {
106+
std::string query_candidate = current_query_buffer.str();
107+
current_query_buffer.str("");
108+
current_query_buffer.clear();
109+
std::string final_query = trim_string(query_candidate);
110+
if (!final_query.empty()) {
111+
all_queries.push_back(final_query);
112+
}
113+
}
114+
115+
if (all_queries.empty()) {
116+
std::cout << "No queries read from standard input. Exiting." << std::endl;
117+
return 0;
118+
}
119+
std::cout << all_queries.size() << " query/queries read from input. Starting parsing iterations." << std::endl;
120+
121+
MysqlParser::Parser parser;
122+
long long successful_parses = 0;
123+
long long failed_parses = 0;
124+
125+
// Start timer *after* reading input and *before* parsing loop
126+
auto total_start_time = std::chrono::high_resolution_clock::now();
127+
128+
for (int iter = 0; iter < iterations_count; ++iter) {
129+
if (iterations_count > 1 && all_queries.size() > 0) {
130+
std::cout << "Iteration " << (iter + 1) << "/" << iterations_count << std::endl;
131+
}
132+
for (const std::string& query_to_parse : all_queries) {
133+
// Output the query being parsed if verbose on first iteration, or for debugging
134+
// if (verbose_ast_first_iteration && iter == 0) {
135+
// std::cout << "Parsing query: [" << query_to_parse << "]" << std::endl;
136+
// }
137+
138+
parser.clearErrors();
139+
std::unique_ptr<MysqlParser::AstNode> ast = parser.parse(query_to_parse);
140+
141+
if (ast) {
142+
successful_parses++;
143+
if (verbose_ast_first_iteration && iter == 0) {
144+
std::cout << "------------------------------------------\n";
145+
std::cout << "Query: " << query_to_parse << std::endl;
146+
std::cout << "Parsing successful! AST:" << std::endl;
147+
MysqlParser::print_ast(ast.get());
148+
std::cout << "------------------------------------------\n\n";
149+
}
150+
} else {
151+
failed_parses++;
152+
if (verbose_ast_first_iteration && iter == 0) {
153+
std::cout << "------------------------------------------\n";
154+
std::cout << "Query: " << query_to_parse << std::endl;
155+
std::cout << "Parsing failed." << std::endl;
156+
const auto& errors = parser.getErrors();
157+
if (errors.empty()) {
158+
std::cout << " (No specific error messages)" << std::endl;
159+
} else {
160+
for (const auto& error : errors) {
161+
std::cout << " Error: " << error << std::endl;
162+
}
163+
}
164+
std::cout << "------------------------------------------\n\n";
165+
}
166+
}
167+
}
168+
}
169+
170+
auto total_end_time = std::chrono::high_resolution_clock::now();
171+
std::chrono::duration<double> total_duration = total_end_time - total_start_time;
172+
double total_seconds = total_duration.count();
173+
174+
long long total_parsing_attempts = static_cast<long long>(iterations_count) * all_queries.size();
175+
176+
double parsing_per_second = (total_seconds > 0 && total_parsing_attempts > 0) ? (total_parsing_attempts / total_seconds) : 0;
177+
178+
std::cout << "\n======= SUMMARY =======\n";
179+
std::cout << "Unique queries read from input: " << all_queries.size() << std::endl;
180+
std::cout << "Iterations performed over these queries: " << iterations_count << std::endl;
181+
std::cout << "Total parsing attempts: " << total_parsing_attempts << std::endl;
182+
std::cout << "Successful parses: " << successful_parses << std::endl;
183+
std::cout << "Failed parses: " << failed_parses << std::endl;
184+
std::cout << "Total parsing time: " << std::fixed << std::setprecision(3) << total_seconds << " seconds" << std::endl;
185+
if (total_parsing_attempts > 0 && total_seconds > 0) {
186+
std::cout << "Average parsing speed: " << std::fixed << std::setprecision(2) << parsing_per_second << " queries/second" << std::endl;
187+
} else {
188+
std::cout << "Average parsing speed: N/A (no queries parsed or zero execution time)" << std::endl;
189+
}
190+
std::cout << "=======================\n";
191+
192+
return 0;
193+
}

src/mysql_parser/mysql_parser.y

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ int mysql_yylex(union MYSQL_YYSTYPE* yylval_param, yyscan_t yyscanner, MysqlPars
9191
%type <node_val> opt_locking_clause_list locking_clause_list locking_clause lock_strength opt_lock_table_list opt_lock_option
9292
%type <node_val> subquery derived_table
9393

94+
%type <node_val> single_input_statement
95+
9496
// Precedence
9597
%left TOKEN_OR // Assuming OR might be added
9698
%left TOKEN_AND
@@ -110,13 +112,35 @@ int mysql_yylex(union MYSQL_YYSTYPE* yylval_param, yyscan_t yyscanner, MysqlPars
110112
%right TOKEN_FOR
111113
%left TOKEN_COMMA
112114

113-
%start query_list
115+
%start single_input_statement
114116

115117
%%
116118

117-
query_list:
118-
/* empty */ { if (parser_context) parser_context->internal_set_ast(nullptr); }
119-
| query_list statement { /* Managed by parser_context */ }
119+
// New start rule definition:
120+
// This rule expects to parse one statement, or handle empty input.
121+
// Bison will implicitly expect this rule to consume the entire input stream
122+
// up to the EOF marker returned by the lexer (usually 0).
123+
// New start rule definition:
124+
single_input_statement:
125+
/* empty input */ {
126+
if (parser_context) {
127+
// If Parser::parse() initializes its ast_root_ to nullptr before calling yyparse,
128+
// and internal_set_ast(nullptr) is safe (e.g., deletes old, sets to null),
129+
// this call ensures the parser's state is clean for empty input.
130+
parser_context->internal_set_ast(nullptr);
131+
}
132+
$$ = nullptr; // The result of parsing an empty input is a null AST.
133+
}
134+
| statement {
135+
// The 'statement' rule's alternatives (e.g., select_statement, insert_statement)
136+
// are responsible for calling parser_context->internal_set_ast($1)
137+
// with the AST node they produce.
138+
// This 'single_input_statement' rule simply propagates the AST node ($1)
139+
// received from the 'statement' rule.
140+
// The parser_context->ast_root_ should already hold the correct AST node ($1)
141+
// due to the call within the 'statement' rule's actions.
142+
$$ = $1;
143+
}
120144
;
121145

122146
optional_semicolon:

0 commit comments

Comments
 (0)