Minor fixes

This commit is contained in:
Gigachad 2025-03-02 23:17:59 +00:00
parent 799b10d545
commit 72e61a1025
5 changed files with 388 additions and 173 deletions

BIN
bin/main Executable file

Binary file not shown.

Binary file not shown.

View File

@ -1,42 +1,53 @@
#ifndef _DB_H_ #ifndef DB_H
#define _DB_H_ #define DB_H
#include <stdio.h>
#define MAX_TABLES 10
#define MAX_COLUMNS 10
#define MAX_ROWS 100
#define MAX_QUERY_LENGTH 256 #define MAX_QUERY_LENGTH 256
#define MAX_NAME_LENGTH 50
/* Default filename for saving/loading the database */
#define DB_FILE "database.db" #define DB_FILE "database.db"
typedef struct
typedef struct Column
{ {
char name[MAX_NAME_LENGTH]; char *name;
char data[MAX_ROWS][MAX_NAME_LENGTH]; char **data; /* Array of string data for each row */
} Column; } Column;
typedef struct typedef struct Table
{ {
char name[MAX_NAME_LENGTH]; char *name;
Column columns[MAX_COLUMNS];
int column_count;
int row_count; int row_count;
int column_count;
Column **columns;
} Table; } Table;
typedef struct typedef struct Database
{ {
Table tables[MAX_TABLES];
int table_count; int table_count;
Table **tables;
} Database; } Database;
/* Database Operations */
Database *create_db(void); Database *create_db(void);
void create_table(Database *db, const char *table_name, char *columns); void free_database(Database *db);
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);
#endif /* 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 */

489
src/db.c
View File

@ -2,42 +2,79 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include "db.h" #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; char *end;
/* Trim leading whitespace */
while (isspace((unsigned char)*str)) while (isspace((unsigned char)*str))
{ {
str++; str++;
} }
if (*str == 0) if (*str == '\0')
{ {
return; return str;
} }
/* Trim trailing whitespace */
end = str + strlen(str) - 1; end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) while (end > str && isspace((unsigned char)*end))
{ {
end--; end--;
} }
/* Write new null terminator */
end[1] = '\0'; 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 validate_ipv4_address(const char *ip)
{ {
int segments; int segments = 0;
int ch_count; int ch_count = 0;
const char *ptr; const char *ptr = ip;
segments = 0;
ch_count = 0;
ptr = ip;
while (*ptr) while (*ptr)
{ {
@ -47,7 +84,6 @@ int validate_ipv4_address(const char *ip)
{ {
return 0; return 0;
} }
segments++; segments++;
ch_count = 0; ch_count = 0;
} }
@ -59,7 +95,6 @@ int validate_ipv4_address(const char *ip)
{ {
ch_count++; ch_count++;
} }
ptr++; ptr++;
} }
@ -67,217 +102,389 @@ int validate_ipv4_address(const char *ip)
{ {
return 0; return 0;
} }
return 1; return 1;
} }
/* Creates a new Database instance.
* Returns a pointer to the new Database or NULL on failure.
*/
Database *create_db(void) Database *create_db(void)
{ {
Database *db; Database *db = malloc(sizeof(Database));
db = calloc(1, sizeof(*db));
if (db == NULL) if (db == NULL)
{ {
printf("Failed to allocate memory for DB.\n"); printf("Failed to allocate memory for DB.\n");
return NULL; return NULL;
} }
db->tables = NULL;
db->table_count = 0; db->table_count = 0;
return db; 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; if (db == NULL)
for (i = 0; i < db->table_count; i++)
{ {
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; 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); printf("Error: Table '%s' already exists.\n", table_name);
return; return;
} }
table = &db->tables[db->table_count++]; Table *table = malloc(sizeof(Table));
strcpy(table->name, table_name); if (table == NULL)
table->column_count = 0;
table->row_count = 0;
token = strtok(columns, ",");
while (token && table->column_count < MAX_COLUMNS)
{ {
trim_whitespace(token); printf("Error: Memory allocation failed for table '%s'.\n", table_name);
strcpy(table->columns[table->column_count++].name, token); 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, ","); token = strtok(NULL, ",");
} }
free(cols_copy);
if (table->column_count == 0) if (table->column_count == 0)
{ {
printf("Error: No columns defined for table '%s'.\n", table_name); printf("Error: No columns defined for table '%s'.\n", table_name);
db->table_count--; free(table->name);
free(table);
return; 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; Table *table = find_table(db, table_name);
char *token; if (table == NULL)
int col_index;
table = find_table(db, table_name);
if (!table)
{ {
printf("Error: Table '%s' does not exist.\n", table_name); printf("Error: Table '%s' does not exist.\n", table_name);
return; 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; 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; return;
} }
token = strtok(values, ","); while (token != NULL && col_index < table->column_count)
col_index = 0;
while (token && col_index < table->column_count)
{ {
trim_whitespace(token); token = trim_whitespace(token);
/* Validate IPv4 address if required */
if (strcmp(table->columns[col_index].name, "IPv4") == 0) if (strcmp(table->columns[col_index]->name, "IPv4") == 0)
{ {
if (!validate_ipv4_address(token)) if (!validate_ipv4_address(token))
{ {
printf("Error: Invalid IPv4 address '%s'.\n", 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; return;
} }
} }
values[col_index++] = strdup(token);
strcpy(table->columns[col_index].data[table->row_count], token);
token = strtok(NULL, ","); token = strtok(NULL, ",");
col_index++;
} }
free(vals_copy);
if (col_index != table->column_count) if (col_index != table->column_count)
{ {
printf("Error: Column count mismatch for table '%s'.\n", table_name); 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; 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++; table->row_count++;
printf("Row inserted into table '%s'.\n", table_name); 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) void select_from_table(Database *db, const char *table_name)
{ {
Table *table; Table *table = find_table(db, table_name);
int i; if (table == NULL)
int j;
table = find_table(db, table_name);
if (!table)
{ {
printf("Error: Table '%s' does not exist.\n", table_name); printf("Error: Table '%s' does not exist.\n", table_name);
return; return;
} }
printf("Table: %s\n", table->name); printf("Table: %s\n", table->name);
for (int i = 0; i < table->column_count; i++)
for (i = 0; i < table->column_count; i++)
{ {
printf("%s\t", table->columns[i].name); printf("%s\t", table->columns[i]->name);
} }
printf("\n"); 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"); printf("\n");
} }
} }
/* Saves the database to a binary file.
*/
void save_database_to_file(Database *db, const char *filename) void save_database_to_file(Database *db, const char *filename)
{ {
FILE *file; FILE *file = fopen(filename, "wb");
if (file == NULL)
file = fopen(filename, "wb");
if (!file)
{ {
printf("Error: Could not open file '%s' for writing.\n", filename); printf("Error: Could not open file '%s' for writing.\n", filename);
return; 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); fclose(file);
printf("Database saved to '%s'.\n", filename); 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 *file = fopen(filename, "rb");
if (file == NULL)
file = fopen(filename, "rb");
if (!file)
{ {
printf("Error: Could not open file '%s' for reading.\n", filename); 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); fclose(file);
printf("Database loaded from '%s'.\n", filename); 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 query_copy[MAX_QUERY_LENGTH];
char *command; strncpy(query_copy, query, MAX_QUERY_LENGTH - 1);
char *table_name; query_copy[MAX_QUERY_LENGTH - 1] = '\0';
char *columns;
strcpy(query_copy, query); char *command = strtok(query_copy, " ");
command = strtok(query_copy, " "); if (command == NULL)
{
printf("Error: Empty query.\n");
return db;
}
if (strcmp(command, "CREATE") == 0) 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) if (next_token == NULL || strcmp(next_token, "TABLE") != 0)
{ {
printf("Error: Invalid CREATE TABLE syntax.\n"); printf("Error: Invalid CREATE TABLE syntax.\n");
return; return db;
} }
table_name = strtok(NULL, " "); char *table_name = strtok(NULL, " ");
if (table_name == NULL) if (table_name == NULL)
{ {
printf("Error: Table name is missing.\n"); printf("Error: Table name is missing.\n");
return; return db;
} }
columns = strchr(query, '('); char *columns = strchr(query, '(');
if (columns == NULL) if (columns == NULL)
{ {
printf("Error: Missing column definitions.\n"); printf("Error: Missing column definitions.\n");
return; return db;
} }
columns++; columns++;
char *closing_paren = strchr(columns, ')'); char *closing_paren = strchr(columns, ')');
if (closing_paren == NULL) if (closing_paren == NULL)
{ {
printf("Error: Missing closing parenthesis in column definitions.\n"); printf("Error: Missing closing parenthesis in column definitions.\n");
return; return db;
} }
*closing_paren = '\0'; *closing_paren = '\0';
trim_whitespace(columns);
columns = trim_whitespace(columns);
if (strlen(columns) == 0) if (strlen(columns) == 0)
{ {
printf("Error: No columns defined for table '%s'.\n", table_name); printf("Error: No columns defined for table '%s'.\n", table_name);
return; return db;
} }
create_table(db, table_name, columns); create_table(db, table_name, columns);
} }
else if (strcmp(command, "INSERT") == 0) 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) if (next_token == NULL || strcmp(next_token, "INTO") != 0)
{ {
printf("Error: Invalid INSERT INTO syntax.\n"); printf("Error: Invalid INSERT INTO syntax.\n");
return; return db;
} }
table_name = strtok(NULL, " "); char *table_name = strtok(NULL, " ");
if (table_name == NULL) if (table_name == NULL)
{ {
printf("Error: Table name is missing.\n"); printf("Error: Table name is missing.\n");
return; return db;
} }
columns = strchr(query, '('); char *values = strchr(query, '(');
if (columns == NULL) if (values == NULL)
{ {
printf("Error: Missing values.\n"); printf("Error: Missing values.\n");
return; return db;
} }
values++;
columns++; char *closing_paren = strchr(values, ')');
char *closing_paren = strchr(columns, ')');
if (closing_paren == NULL) if (closing_paren == NULL)
{ {
printf("Error: Missing closing parenthesis in values.\n"); printf("Error: Missing closing parenthesis in values.\n");
return; return db;
} }
*closing_paren = '\0'; *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); printf("Error: No values provided for table '%s'.\n", table_name);
return; return db;
} }
insert_into_table(db, table_name, values);
insert_into_table(db, table_name, columns);
} }
else if (strcmp(command, "SELECT") == 0) else if (strcmp(command, "SELECT") == 0)
{ {
strtok(NULL, " "); /* Expected syntax: SELECT * FROM table_name */
strtok(NULL, " "); strtok(NULL, " "); // Skip '*'
table_name = strtok(NULL, " "); strtok(NULL, " "); // Skip 'FROM'
char *table_name = strtok(NULL, " ");
if (table_name == NULL) if (table_name == NULL)
{ {
printf("Error: Table name is missing in SELECT query.\n"); printf("Error: Table name is missing in SELECT query.\n");
return; return db;
} }
select_from_table(db, table_name); select_from_table(db, table_name);
} }
else if (strcmp(command, "SAVE") == 0) else if (strcmp(command, "SAVE") == 0)
@ -382,10 +583,12 @@ void parse_query(Database *db, const char *query)
} }
else if (strcmp(command, "LOAD") == 0) else if (strcmp(command, "LOAD") == 0)
{ {
load_database_from_file(db, DB_FILE); db = load_database_from_file(db, DB_FILE);
} }
else else
{ {
printf("Error: Unsupported query.\n"); printf("Error: Unsupported query.\n");
} }
}
return db;
}

View File

@ -1,15 +1,14 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "linenoise.h" #include "linenoise.h"
#include "db.h" #include "db.h"
int main() int main(void)
{ {
char *query; char *query;
Database *db; Database *db;
db = create_db(); db = create_db();
printf("Simple SQL-like Database\n"); printf("Simple SQL-like Database\n");
@ -34,12 +33,14 @@ int main()
if (strlen(query) > 0) if (strlen(query) > 0)
{ {
parse_query(db, query); db = parse_query(db, query);
linenoiseHistoryAdd(query); linenoiseHistoryAdd(query);
} }
free(query); free(query);
} }
free_database(db);
return 0; return 0;
} }