1- /* Serial Implementation - uses the bplus serial engine and tokenizer to execute a provided SQL query */
1+ /* Serial Implementation - Runs commands from the query text file using connectEngine */
22
33#define _POSIX_C_SOURCE 200809L // For strdup
44#include <stdio.h>
55#include <stdlib.h>
66#include <string.h>
7- #include <strings.h>
8- #include <ctype.h>
9- #include <assert.h>
10- #include "../include/executeEngine-serial.h"
11- #include "../include/sql.h"
12- #include "../include/printHelper.h"
137#include <time.h>
14-
15- // Constants for the test environment
16- #define DATA_FILE "data-generation/commands_50k.csv"
17- #define TABLE_NAME "commands"
18- #define MAX_TOKENS 100
19- #define ROW_LIMIT 20 // Max rows to print in output
20-
21- // Forward declarations B+ tree implementation
22- typedef struct node node ; // Pull node declaration from serial bplus
23- typedef struct record record ; // Pull record declaration from serial bplus
24-
25- /* --- Helper functions --- */
26-
27- // Helper to safely copy strings with truncation
28- static inline void safe_copy (char * dst , size_t n , const char * src ) {
29- snprintf (dst , n , "%.*s" , (int )n - 1 , src );
30- }
31-
32- // Helper to trim whitespace from a string
33- static inline char * trim (char * s ) {
34- while (* s && isspace ((unsigned char )* s )) s ++ ;
35- return s ;
36- }
37-
38- // Helper to map Parser OperatorType to string
39- const char * get_operator_string (OperatorType op ) {
40- switch (op ) {
41- case OP_EQ : return "=" ;
42- case OP_NEQ : return "!=" ;
43- case OP_GT : return ">" ;
44- case OP_LT : return "<" ;
45- case OP_GTE : return ">=" ;
46- case OP_LTE : return "<=" ;
47- default : return "=" ;
48- }
49- }
50-
51- // Helper to map Parser LogicOperator to string
52- const char * get_logic_op_string (LogicOperator op ) {
53- switch (op ) {
54- case LOGIC_AND : return "AND" ;
55- case LOGIC_OR : return "OR" ;
56- default : return "AND" ;
57- }
58- }
59-
60- // Optimal indexes constant
61- const char * optimalIndexes [] = {
62- "command_id" ,
63- "user_id" ,
64- "risk_level" ,
65- "exit_code" ,
66- "sudo_used"
67- };
68- const FieldType optimalIndexTypes [] = {
69- FIELD_UINT64 ,
70- FIELD_INT ,
71- FIELD_INT ,
72- FIELD_INT ,
73- FIELD_BOOL
74- };
75- const int numOptimalIndexes = 5 ;
76-
77- // Helper to convert ParsedSQL conditions to engine's whereClauseS linked list
78- struct whereClauseS * convert_conditions (ParsedSQL * parsed ) {
79- if (parsed -> num_conditions == 0 ) return NULL ;
80-
81- struct whereClauseS * head = NULL ;
82- struct whereClauseS * current = NULL ;
83-
84- for (int i = 0 ; i < parsed -> num_conditions ; i ++ ) {
85- struct whereClauseS * node = malloc (sizeof (struct whereClauseS ));
86- node -> attribute = parsed -> conditions [i ].column ;
87- node -> operator = get_operator_string (parsed -> conditions [i ].op );
88- node -> value = parsed -> conditions [i ].value ;
89-
90- // Simple type inference for the test
91- if (parsed -> conditions [i ].is_numeric ) {
92- node -> value_type = 0 ; // Integer/Number
93- } else {
94- node -> value_type = 1 ; // String
95- }
96-
97- // TODO - Handle sub-expressions and nested conditions
98- // Note: Nested conditions are not currently supported by the test harness
99- node -> next = NULL ;
100- node -> sub = NULL ;
101-
102- // Set logical operator for the *previous* node to connect to this one
103- // The parser stores logic ops between conditions. logic_ops[0] is between cond[0] and cond[1]
104- if (i < parsed -> num_conditions - 1 ) {
105- node -> logical_op = get_logic_op_string (parsed -> logic_ops [i ]);
106- } else {
107- node -> logical_op = NULL ;
108- }
109-
110- if (head == NULL ) {
111- head = node ;
112- current = node ;
113- } else {
114- current -> next = node ;
115- current = node ;
116- }
117- }
118- return head ;
119- }
120-
121- // Helper to free the manually constructed where clause
122- void free_where_clause_list (struct whereClauseS * head ) {
123- while (head ) {
124- struct whereClauseS * temp = head ;
125- head = head -> next ;
126- free (temp );
127- }
128- }
129-
130- // Main test runner for a single query string
131- void run_test_query (struct engineS * engine , const char * query , int max_rows ) {
132-
133- // Print query for testing
134- printf ("Executing Query: %s\n" , query );
135-
136- // Tokenize
137- Token tokens [MAX_TOKENS ];
138- int num_tokens = tokenize (query , tokens , MAX_TOKENS );
139- if (num_tokens <= 0 ) {
140- printf ("Tokenization failed.\n" );
141- return ;
142- }
143-
144- // Parse - Determine which command is ran and extract components
145- ParsedSQL parsed = parse_tokens (tokens );
146-
147- // Convert to Engine Arguments
148- const char * selectItems [parsed .num_columns > 0 ? parsed .num_columns : 1 ];
149- int numSelectItems = 0 ;
150-
151- if (!parsed .select_all ) {
152- numSelectItems = parsed .num_columns ;
153- for (int i = 0 ; i < numSelectItems ; i ++ ) {
154- selectItems [i ] = parsed .columns [i ];
155- }
156- }
157-
158- // Handle non-SELECT commands here and return early
159- switch (parsed .command ) {
160- case CMD_INSERT : {
161- if (parsed .num_values != 12 ) {
162- printf ("Error: INSERT requires exactly 12 values.\n" );
163- return ;
164- }
165- record r ;
166- r .command_id = strtoull (parsed .insert_values [0 ], NULL , 10 );
167- safe_copy (r .raw_command , sizeof (r .raw_command ), parsed .insert_values [1 ]);
168- safe_copy (r .base_command , sizeof (r .base_command ), parsed .insert_values [2 ]);
169- safe_copy (r .shell_type , sizeof (r .shell_type ), parsed .insert_values [3 ]);
170- r .exit_code = atoi (parsed .insert_values [4 ]);
171- safe_copy (r .timestamp , sizeof (r .timestamp ), parsed .insert_values [5 ]);
172-
173- // Handle boolean sudo_used
174- r .sudo_used = (strcasecmp (parsed .insert_values [6 ], "true" ) == 0 || strcmp (parsed .insert_values [6 ], "1" ) == 0 );
175-
176- safe_copy (r .working_directory , sizeof (r .working_directory ), parsed .insert_values [7 ]);
177- r .user_id = atoi (parsed .insert_values [8 ]);
178- safe_copy (r .user_name , sizeof (r .user_name ), parsed .insert_values [9 ]);
179- safe_copy (r .host_name , sizeof (r .host_name ), parsed .insert_values [10 ]);
180- r .risk_level = atoi (parsed .insert_values [11 ]);
181-
182- if (executeQueryInsertSerial (engine , parsed .table , & r )) {
183- printf ("Insert successful.\n\n" );
184- } else {
185- printf ("Insert failed.\n\n" );
186- }
187- return ;
188- }
189-
190- case CMD_DELETE : {
191- struct whereClauseS * whereClause = convert_conditions (& parsed );
192- struct resultSetS * result = executeQueryDeleteSerial (engine , parsed .table , whereClause );
193- if (result ) {
194- printf ("Delete successful. Rows affected: %d\n" , result -> numRecords );
195- freeResultSet (result );
196- } else {
197- printf ("Delete failed.\n" );
198- }
199- free_where_clause_list (whereClause );
200- return ;
201- }
202-
203- case CMD_SELECT : {
204- // Handled below
205- break ;
206- }
207-
208- case CMD_NONE : {
209- printf ("No command detected.\n" );
210- return ;
211- }
212-
213- default : {
214- printf ("Unsupported command.\n" );
215- return ;
216- }
217- }
218-
219- // Retreive the WHERE clause
220- struct whereClauseS * whereClause = convert_conditions (& parsed );
221-
222- // Execute
223- struct resultSetS * result = executeQuerySelectSerial (
224- engine ,
225- selectItems ,
226- numSelectItems ,
227- parsed .table ,
228- whereClause
229- );
230-
231- // Verify and Print
232- printTable (NULL , result , max_rows );
233-
234- // Cleanup
235- if (result ) freeResultSet (result ); // Free the results object
236- free_where_clause_list (whereClause ); // Free where clause linked list
237- printf ("\n" );
238- }
239-
240- /* --- Main Functionality - intake commands from text file and process */
8+ #include "../include/executeEngine-serial.h"
9+ #include "../include/connectEngine.h"
10+ // ANSI color codes for pretty printing
11+ #define CYAN "\x1b[36m"
12+ #define YELLOW "\x1b[33m"
13+ #define BOLD "\x1b[1m"
14+ #define RESET "\x1b[0m"
24115
24216int main (int argc , char * argv []) {
24317
@@ -256,6 +30,9 @@ int main(int argc, char *argv[]) {
25630 TABLE_NAME
25731 );
25832
33+ // End timer for engine initialization
34+ double initTimeTaken = ((double )clock () - totalStart ) / CLOCKS_PER_SEC ;
35+
25936 // Load the COMMANDS into memory (from COMMAND text file)
26037 const char * query_file = "sample-queries.txt" ;
26138 FILE * fp = fopen (query_file , "r" );
@@ -287,6 +64,9 @@ int main(int argc, char *argv[]) {
28764 buffer [fsize ] = 0 ;
28865 fclose (fp );
28966
67+ // End timer for loading queries
68+ double loadTimeTaken = ((double )clock () - totalStart ) / CLOCKS_PER_SEC ;
69+
29070 // Run each command from the command input file
29171 char * query = strtok (buffer , ";" );
29272 while (query ) {
@@ -301,8 +81,14 @@ int main(int argc, char *argv[]) {
30181 free (buffer );
30282 destroyEngineSerial (engine );
30383
84+ // Print total runtime statistics in pretty colors
30485 double totalTimeTaken = ((double )clock () - totalStart ) / CLOCKS_PER_SEC ;
305- printf ("Total Execution Time For All Queries: %.4f seconds\n" , totalTimeTaken );
86+ printf (CYAN "======= Execution Summary =======" RESET "\n" );
87+ printf (CYAN "Engine Initialization Time: " RESET YELLOW "%.4f seconds\n" RESET , initTimeTaken );
88+ printf (CYAN "Query Loading Time: " RESET YELLOW "%.4f seconds\n" RESET , loadTimeTaken - initTimeTaken );
89+ printf (CYAN "Query Execution Time: " RESET YELLOW "%.4f seconds\n" RESET , totalTimeTaken - loadTimeTaken );
90+ printf (BOLD CYAN "Total Execution Time: " RESET BOLD YELLOW "%.4f seconds" RESET "\n" , totalTimeTaken );
91+ printf (CYAN "=================================" RESET "\n" );
30692
30793 return EXIT_SUCCESS ;
30894}
0 commit comments