diff --git a/bin/main b/bin/main new file mode 100755 index 0000000..7ca1b23 Binary files /dev/null and b/bin/main differ diff --git a/database.db b/database.db index 3749b81..86ff8a4 100644 Binary files a/database.db and b/database.db differ diff --git a/include/db.h b/include/db.h index 645eb31..50a2800 100644 --- a/include/db.h +++ b/include/db.h @@ -1,42 +1,53 @@ -#ifndef _DB_H_ -#define _DB_H_ +#ifndef DB_H +#define DB_H + +#include -#define MAX_TABLES 10 -#define MAX_COLUMNS 10 -#define MAX_ROWS 100 #define MAX_QUERY_LENGTH 256 -#define MAX_NAME_LENGTH 50 + +/* Default filename for saving/loading the database */ #define DB_FILE "database.db" -typedef struct + +typedef struct Column { - char name[MAX_NAME_LENGTH]; - char data[MAX_ROWS][MAX_NAME_LENGTH]; + char *name; + char **data; /* Array of string data for each row */ } Column; -typedef struct +typedef struct Table { - char name[MAX_NAME_LENGTH]; - Column columns[MAX_COLUMNS]; - int column_count; + char *name; int row_count; + int column_count; + Column **columns; } Table; -typedef struct +typedef struct Database { - Table tables[MAX_TABLES]; int table_count; + Table **tables; } Database; +/* Database Operations */ Database *create_db(void); -void create_table(Database *db, const char *table_name, char *columns); -void insert_into_table(Database *db, const char *table_name, char *values); -void select_from_table(Database *db, const char *table_name); -void parse_query(Database *db, const char *query); -Table *find_table(Database *db, const char *table_name); -void save_database_to_file(Database *db, const char *filename); -void load_database_from_file(Database *db, const char *filename); -int validate_ipv4_address(const char *ip); -void trim_whitespace(char *str); +void free_database(Database *db); -#endif \ No newline at end of file +/* Table Operations */ +Table *find_table(Database *db, const char *table_name); +void create_table(Database *db, const char *table_name, const char *columns_str); +void insert_into_table(Database *db, const char *table_name, const char *values_str); +void select_from_table(Database *db, const char *table_name); + +/* File Operations */ +void save_database_to_file(Database *db, const char *filename); +Database *load_database_from_file(Database *db, const char *filename); + +/* Query Parsing */ +Database *parse_query(Database *db, const char *query); + +/* Utility Functions */ +int validate_ipv4_address(const char *ip); +char *trim_whitespace(char *str); + +#endif /* DB_H */ diff --git a/src/db.c b/src/db.c index a76af78..0ba912b 100644 --- a/src/db.c +++ b/src/db.c @@ -2,42 +2,79 @@ #include #include #include - #include "db.h" -void trim_whitespace(char *str) +/* Writes a string to a file. + * The string is preceded by its length (including the null terminator). + */ +static void write_string(FILE *file, const char *str) +{ + int len = (int)strlen(str) + 1; + fwrite(&len, sizeof(int), 1, file); + fwrite(str, sizeof(char), len, file); +} + +/* Reads a string from a file that was written using write_string. + * Returns a pointer to the heap-allocated string, or NULL on failure. + */ +static char *read_string(FILE *file) +{ + int len; + char *str; + + if (fread(&len, sizeof(int), 1, file) != 1) + { + return NULL; + } + + str = malloc(len); + if (str == NULL) + { + return NULL; + } + + fread(str, sizeof(char), len, file); + return str; +} + +/* Trims leading and trailing whitespace from a string in place. + * Returns a pointer to the trimmed string. + */ +char *trim_whitespace(char *str) { char *end; + /* Trim leading whitespace */ while (isspace((unsigned char)*str)) { str++; } - if (*str == 0) + if (*str == '\0') { - return; + return str; } + /* Trim trailing whitespace */ end = str + strlen(str) - 1; - while (end > str && isspace((unsigned char)*end)) { end--; } + /* Write new null terminator */ end[1] = '\0'; + return str; } +/* Validates if the provided string is in valid IPv4 format. + * Returns 1 if valid, 0 otherwise. + */ int validate_ipv4_address(const char *ip) { - int segments; - int ch_count; - const char *ptr; - - segments = 0; - ch_count = 0; - ptr = ip; + int segments = 0; + int ch_count = 0; + const char *ptr = ip; while (*ptr) { @@ -47,7 +84,6 @@ int validate_ipv4_address(const char *ip) { return 0; } - segments++; ch_count = 0; } @@ -59,7 +95,6 @@ int validate_ipv4_address(const char *ip) { ch_count++; } - ptr++; } @@ -67,217 +102,389 @@ int validate_ipv4_address(const char *ip) { return 0; } - return 1; } +/* Creates a new Database instance. + * Returns a pointer to the new Database or NULL on failure. + */ Database *create_db(void) { - Database *db; - - db = calloc(1, sizeof(*db)); + Database *db = malloc(sizeof(Database)); if (db == NULL) { printf("Failed to allocate memory for DB.\n"); return NULL; } + db->tables = NULL; db->table_count = 0; return db; } -Table *find_table(Database *db, const char *table_name) +/* Frees all memory associated with the Database. + */ +void free_database(Database *db) { - int i; - - for (i = 0; i < db->table_count; i++) + if (db == NULL) { - if (strcmp(db->tables[i].name, table_name) == 0) - { - return &db->tables[i]; - } - } - - return NULL; -} - -void create_table(Database *db, const char *table_name, char *columns) -{ - Table *table; - char *token; - - if (db->table_count >= MAX_TABLES) - { - printf("Error: Maximum table limit reached.\n"); return; } - if (find_table(db, table_name)) + for (int i = 0; i < db->table_count; i++) + { + Table *currTable = db->tables[i]; + free(currTable->name); + + for (int j = 0; j < currTable->column_count; j++) + { + Column *currColumn = currTable->columns[j]; + free(currColumn->name); + + for (int r = 0; r < currTable->row_count; r++) + { + free(currColumn->data[r]); + } + free(currColumn->data); + free(currColumn); + } + free(currTable->columns); + free(currTable); + } + free(db->tables); + free(db); +} + +/* Searches for a table by name in the Database. + * Returns a pointer to the Table if found, or NULL otherwise. + */ +Table *find_table(Database *db, const char *table_name) +{ + for (int i = 0; i < db->table_count; i++) + { + if (strcmp(db->tables[i]->name, table_name) == 0) + { + return db->tables[i]; + } + } + return NULL; +} + +/* Creates a new table with the given name and comma-separated column definitions. + */ +void create_table(Database *db, const char *table_name, const char *columns_str) +{ + if (find_table(db, table_name) != NULL) { printf("Error: Table '%s' already exists.\n", table_name); return; } - table = &db->tables[db->table_count++]; - strcpy(table->name, table_name); - table->column_count = 0; - table->row_count = 0; - - token = strtok(columns, ","); - - while (token && table->column_count < MAX_COLUMNS) + Table *table = malloc(sizeof(Table)); + if (table == NULL) { - trim_whitespace(token); - strcpy(table->columns[table->column_count++].name, token); + printf("Error: Memory allocation failed for table '%s'.\n", table_name); + return; + } + table->name = strdup(table_name); + table->row_count = 0; + table->column_count = 0; + table->columns = NULL; + + char *cols_copy = strdup(columns_str); + if (cols_copy == NULL) + { + printf("Error: Memory allocation failed for columns copy.\n"); + free(table); + return; + } + + char *token = strtok(cols_copy, ","); + while (token != NULL) + { + token = trim_whitespace(token); + Column *col = malloc(sizeof(Column)); + if (col == NULL) + { + printf("Error: Memory allocation failed for column '%s'.\n", token); + free(cols_copy); + return; + } + col->name = strdup(token); + col->data = NULL; + + table->columns = realloc(table->columns, sizeof(Column*) * (table->column_count + 1)); + if (table->columns == NULL) + { + printf("Error: Memory allocation failed while adding column '%s'.\n", token); + free(col->name); + free(col); + free(cols_copy); + return; + } + table->columns[table->column_count++] = col; token = strtok(NULL, ","); } + free(cols_copy); if (table->column_count == 0) { printf("Error: No columns defined for table '%s'.\n", table_name); - db->table_count--; + free(table->name); + free(table); return; } - printf("Table '%s' with %d columns created successfully.\n", table_name, table->column_count); + db->tables = realloc(db->tables, sizeof(Table*) * (db->table_count + 1)); + if (db->tables == NULL) + { + printf("Error: Memory allocation failed while adding table '%s'.\n", table_name); + free_database(db); + exit(EXIT_FAILURE); + } + db->tables[db->table_count++] = table; + + printf("Table '%s' with %d columns created successfully.\n", table->name, table->column_count); } -void insert_into_table(Database *db, const char *table_name, char *values) +/* Inserts a new row into the specified table using comma-separated values. + */ +void insert_into_table(Database *db, const char *table_name, const char *values_str) { - Table *table; - char *token; - int col_index; - - table = find_table(db, table_name); - - if (!table) + Table *table = find_table(db, table_name); + if (table == NULL) { printf("Error: Table '%s' does not exist.\n", table_name); return; } - if (table->column_count == 0) + char *vals_copy = strdup(values_str); + if (vals_copy == NULL) { - printf("Error: Table '%s' has no columns defined.\n", table_name); + printf("Error: Memory allocation failed for values copy.\n"); return; } - if (table->row_count >= MAX_ROWS) + char *token = strtok(vals_copy, ","); + int col_index = 0; + char **values = malloc(sizeof(char*) * table->column_count); + if (values == NULL) { - printf("Error: Maximum row limit reached for table '%s'.\n", table_name); + printf("Error: Memory allocation failed for values array.\n"); + free(vals_copy); return; } - token = strtok(values, ","); - col_index = 0; - - while (token && col_index < table->column_count) + while (token != NULL && col_index < table->column_count) { - trim_whitespace(token); - - if (strcmp(table->columns[col_index].name, "IPv4") == 0) + token = trim_whitespace(token); + /* Validate IPv4 address if required */ + if (strcmp(table->columns[col_index]->name, "IPv4") == 0) { if (!validate_ipv4_address(token)) { printf("Error: Invalid IPv4 address '%s'.\n", token); + free(vals_copy); + for (int i = 0; i < col_index; i++) + { + free(values[i]); + } + free(values); return; } } - - strcpy(table->columns[col_index].data[table->row_count], token); + values[col_index++] = strdup(token); token = strtok(NULL, ","); - col_index++; } + free(vals_copy); if (col_index != table->column_count) { printf("Error: Column count mismatch for table '%s'.\n", table_name); + for (int i = 0; i < col_index; i++) + { + free(values[i]); + } + free(values); return; } + for (int i = 0; i < table->column_count; i++) + { + Column *col = table->columns[i]; + col->data = realloc(col->data, sizeof(char*) * (table->row_count + 1)); + if (col->data == NULL) + { + printf("Error: Memory allocation failed while inserting row.\n"); + free(values); + return; + } + col->data[table->row_count] = values[i]; + } + free(values); table->row_count++; printf("Row inserted into table '%s'.\n", table_name); } +/* Displays the contents of the specified table. + */ void select_from_table(Database *db, const char *table_name) { - Table *table; - int i; - int j; - - table = find_table(db, table_name); - - if (!table) + Table *table = find_table(db, table_name); + if (table == NULL) { printf("Error: Table '%s' does not exist.\n", table_name); return; } printf("Table: %s\n", table->name); - - for (i = 0; i < table->column_count; i++) + for (int i = 0; i < table->column_count; i++) { - printf("%s\t", table->columns[i].name); + printf("%s\t", table->columns[i]->name); } - printf("\n"); - for (i = 0; i < table->row_count; i++) + for (int r = 0; r < table->row_count; r++) { - for (j = 0; j < table->column_count; j++) + for (int c = 0; c < table->column_count; c++) { - printf("%s\t", table->columns[j].data[i]); + printf("%s\t", table->columns[c]->data[r]); } - printf("\n"); } } +/* Saves the database to a binary file. + */ void save_database_to_file(Database *db, const char *filename) { - FILE *file; - - file = fopen(filename, "wb"); - - if (!file) + FILE *file = fopen(filename, "wb"); + if (file == NULL) { printf("Error: Could not open file '%s' for writing.\n", filename); return; } - fwrite(db, sizeof(Database), 1, file); + fwrite(&db->table_count, sizeof(int), 1, file); + for (int i = 0; i < db->table_count; i++) + { + Table *table = db->tables[i]; + write_string(file, table->name); + fwrite(&table->column_count, sizeof(int), 1, file); + fwrite(&table->row_count, sizeof(int), 1, file); + for (int j = 0; j < table->column_count; j++) + { + Column *col = table->columns[j]; + write_string(file, col->name); + for (int r = 0; r < table->row_count; r++) + { + write_string(file, col->data[r]); + } + } + } fclose(file); printf("Database saved to '%s'.\n", filename); } -void load_database_from_file(Database *db, const char *filename) +/* Loads a database from a binary file. + * Frees the current database and returns a new one loaded from the file. + */ +Database *load_database_from_file(Database *db, const char *filename) { - FILE *file; - - file = fopen(filename, "rb"); - - if (!file) + FILE *file = fopen(filename, "rb"); + if (file == NULL) { printf("Error: Could not open file '%s' for reading.\n", filename); - return; + return db; } - fread(db, sizeof(Database), 1, file); + free_database(db); + Database *new_db = create_db(); + if (new_db == NULL) + { + fclose(file); + return NULL; + } + int table_count = 0; + fread(&table_count, sizeof(int), 1, file); + for (int i = 0; i < table_count; i++) + { + Table *table = malloc(sizeof(Table)); + if (table == NULL) + { + printf("Error: Memory allocation failed while loading table.\n"); + continue; + } + + table->name = read_string(file); + fread(&table->column_count, sizeof(int), 1, file); + fread(&table->row_count, sizeof(int), 1, file); + table->columns = NULL; + + for (int j = 0; j < table->column_count; j++) + { + Column *col = malloc(sizeof(Column)); + if (col == NULL) + { + printf("Error: Memory allocation failed while loading column.\n"); + continue; + } + col->name = read_string(file); + col->data = NULL; + + for (int r = 0; r < table->row_count; r++) + { + char *cell = read_string(file); + col->data = realloc(col->data, sizeof(char*) * (r + 1)); + if (col->data == NULL) + { + printf("Error: Memory allocation failed while loading row data.\n"); + free(cell); + continue; + } + col->data[r] = cell; + } + table->columns = realloc(table->columns, sizeof(Column*) * (j + 1)); + if (table->columns == NULL) + { + printf("Error: Memory allocation failed while loading columns array.\n"); + free(col); + continue; + } + table->columns[j] = col; + } + new_db->tables = realloc(new_db->tables, sizeof(Table*) * (i + 1)); + if (new_db->tables == NULL) + { + printf("Error: Memory allocation failed while adding table to database.\n"); + free(table); + continue; + } + new_db->tables[i] = table; + new_db->table_count++; + } fclose(file); printf("Database loaded from '%s'.\n", filename); + return new_db; } -void parse_query(Database *db, const char *query) +/* Parses and executes a query string. + * Supported commands: CREATE TABLE, INSERT INTO, SELECT, SAVE, LOAD. + */ +Database *parse_query(Database *db, const char *query) { char query_copy[MAX_QUERY_LENGTH]; - char *command; - char *table_name; - char *columns; + strncpy(query_copy, query, MAX_QUERY_LENGTH - 1); + query_copy[MAX_QUERY_LENGTH - 1] = '\0'; - strcpy(query_copy, query); - command = strtok(query_copy, " "); + char *command = strtok(query_copy, " "); + if (command == NULL) + { + printf("Error: Empty query.\n"); + return db; + } if (strcmp(command, "CREATE") == 0) { @@ -285,40 +492,37 @@ void parse_query(Database *db, const char *query) if (next_token == NULL || strcmp(next_token, "TABLE") != 0) { printf("Error: Invalid CREATE TABLE syntax.\n"); - return; + return db; } - table_name = strtok(NULL, " "); + char *table_name = strtok(NULL, " "); if (table_name == NULL) { printf("Error: Table name is missing.\n"); - return; + return db; } - columns = strchr(query, '('); + char *columns = strchr(query, '('); if (columns == NULL) { printf("Error: Missing column definitions.\n"); - return; + return db; } - columns++; char *closing_paren = strchr(columns, ')'); if (closing_paren == NULL) { printf("Error: Missing closing parenthesis in column definitions.\n"); - return; + return db; } - *closing_paren = '\0'; - trim_whitespace(columns); + columns = trim_whitespace(columns); if (strlen(columns) == 0) { printf("Error: No columns defined for table '%s'.\n", table_name); - return; + return db; } - create_table(db, table_name, columns); } else if (strcmp(command, "INSERT") == 0) @@ -327,53 +531,50 @@ void parse_query(Database *db, const char *query) if (next_token == NULL || strcmp(next_token, "INTO") != 0) { printf("Error: Invalid INSERT INTO syntax.\n"); - return; + return db; } - table_name = strtok(NULL, " "); + char *table_name = strtok(NULL, " "); if (table_name == NULL) { printf("Error: Table name is missing.\n"); - return; + return db; } - columns = strchr(query, '('); - if (columns == NULL) + char *values = strchr(query, '('); + if (values == NULL) { printf("Error: Missing values.\n"); - return; + return db; } - - columns++; - char *closing_paren = strchr(columns, ')'); + values++; + char *closing_paren = strchr(values, ')'); if (closing_paren == NULL) { printf("Error: Missing closing parenthesis in values.\n"); - return; + return db; } - *closing_paren = '\0'; - trim_whitespace(columns); - if (strlen(columns) == 0) + values = trim_whitespace(values); + if (strlen(values) == 0) { printf("Error: No values provided for table '%s'.\n", table_name); - return; + return db; } - - insert_into_table(db, table_name, columns); + insert_into_table(db, table_name, values); } else if (strcmp(command, "SELECT") == 0) { - strtok(NULL, " "); - strtok(NULL, " "); - table_name = strtok(NULL, " "); + /* Expected syntax: SELECT * FROM table_name */ + strtok(NULL, " "); // Skip '*' + strtok(NULL, " "); // Skip 'FROM' + char *table_name = strtok(NULL, " "); if (table_name == NULL) { printf("Error: Table name is missing in SELECT query.\n"); - return; + return db; } - select_from_table(db, table_name); } else if (strcmp(command, "SAVE") == 0) @@ -382,10 +583,12 @@ void parse_query(Database *db, const char *query) } else if (strcmp(command, "LOAD") == 0) { - load_database_from_file(db, DB_FILE); + db = load_database_from_file(db, DB_FILE); } else { printf("Error: Unsupported query.\n"); } -} \ No newline at end of file + + return db; +} diff --git a/src/main.c b/src/main.c index 6daa234..b038510 100644 --- a/src/main.c +++ b/src/main.c @@ -1,15 +1,14 @@ #include #include #include - #include "linenoise.h" #include "db.h" -int main() +int main(void) { char *query; Database *db; - + db = create_db(); printf("Simple SQL-like Database\n"); @@ -34,12 +33,14 @@ int main() if (strlen(query) > 0) { - parse_query(db, query); + db = parse_query(db, query); linenoiseHistoryAdd(query); } free(query); } + free_database(db); + return 0; -} \ No newline at end of file +}