├── config-example.json ├── include ├── connection_data.h ├── put_handler.h ├── post_handler.h ├── get_handler.h ├── delete_handler.h ├── kdtree.h ├── compare_handler.h └── vector_database.h ├── LICENSE ├── test └── add_vectors.sh ├── Makefile ├── src ├── delete_handler.c ├── kdtree.c ├── get_handler.c ├── put_handler.c ├── vector_database.c ├── main.c ├── post_handler.c └── compare_handler.c └── README.md /config-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "DB_FILENAME": "vector_database.db", 3 | "DEFAULT_PORT": 8888, 4 | "DEFAULT_KD_TREE_DIMENSION": 3, 5 | "DB_VECTOR_SIZE": 120 6 | } -------------------------------------------------------------------------------- /include/connection_data.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_DATA_H 2 | #define CONNECTION_DATA_H 3 | /** 4 | * @struct ConnectionData 5 | * @brief Structure to hold connection data. 6 | */ 7 | typedef struct ConnectionData { 8 | char *data; ///< Pointer to data buffer 9 | size_t data_size; ///< Size of the data buffer 10 | } ConnectionData; 11 | 12 | #endif // CONNECTION_DATA_H 13 | -------------------------------------------------------------------------------- /include/put_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef PUT_HANDLER_H 2 | #define PUT_HANDLER_H 3 | 4 | #include 5 | 6 | #include "vector_database.h" 7 | 8 | /** 9 | * @brief Handles PUT requests. 10 | * 11 | * @param cls User-defined data, in this case, the database. 12 | * @param connection MHD_Connection object representing the connection. 13 | * @param url URL of the request. 14 | * @param method HTTP method (should be "PUT"). 15 | * @param version HTTP version. 16 | * @param upload_data Data being uploaded in the request. 17 | * @param upload_data_size Size of the upload data. 18 | * @param con_cls Connection-specific data. 19 | * @return MHD_Result indicating the success or failure of the operation. 20 | */ 21 | extern enum MHD_Result put_handler(void *cls, struct MHD_Connection *connection, 22 | const char *url, const char *method, 23 | const char *version, const char *upload_data, 24 | size_t *upload_data_size, void **con_cls); 25 | 26 | #endif // PUT_HANDLER_H 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ppgranger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/post_handler.h: -------------------------------------------------------------------------------- 1 | // post_handler.h 2 | 3 | #ifndef POST_HANDLER_H 4 | #define POST_HANDLER_H 5 | 6 | #include "vector_database.h" 7 | 8 | /** 9 | * @struct PostHandlerData 10 | * @brief Structure to hold data for the POST handler 11 | */ 12 | typedef struct { 13 | VectorDatabase* db; 14 | size_t db_vector_size; 15 | } PostHandlerData; 16 | 17 | /** 18 | * @brief Function to handle POST requests. 19 | * 20 | * @param cls User-defined data, in this case, the database. 21 | * @param connection MHD_Connection object. 22 | * @param url URL of the request. 23 | * @param method HTTP method (should be "POST"). 24 | * @param version HTTP version. 25 | * @param upload_data Data being uploaded in the request. 26 | * @param upload_data_size Size of the upload data. 27 | * @param con_cls Connection-specific data. 28 | * @return MHD_Result indicating the success or failure of the operation. 29 | */ 30 | enum MHD_Result post_handler(void* cls, struct MHD_Connection* connection, 31 | const char* url, const char* method, 32 | const char* version, const char* upload_data, 33 | size_t* upload_data_size, void** con_cls); 34 | 35 | #endif // POST_HANDLER_H 36 | -------------------------------------------------------------------------------- /include/get_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef GET_HANDLER_H 2 | #define GET_HANDLER_H 3 | 4 | #include 5 | 6 | #include "vector_database.h" 7 | 8 | /** 9 | * @struct GetHandlerData 10 | * @brief Structure to hold data for the GET handler. 11 | */ 12 | typedef struct GetHandlerData { 13 | VectorDatabase* db; /**< Pointer to the vector database */ 14 | } GetHandlerData; 15 | 16 | /** 17 | * @brief Handles GET requests. 18 | * 19 | * @param cls User-defined data, in this case, the database. 20 | * @param connection MHD_Connection object. 21 | * @param url URL of the request. 22 | * @param method HTTP method (should be "GET"). 23 | * @param version HTTP version. 24 | * @param upload_data Data being uploaded in the request (should be empty for GET requests). 25 | * @param upload_data_size Size of the upload data. 26 | * @param con_cls Connection-specific data. 27 | * @return MHD_Result indicating the success or failure of the operation. 28 | */ 29 | extern enum MHD_Result get_handler(void *cls, struct MHD_Connection *connection, 30 | const char *url, const char *method, 31 | const char *version, const char *upload_data, 32 | size_t *upload_data_size, void **con_cls); 33 | 34 | #endif // GET_HANDLER_H 35 | -------------------------------------------------------------------------------- /include/delete_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef DELETE_HANDLER_H 2 | #define DELETE_HANDLER_H 3 | 4 | #include 5 | 6 | #include "vector_database.h" 7 | 8 | /** 9 | * @struct DeleteHandlerData 10 | * @brief Structure to hold data for the DELETE handler. 11 | */ 12 | typedef struct DeleteHandlerData { 13 | VectorDatabase* db; /**< Pointer to the vector database */ 14 | } DeleteHandlerData; 15 | 16 | /** 17 | * @brief Handles DELETE requests. 18 | * 19 | * @param cls User-defined data, in this case, the database. 20 | * @param connection MHD_Connection object. 21 | * @param url URL of the request. 22 | * @param method HTTP method (should be "DELETE"). 23 | * @param version HTTP version. 24 | * @param upload_data Data being uploaded in the request (should be empty for DELETE requests). 25 | * @param upload_data_size Size of the upload data. 26 | * @param con_cls Connection-specific data. 27 | * @return MHD_Result indicating the success or failure of the operation. 28 | */ 29 | extern enum MHD_Result delete_handler(void *cls, struct MHD_Connection *connection, 30 | const char *url, const char *method, 31 | const char *version, const char *upload_data, 32 | size_t *upload_data_size, void **con_cls); 33 | 34 | #endif // DELETE_HANDLER_H 35 | -------------------------------------------------------------------------------- /test/add_vectors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Seed the random number generator only once 4 | RANDOM=$$$(date +%s) 5 | 6 | for i in $(seq 1 100000); do 7 | # Generate a UUID for each vector 8 | uuid=$(uuidgen) 9 | 10 | # Vector size (set to 128 for all vectors) 11 | vector_size=128 12 | vector="[" 13 | for ((j=0; j<$vector_size; j++)); do 14 | # Generate a new integer part for each element 15 | integer_part=$(awk -v min=1 -v max=9 -v seed=$RANDOM 'BEGIN{srand(seed); printf "%d", int(min+rand()*(max-min+1))}') 16 | decimal_places=$((RANDOM % 7)) 17 | decimal_part="" 18 | for ((k=0; k<$decimal_places; k++)); do 19 | decimal_part="${decimal_part}$((RANDOM % 10))" 20 | done 21 | 22 | # Add decimal point only if there are decimal places 23 | if (( decimal_places > 0 )); then 24 | random_number="${integer_part}.${decimal_part}" 25 | else 26 | random_number="${integer_part}" 27 | fi 28 | 29 | vector="${vector}${random_number}" 30 | if ((j < $vector_size - 1)); then 31 | vector="${vector}, " 32 | fi 33 | done 34 | vector="${vector}]" 35 | 36 | # Construct JSON payload with UUID and vector 37 | json_payload=$(printf '{"uuid": "%s", "vector": %s}' "$uuid" "$vector") 38 | 39 | # Send POST request with JSON payload 40 | curl -X POST -H "Content-Type: application/json" -d "${json_payload}" http://localhost:8888/vector 41 | done 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Define the compiler and the flags 2 | CC = gcc 3 | CFLAGS = -Wall -I/opt/homebrew/include -I./include -pthread 4 | # For debug add -g -fsanitize=address 5 | # lldb ./executable/vector_db_server 6 | # breakpoint set -n malloc_error_break 7 | # run 8 | # bt 9 | LDFLAGS = -L/opt/homebrew/lib -lmicrohttpd -lcjson -pthread 10 | 11 | # Define the target executable and directory 12 | TARGET_DIR = executable 13 | TARGET = $(TARGET_DIR)/vector_db_server 14 | 15 | # Define the source files 16 | SRCS = src/vector_database.c src/get_handler.c src/post_handler.c src/put_handler.c src/delete_handler.c src/compare_handler.c src/main.c src/kdtree.c 17 | 18 | # Define the object files with directory prefix 19 | OBJS = $(addprefix $(TARGET_DIR)/, $(notdir $(SRCS:.c=.o))) 20 | 21 | # Default rule to build the target 22 | all: $(TARGET) 23 | 24 | # Rule to create the target directory if it does not exist 25 | $(TARGET_DIR): 26 | mkdir -p $(TARGET_DIR) 27 | 28 | # Rule to build the target executable from object files 29 | $(TARGET): $(OBJS) | $(TARGET_DIR) 30 | $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LDFLAGS) 31 | 32 | # Rule to compile source files into object files in the target directory 33 | $(TARGET_DIR)/%.o: src/%.c | $(TARGET_DIR) 34 | $(CC) $(CFLAGS) -c $< -o $@ 35 | 36 | # Clean up only the object files 37 | clean: 38 | rm -f $(OBJS) 39 | 40 | # Clean up all generated files (object files and executable) 41 | clean-all: 42 | rm -f $(OBJS) $(TARGET) 43 | 44 | .PHONY: all clean clean-all 45 | -------------------------------------------------------------------------------- /include/kdtree.h: -------------------------------------------------------------------------------- 1 | #ifndef KDTREE_H 2 | #define KDTREE_H 3 | 4 | #include 5 | 6 | /** 7 | * @struct KDTreeNode 8 | * @brief Structure representing a KD-tree node. 9 | */ 10 | typedef struct KDTreeNode { 11 | double *point; /**< Point in the k-dimensional space */ 12 | size_t index; /**< Index of the point in the original dataset */ 13 | struct KDTreeNode *left; /**< Left child node */ 14 | struct KDTreeNode *right; /**< Right child node */ 15 | } KDTreeNode; 16 | 17 | /** 18 | * @struct KDTree 19 | * @brief Structure representing a KD-tree. 20 | */ 21 | typedef struct KDTree { 22 | KDTreeNode *root; /**< Root node of the KD-tree */ 23 | size_t dimension; /**< Dimensionality of the points */ 24 | } KDTree; 25 | 26 | /** 27 | * @brief Create a new KD-tree. 28 | * 29 | * @param dimension Dimensionality of the points. 30 | * @return Pointer to the newly created KD-tree. 31 | */ 32 | KDTree* kdtree_create(size_t dimension); 33 | 34 | /** 35 | * @brief Insert a point into the KD-tree. 36 | * 37 | * @param tree KD-tree into which the point is to be inserted. 38 | * @param point Point to be inserted. 39 | * @param index Index of the point in the original dataset. 40 | */ 41 | void kdtree_insert(KDTree* tree, const double* point, size_t index); 42 | 43 | /** 44 | * @brief Free the memory allocated for the KD-tree. 45 | * 46 | * @param tree KD-tree to be freed. 47 | */ 48 | void kdtree_free(KDTree* tree); 49 | 50 | /** 51 | * @brief Find the nearest neighbor in the KD-tree. 52 | * 53 | * @param tree KD-tree to search in. 54 | * @param point Point to find the nearest neighbor for. 55 | * @return Index of the nearest neighbor. 56 | */ 57 | size_t kdtree_nearest(KDTree *tree, const double *point); 58 | 59 | #endif // KDTREE_H 60 | -------------------------------------------------------------------------------- /include/compare_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPARE_HANDLER_H 2 | #define COMPARE_HANDLER_H 3 | 4 | #include 5 | 6 | #include "vector_database.h" 7 | 8 | /** 9 | * @brief Handles comparison requests (e.g., cosine similarity, Euclidean distance, dot product). 10 | * 11 | * @param cls User-defined data, in this case, the database. 12 | * @param connection MHD_Connection object. 13 | * @param url URL of the request. 14 | * @param method HTTP method. 15 | * @param version HTTP version. 16 | * @param upload_data Data being uploaded in the request. 17 | * @param upload_data_size Size of the upload data. 18 | * @param con_cls Connection-specific data. 19 | * @return MHD_Result indicating the success or failure of the operation. 20 | */ 21 | enum MHD_Result compare_handler(void* cls, struct MHD_Connection* connection, 22 | const char* url, const char* method, 23 | const char* version, const char* upload_data, 24 | size_t* upload_data_size, void** con_cls); 25 | 26 | /** 27 | * @brief Handles nearest neighbor search requests. 28 | * 29 | * @param cls User-defined data, in this case, the database. 30 | * @param connection MHD_Connection object. 31 | * @param url URL of the request. 32 | * @param method HTTP method. 33 | * @param version HTTP version. 34 | * @param upload_data Data being uploaded in the request. 35 | * @param upload_data_size Size of the upload data. 36 | * @param con_cls Connection-specific data. 37 | * @return MHD_Result indicating the success or failure of the operation. 38 | */ 39 | enum MHD_Result nearest_handler(void* cls, struct MHD_Connection* connection, 40 | const char* url, const char* method, 41 | const char* version, const char* upload_data, 42 | size_t* upload_data_size, void** con_cls); 43 | 44 | #endif // COMPARE_HANDLER_H 45 | -------------------------------------------------------------------------------- /include/vector_database.h: -------------------------------------------------------------------------------- 1 | #ifndef VECTOR_DATABASE_H 2 | #define VECTOR_DATABASE_H 3 | 4 | #include 5 | #include 6 | #include "kdtree.h" 7 | 8 | #define UUID_SIZE 37 // UUID Size(36 chars + 1 for '\0') 9 | 10 | /** 11 | * @struct Vector 12 | * @brief Represents a vector with its data. 13 | */ 14 | typedef struct Vector { 15 | char uuid[UUID_SIZE]; /**< UUID of the vector */ 16 | size_t dimension; /**< Dimension of the vector */ 17 | double* data; /**< Array of vector data */ 18 | } Vector; 19 | 20 | /** 21 | * @struct VectorDatabase 22 | * @brief Represents a database of vectors with dynamic resizing and KD-Tree for efficient search. 23 | */ 24 | typedef struct VectorDatabase { 25 | Vector* vectors; /**< Array of vectors */ 26 | size_t size; /**< Current size of the vector array */ 27 | size_t capacity; /**< Current capacity of the vector array */ 28 | KDTree* kdtree; /**< KD-Tree for efficient search operations */ 29 | pthread_mutex_t mutex; // Add mutex to protect shared resources 30 | } VectorDatabase; 31 | 32 | /** 33 | * @brief Initializes a new vector database. 34 | * 35 | * @param initial_capacity Initial capacity of the vector array. 36 | * @param dimension Dimension of the vectors and KD-Tree. 37 | * @return Pointer to the initialized VectorDatabase structure. 38 | */ 39 | VectorDatabase* vector_db_init(size_t initial_capacity, size_t dimension); 40 | 41 | /** 42 | * @brief Frees the memory allocated for the vector database. 43 | * 44 | * @param db Pointer to the VectorDatabase structure to be freed. 45 | */ 46 | void vector_db_free(VectorDatabase* db); 47 | 48 | /** 49 | * @brief Inserts a vector into the database. 50 | * 51 | * @param db Pointer to the VectorDatabase structure. 52 | * @param vec Vector to be inserted. 53 | * @return Index of the inserted vector or -1 on failure. 54 | */ 55 | size_t vector_db_insert(VectorDatabase* db, Vector vec); 56 | 57 | /** 58 | * @brief Reads a vector from the database. 59 | * 60 | * @param db Pointer to the VectorDatabase structure. 61 | * @param index Index of the vector to be read. 62 | * @return Pointer to the vector at the specified index or NULL if the index is out of bounds. 63 | */ 64 | Vector* vector_db_read(VectorDatabase* db, size_t index); 65 | 66 | /** 67 | * @brief Reads a vector from the database, by uuid. 68 | * 69 | * @param db Pointer to the VectorDatabase structure. 70 | * @param uuid UUID of the vector to be read. 71 | * @return Pointer to the vector at the specified index or NULL if the index is out of bounds. 72 | */ 73 | Vector* vector_db_read_by_uuid(VectorDatabase* db, const char* uuid); 74 | 75 | 76 | /** 77 | * @brief Updates a vector in the database. 78 | * 79 | * @param db Pointer to the VectorDatabase structure. 80 | * @param index Index of the vector to be updated. 81 | * @param vec New vector data. 82 | */ 83 | void vector_db_update(VectorDatabase* db, size_t index, Vector vec); 84 | 85 | /** 86 | * @brief Deletes a vector from the database. 87 | * 88 | * @param db Pointer to the VectorDatabase structure. 89 | * @param index Index of the vector to be deleted. 90 | */ 91 | void vector_db_delete(VectorDatabase* db, size_t index); 92 | 93 | /** 94 | * @brief Saves the database to a file. 95 | * 96 | * @param db Pointer to the VectorDatabase structure. 97 | * @param filename Name of the file to save the database to. 98 | */ 99 | void vector_db_save(VectorDatabase* db, const char* filename); 100 | 101 | /** 102 | * @brief Loads the database from a file. 103 | * 104 | * @param filename Name of the file to load the database from. 105 | * @param dimension Dimension of the vectors and KD-Tree. 106 | * @return Pointer to the loaded VectorDatabase structure. 107 | */ 108 | VectorDatabase* vector_db_load(const char* filename, size_t dimension); 109 | 110 | /** 111 | * @brief Calculates the cosine similarity between two vectors. 112 | * 113 | * @param vec1 First vector. 114 | * @param vec2 Second vector. 115 | * @return Cosine similarity value. 116 | */ 117 | float cosine_similarity(Vector vec1, Vector vec2); 118 | 119 | /** 120 | * @brief Calculates the Euclidean distance between two vectors. 121 | * 122 | * @param vec1 First vector. 123 | * @param vec2 Second vector. 124 | * @return Euclidean distance value. 125 | */ 126 | float euclidean_distance(Vector vec1, Vector vec2); 127 | 128 | /** 129 | * @brief Calculates the dot product of two vectors. 130 | * 131 | * @param vec1 First vector. 132 | * @param vec2 Second vector. 133 | * @return Dot product value. 134 | */ 135 | float dot_product(Vector vec1, Vector vec2); 136 | 137 | #endif // VECTOR_DATABASE_H 138 | -------------------------------------------------------------------------------- /src/delete_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "../include/vector_database.h" 8 | #include "../include/delete_handler.h" 9 | 10 | /** 11 | * @brief Callback function to handle DELETE request data. 12 | * 13 | * @param cls User-defined data, in this case, the vector database. 14 | * @param connection Pointer to MHD_Connection object. 15 | * @param url URL of the request. 16 | * @param method HTTP method (should be "DELETE"). 17 | * @param version HTTP version. 18 | * @param upload_data Data being uploaded in the request. 19 | * @param upload_data_size Size of the upload data. 20 | * @param con_cls Connection-specific data. 21 | * @return MHD_Result indicating the success or failure of the operation. 22 | */ 23 | static enum MHD_Result delete_handler_callback(void* cls, struct MHD_Connection* connection, 24 | const char* url, const char* method, 25 | const char* version, const char* upload_data, 26 | size_t* upload_data_size, void** con_cls); 27 | 28 | /** 29 | * @brief Function to handle DELETE requests. 30 | * 31 | * @param cls User-defined data, in this case, the vector database. 32 | * @param connection Pointer to MHD_Connection object. 33 | * @param url URL of the request. 34 | * @param method HTTP method (should be "DELETE"). 35 | * @param version HTTP version. 36 | * @param upload_data Data being uploaded in the request. 37 | * @param upload_data_size Size of the upload data. 38 | * @param con_cls Connection-specific data. 39 | * @return MHD_Result indicating the success or failure of the operation. 40 | */ 41 | enum MHD_Result delete_handler(void* cls, struct MHD_Connection* connection, 42 | const char* url, const char* method, 43 | const char* version, const char* upload_data, 44 | size_t* upload_data_size, void** con_cls) { 45 | struct DeleteHandlerData* handler_data = (struct DeleteHandlerData*)cls; 46 | return delete_handler_callback(handler_data->db, connection, url, method, version, 47 | upload_data, upload_data_size, con_cls); 48 | } 49 | 50 | /** 51 | * @brief Callback function to handle DELETE request data. 52 | * 53 | * @param cls User-defined data, in this case, the vector database. 54 | * @param connection Pointer to MHD_Connection object. 55 | * @param url URL of the request. 56 | * @param method HTTP method (should be "DELETE"). 57 | * @param version HTTP version. 58 | * @param upload_data Data being uploaded in the request. 59 | * @param upload_data_size Size of the upload data. 60 | * @param con_cls Connection-specific data. 61 | * @return MHD_Result indicating the success or failure of the operation. 62 | */ 63 | static enum MHD_Result delete_handler_callback(void* cls, struct MHD_Connection* connection, 64 | const char* url, const char* method, 65 | const char* version, const char* upload_data, 66 | size_t* upload_data_size, void** con_cls) { 67 | VectorDatabase* db = (VectorDatabase*)cls; 68 | 69 | // Retrieve the 'index' query parameter 70 | const char* index_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "index"); 71 | if (!index_str) { 72 | // Respond with an error if 'index' is missing 73 | const char* error_msg = "Missing 'index' query parameter"; 74 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 75 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 76 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain"); 77 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 78 | MHD_destroy_response(response); 79 | return ret == MHD_YES ? MHD_YES : MHD_NO; 80 | } 81 | 82 | // Convert the 'index' query parameter to a size_t value 83 | size_t index = atoi(index_str); 84 | if (index >= db->size) { 85 | // Respond with an error if the index is out of bounds 86 | const char* error_msg = "Index out of bounds"; 87 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 88 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 89 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain"); 90 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 91 | MHD_destroy_response(response); 92 | return ret == MHD_YES ? MHD_YES : MHD_NO; 93 | } 94 | 95 | // Delete the vector from the database 96 | vector_db_delete(db, index); 97 | 98 | // Respond with an empty response to indicate success 99 | struct MHD_Response* response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); 100 | int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); 101 | MHD_destroy_response(response); 102 | return ret == MHD_YES ? MHD_YES : MHD_NO; 103 | } 104 | -------------------------------------------------------------------------------- /src/kdtree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../include/kdtree.h" 6 | 7 | /** 8 | * @brief Create a new KD-tree node. 9 | * 10 | * @param point Point in the k-dimensional space. 11 | * @param index Index of the point in the original dataset. 12 | * @param dimension Dimensionality of the points. 13 | * @return Pointer to the newly created KD-tree node. 14 | */ 15 | KDTreeNode* kdtree_create_node(const double *point, size_t index, size_t dimension) { 16 | printf("Creating KDTreeNode with index %zu and dimension %zu\n", index, dimension); 17 | KDTreeNode *node = (KDTreeNode*)malloc(sizeof(KDTreeNode)); 18 | if (!node) return NULL; 19 | 20 | node->point = (double*)malloc(dimension * sizeof(double)); 21 | if (!node->point) { 22 | free(node); 23 | return NULL; 24 | } 25 | 26 | for (size_t i = 0; i < dimension; i++) { 27 | node->point[i] = point[i]; 28 | } 29 | 30 | node->index = index; 31 | node->left = NULL; 32 | node->right = NULL; 33 | 34 | return node; 35 | } 36 | 37 | /** 38 | * @brief Insert a point into the KD-tree recursively. 39 | * 40 | * @param node Current node in the KD-tree. 41 | * @param point Point to be inserted. 42 | * @param index Index of the point in the original dataset. 43 | * @param depth Current depth in the KD-tree. 44 | * @param dimension Dimensionality of the points. 45 | * @return Pointer to the updated KD-tree node. 46 | */ 47 | KDTreeNode* kdtree_insert_rec(KDTreeNode *node, const double *point, size_t index, size_t depth, size_t dimension) { 48 | printf("Inserting recursively at depth %zu, dimension %zu\n", depth, dimension); 49 | if (!node) { 50 | return kdtree_create_node(point, index, dimension); 51 | } 52 | 53 | size_t cd = depth % dimension; 54 | 55 | if (point[cd] < node->point[cd]) { 56 | node->left = kdtree_insert_rec(node->left, point, index, depth + 1, dimension); 57 | } else { 58 | node->right = kdtree_insert_rec(node->right, point, index, depth + 1, dimension); 59 | } 60 | 61 | return node; 62 | } 63 | 64 | /** 65 | * @brief Create a new KD-tree. 66 | * 67 | * @param dimension Dimensionality of the points. 68 | * @return Pointer to the newly created KD-tree. 69 | */ 70 | KDTree* kdtree_create(size_t dimension) { 71 | KDTree *tree = (KDTree*)malloc(sizeof(KDTree)); 72 | if (!tree) return NULL; 73 | 74 | tree->root = NULL; 75 | tree->dimension = dimension; 76 | 77 | return tree; 78 | } 79 | 80 | /** 81 | * @brief Insert a point into the KD-tree. 82 | * 83 | * @param tree KD-tree into which the point is to be inserted. 84 | * @param point Point to be inserted. 85 | * @param index Index of the point in the original dataset. 86 | */ 87 | void kdtree_insert(KDTree *tree, const double *point, size_t index) { 88 | if (tree == NULL) return; 89 | printf("Inserting point into KDTree\n"); 90 | tree->root = kdtree_insert_rec(tree->root, point, index, 0, tree->dimension); 91 | } 92 | 93 | /** 94 | * @brief Free the memory allocated for the KD-tree nodes recursively. 95 | * 96 | * @param node Current node in the KD-tree. 97 | */ 98 | void kdtree_free_rec(KDTreeNode *node) { 99 | if (node) { 100 | kdtree_free_rec(node->left); 101 | kdtree_free_rec(node->right); 102 | free(node->point); 103 | free(node); 104 | } 105 | } 106 | 107 | /** 108 | * @brief Free the memory allocated for the KD-tree. 109 | * 110 | * @param tree KD-tree to be freed. 111 | */ 112 | void kdtree_free(KDTree *tree) { 113 | if (tree) { 114 | kdtree_free_rec(tree->root); 115 | tree->root = NULL; // Avoid dangling pointer 116 | free(tree); 117 | } 118 | } 119 | 120 | /** 121 | * @brief Find the nearest neighbor in the KD-tree recursively. 122 | * 123 | * @param node Current node in the KD-tree. 124 | * @param point Point to find the nearest neighbor for. 125 | * @param depth Current depth in the KD-tree. 126 | * @param dimension Dimensionality of the points. 127 | * @param best_node Best node found so far. 128 | * @param best_dist Best distance found so far. 129 | * @return Pointer to the best KD-tree node found so far. 130 | */ 131 | KDTreeNode* kdtree_nearest_rec(KDTreeNode *node, const double *point, size_t depth, size_t dimension, KDTreeNode *best_node, double *best_dist) { 132 | if (!node) return best_node; 133 | 134 | double d = 0; 135 | for (size_t i = 0; i < dimension; i++) { 136 | d += (node->point[i] - point[i]) * (node->point[i] - point[i]); 137 | } 138 | 139 | if (d < *best_dist) { 140 | *best_dist = d; 141 | best_node = node; 142 | } 143 | 144 | size_t cd = depth % dimension; 145 | KDTreeNode *next_node = NULL, *other_node = NULL; 146 | 147 | if (point[cd] < node->point[cd]) { 148 | next_node = node->left; 149 | other_node = node->right; 150 | } else { 151 | next_node = node->right; 152 | other_node = node->left; 153 | } 154 | 155 | best_node = kdtree_nearest_rec(next_node, point, depth + 1, dimension, best_node, best_dist); 156 | 157 | if ((point[cd] - node->point[cd]) * (point[cd] - node->point[cd]) < *best_dist) { 158 | best_node = kdtree_nearest_rec(other_node, point, depth + 1, dimension, best_node, best_dist); 159 | } 160 | 161 | return best_node; 162 | } 163 | 164 | /** 165 | * @brief Find the nearest neighbor in the KD-tree. 166 | * 167 | * @param tree KD-tree to search in. 168 | * @param point Point to find the nearest neighbor for. 169 | * @return Index of the nearest neighbor. 170 | */ 171 | size_t kdtree_nearest(KDTree *tree, const double *point) { 172 | if (tree == NULL || tree->root == NULL) { 173 | return (size_t)-1; 174 | } 175 | double best_dist = INFINITY; 176 | KDTreeNode *best_node = kdtree_nearest_rec(tree->root, point, 0, tree->dimension, NULL, &best_dist); 177 | return best_node ? best_node->index : (size_t)-1; 178 | } 179 | -------------------------------------------------------------------------------- /src/get_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include "cjson/cJSON.h" 7 | 8 | #include "../include/vector_database.h" 9 | #include "../include/get_handler.h" 10 | 11 | 12 | /** 13 | * @brief Callback function to handle GET requests. 14 | * 15 | * @param cls User-defined data, in this case, the database pointer. 16 | * @param connection MHD_Connection object representing the connection. 17 | * @param url URL of the request. 18 | * @param method HTTP method (should be "GET"). 19 | * @param version HTTP version. 20 | * @param upload_data Data being uploaded in the request (not used here). 21 | * @param upload_data_size Size of the upload data (not used here). 22 | * @param con_cls Connection-specific data (not used here). 23 | * @return MHD_Result indicating the success or failure of the operation. 24 | */ 25 | static enum MHD_Result get_handler_callback(void* cls, struct MHD_Connection* connection, 26 | const char* url, const char* method, 27 | const char* version, 28 | const char* upload_data, 29 | size_t* upload_data_size, void** con_cls); 30 | 31 | /** 32 | * @brief Function to handle GET requests. 33 | * 34 | * @param cls User-defined data, in this case, the database pointer. 35 | * @param connection MHD_Connection object representing the connection. 36 | * @param url URL of the request. 37 | * @param method HTTP method (should be "GET"). 38 | * @param version HTTP version. 39 | * @param upload_data Data being uploaded in the request (not used here). 40 | * @param upload_data_size Size of the upload data (not used here). 41 | * @param con_cls Connection-specific data. 42 | * @return MHD_Result indicating the success or failure of the operation. 43 | */ 44 | enum MHD_Result get_handler(void* cls, struct MHD_Connection* connection, 45 | const char* url, const char* method, 46 | const char* version, 47 | const char* upload_data, 48 | size_t* upload_data_size, void** con_cls) { 49 | struct GetHandlerData* handler_data = (struct GetHandlerData*)cls; 50 | return get_handler_callback(handler_data->db, connection, url, method, version, 51 | upload_data, upload_data_size, con_cls); 52 | } 53 | 54 | /** 55 | * @brief Callback function to handle GET request data. 56 | * 57 | * @param cls User-defined data, in this case, the database pointer. 58 | * @param connection MHD_Connection object representing the connection. 59 | * @param url URL of the request. 60 | * @param method HTTP method (should be "GET"). 61 | * @param version HTTP version. 62 | * @param upload_data Data being uploaded in the request (not used here). 63 | * @param upload_data_size Size of the upload data (not used here). 64 | * @param con_cls Connection-specific data. 65 | * @return MHD_Result indicating the success or failure of the operation. 66 | */ 67 | static enum MHD_Result get_handler_callback(void* cls, struct MHD_Connection* connection, 68 | const char* url, const char* method, 69 | const char* version, 70 | const char* upload_data, 71 | size_t* upload_data_size, void** con_cls) { 72 | VectorDatabase* db = (VectorDatabase*)cls; 73 | if (!db) { 74 | fprintf(stderr, "Database pointer is NULL in handler callback\n"); 75 | return MHD_NO; 76 | } 77 | 78 | // Retrieve the 'index' and 'uuid' query parameters 79 | const char* index_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "index"); 80 | const char* uuid_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "uuid"); 81 | 82 | Vector* vec = NULL; 83 | size_t vec_index = 0; // Initialize index variable 84 | 85 | if (index_str) { 86 | // Handle request by index 87 | vec_index = (size_t)atoi(index_str); 88 | if (vec_index >= db->size) { 89 | // Respond with an error if the index is out of bounds 90 | const char* error_msg = "{\"error\": \"Index out of bounds\"}"; 91 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 92 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 93 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 94 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 95 | MHD_destroy_response(response); 96 | return ret == MHD_YES ? MHD_YES : MHD_NO; 97 | } 98 | vec = vector_db_read(db, vec_index); 99 | } else if (uuid_str) { 100 | // Handle request by UUID 101 | vec = vector_db_read_by_uuid(db, uuid_str); 102 | if (!vec) { 103 | // Respond with an error if the vector is not found 104 | const char* error_msg = "{\"error\": \"Vector not found\"}"; 105 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 106 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 107 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 108 | int ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); 109 | MHD_destroy_response(response); 110 | return ret == MHD_YES ? MHD_YES : MHD_NO; 111 | } 112 | // Find the index of the vector with the given UUID 113 | for (size_t i = 0; i < db->size; ++i) { 114 | if (strcmp(db->vectors[i].uuid, uuid_str) == 0) { 115 | vec_index = i; 116 | break; 117 | } 118 | } 119 | } else { 120 | // Respond with an error if neither 'index' nor 'uuid' is provided 121 | const char* error_msg = "{\"error\": \"Missing 'index' or 'uuid' query parameter\"}"; 122 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 123 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 124 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 125 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 126 | MHD_destroy_response(response); 127 | return ret == MHD_YES ? MHD_YES : MHD_NO; 128 | } 129 | 130 | if (!vec || !vec->data) { 131 | // Respond with an error if the vector data is invalid 132 | const char* error_msg = "{\"error\": \"Vector data is invalid\"}"; 133 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 134 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 135 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 136 | int ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); 137 | MHD_destroy_response(response); 138 | return ret == MHD_YES ? MHD_YES : MHD_NO; 139 | } 140 | 141 | // Prepare the JSON response 142 | cJSON* json_response = cJSON_CreateObject(); 143 | if (!json_response) { 144 | // Respond with an error if JSON creation fails 145 | const char* error_msg = "{\"error\": \"Failed to create JSON object\"}"; 146 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 147 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 148 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 149 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 150 | MHD_destroy_response(response); 151 | return ret == MHD_YES ? MHD_YES : MHD_NO; 152 | } 153 | 154 | // Convert the vector to a JSON array with precise float formatting 155 | cJSON* json_array = cJSON_CreateArray(); 156 | if (!json_array) { 157 | cJSON_Delete(json_response); 158 | const char* error_msg = "{\"error\": \"Failed to create JSON array\"}"; 159 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 160 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 161 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 162 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 163 | MHD_destroy_response(response); 164 | return ret == MHD_YES ? MHD_YES : MHD_NO; 165 | } 166 | 167 | // Add vector data to the JSON array 168 | for (size_t i = 0; i < vec->dimension; ++i) { 169 | cJSON_AddItemToArray(json_array, cJSON_CreateNumber(vec->data[i])); 170 | } 171 | 172 | // Add vector details to the JSON response 173 | cJSON_AddStringToObject(json_response, "uuid", vec->uuid); // Add UUID 174 | cJSON_AddNumberToObject(json_response, "index", vec_index); // Add index 175 | cJSON_AddItemToObject(json_response, "vector", json_array); 176 | 177 | // Convert JSON object to string 178 | char* response_str = cJSON_PrintUnformatted(json_response); 179 | cJSON_Delete(json_response); 180 | 181 | if (!response_str) { 182 | // Respond with an error if JSON string conversion fails 183 | const char* error_msg = "{\"error\": \"Failed to print JSON\"}"; 184 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 185 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 186 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 187 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 188 | MHD_destroy_response(response); 189 | return ret == MHD_YES ? MHD_YES : MHD_NO; 190 | } 191 | 192 | // Create and send the HTTP response 193 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(response_str), 194 | (void*)response_str, MHD_RESPMEM_MUST_FREE); 195 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 196 | int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); 197 | MHD_destroy_response(response); 198 | return ret == MHD_YES ? MHD_YES : MHD_NO; 199 | } 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Vector DB 2 | 3 | Simple Vector DB is a lightweight, efficient, and easy-to-use vector database designed to store, retrieve, and manage high-dimensional vectors. It supports operations such as insertion, update, deletion, and comparison of vectors using cosine similarity, Euclidean distance, and dot product. Additionally, it allows for finding the nearest vector based on KD-tree median points. 4 | 5 | ## Table of Contents 6 | 7 | - [Features](#features) 8 | - [Installation](#installation) 9 | - [Dependencies](#dependencies) 10 | - [macOS](#macos) 11 | - [Linux](#linux) 12 | - [Windows](#windows) 13 | - [Finding Library Paths](#finding-library-paths) 14 | - [Updating the Makefile](#updating-the-makefile) 15 | - [Usage](#usage) 16 | - [Starting the Server](#starting-the-server) 17 | - [Fill Database with Dummy vector](#fill-database-with-dummy-vector) 18 | - [API Endpoints](#api-endpoints) 19 | - [Insert a Vector](#insert-a-vector) 20 | - [Retrieve a Vector](#retrieve-a-vector) 21 | - [Update a Vector](#update-a-vector) 22 | - [Delete a Vector](#delete-a-vector) 23 | - [Compare Vectors](#compare-vectors) 24 | - [Find Nearest Vector](#find-nearest-vector) 25 | - [Build and Run](#build-and-run) 26 | - [Contributing](#contributing) 27 | - [License](#license) 28 | 29 | ## Features 30 | 31 | - **Efficient Vector Storage**: Stores high-dimensional vectors with dynamic allocation. 32 | - **Vector Operations**: Supports insertion, retrieval, update, and deletion of vectors. 33 | - **Comparison Metrics**: Compare vectors using cosine similarity, Euclidean distance, and dot product. 34 | - **Nearest Vector Search**: Find the nearest vector based on KD-tree median points for efficient indexing and improved performance. 35 | - **RESTful API**: Simple and intuitive API endpoints for easy integration. 36 | - **Persistent Storage**: Save and load vector databases from disk. 37 | 38 | ## Installation 39 | 40 | ### Dependencies 41 | 42 | Simple Vector DB depends on `libmicrohttpd` and `cJSON`. Here are the instructions to install these dependencies on different operating systems. 43 | 44 | #### macOS 45 | 46 | 1. **libmicrohttpd**: 47 | ```sh 48 | brew install libmicrohttpd 49 | ``` 50 | 2. **cJSON**: 51 | ```sh 52 | brew install cjson 53 | ``` 54 | 55 | #### Linux 56 | 57 | For Debian-based distributions (e.g., Ubuntu): 58 | 59 | 1. **libmicrohttpd**: 60 | ```sh 61 | sudo apt-get update 62 | sudo apt-get install libmicrohttpd-dev 63 | ``` 64 | 2. **cJSON**: 65 | ```sh 66 | sudo apt-get install libcjson-dev 67 | ``` 68 | 69 | For Red Hat-based distributions (e.g., CentOS, Fedora): 70 | 71 | 1. **libmicrohttpd**: 72 | ```sh 73 | sudo dnf install libmicrohttpd-devel 74 | ``` 75 | 2. **cJSON**: 76 | ```sh 77 | sudo dnf install cjson-devel 78 | ``` 79 | 80 | #### Windows 81 | 82 | For Windows, you can use a package manager like vcpkg to install the dependencies. 83 | 84 | 1. **Install vcpkg**: 85 | ```sh 86 | git clone https://github.com/microsoft/vcpkg.git 87 | cd vcpkg 88 | ./bootstrap-vcpkg.sh 89 | ``` 90 | 91 | 2. **libmicrohttpd**: 92 | ```sh 93 | ./vcpkg install libmicrohttpd 94 | ``` 95 | 96 | 3. **cJSON**: 97 | ```sh 98 | ./vcpkg install cjson 99 | ``` 100 | 101 | ### Finding Library Paths 102 | 103 | After installing the libraries, you need to find the paths to `libmicrohttpd.h` and `cjson/cjson.h`. 104 | 105 | 1. **macOS and Linux**: 106 | - Typically, headers are located in `/usr/include`, `/usr/local/include`, or the installation prefix of the package manager (e.g., `/opt/homebrew/include` for Homebrew on macOS). 107 | 108 | - Libraries are usually in `/usr/lib`, `/usr/local/lib`, or the package manager prefix (e.g., `/opt/homebrew/lib`). 109 | 110 | - Use the `find` command to locate the header files: 111 | ```sh 112 | find /usr -name "libmicrohttpd.h" 113 | find /usr -name "cjson.h" 114 | ``` 115 | 116 | 2. **Windows**: 117 | - vcpkg installs libraries in the `vcpkg/installed` directory. You can find headers and libraries in `vcpkg/installed/x64-windows/include` and `vcpkg/installed/x64-windows/lib`. 118 | 119 | ### Updating the Makefile 120 | 121 | Update your Makefile to include the correct paths for the headers and libraries. Below are the two lines that need to be updated: 122 | 123 | ```makefile 124 | CFLAGS = -Wall -I/opt/homebrew/include -I./include 125 | LDFLAGS = -L/opt/homebrew/lib -lmicrohttpd -lcjson 126 | ``` 127 | 128 | Replace `/opt/homebrew/include` and `/opt/homebrew/lib` with the appropriate paths for your system. 129 | 130 | ## Usage 131 | 132 | ### Starting the Server 133 | 134 | You can start the server on the default port (8888) or specify a custom port using the `-p` flag. Additionally, you can specify other parameters such as the database filename, kd-tree dimension, and vector size using the corresponding flags. Alternatively, you can use a configuration file with the `-c` flag. 135 | 136 | ```sh 137 | # Start the server with default settings 138 | ./executable/vector_db_server 139 | 140 | # Start the server on a custom port (e.g., 8080) 141 | ./executable/vector_db_server -p 8080 142 | 143 | # Start the server with a custom database filename 144 | ./executable/vector_db_server -f custom_database.db 145 | 146 | # Start the server with a custom kd-tree dimension 147 | ./executable/vector_db_server -d 5 148 | 149 | # Start the server with a custom vector size 150 | ./executable/vector_db_server -s 256 151 | 152 | # Start the server with a configuration file 153 | ./executable/vector_db_server -c config.json 154 | 155 | # Combine multiple custom settings 156 | ./executable/vector_db_server -p 8080 -f custom_database.db -d 5 -s 256 -c config.json 157 | ``` 158 | 159 | ### Sample Configuration File 160 | 161 | Save this as `config.json`: 162 | 163 | ```json 164 | { 165 | "DB_FILENAME": "vector_database.db", 166 | "DEFAULT_PORT": 8888, 167 | "DEFAULT_KD_TREE_DIMENSION": 3, 168 | "DB_VECTOR_SIZE": 128 169 | } 170 | ``` 171 | 172 | #### Explanation of the Configuration File: 173 | 174 | - `DB_FILENAME`: The name of the database file (e.g., `vector_database.db`). 175 | - `DEFAULT_PORT`: The port number on which the server will run (e.g., `8888`). 176 | - `DEFAULT_KD_TREE_DIMENSION`: The default dimension for the kd-tree (e.g., `3`). 177 | - `DB_VECTOR_SIZE`: The size of the database vectors (e.g., `128`). 178 | 179 | ### Fill Database with Dummy vector 180 | You can fill the database with different vectors of different dimensions. Randomly generated. 181 | ```sh 182 | # Change execution of the file 183 | chmod +x ./test/add_vectors.sh 184 | ./test/add_vectors.sh 185 | ``` 186 | 187 | ### API Endpoints 188 | 189 | #### Insert a Vector 190 | 191 | - **Endpoint**: `/vector` 192 | - **Method**: `POST` 193 | - **Request Body**: JSON array of float64 values. 194 | 195 | ```sh 196 | curl -X POST -H "Content-Type: application/json" -d '{"uuid": "123e4567-e89b-12d3-a456-426614174000", "vector": [1.23, 4.56, 7.89, 0.12, 3.45]}' http://localhost:8888/vector 197 | ``` 198 | 199 | UUID is considered as the bridge (shared key for a chunk) between your application database and the simple vector database. 200 | 201 | **Response**: 202 | 203 | ```json 204 | { 205 | "index": 2, 206 | "vector": [1.0, 2.0, 3.0, 4.08993, 5.937,6.389, 1.39], 207 | "uuid": F07243B9-58D1-4A33-9670-C14FFA9050EF, 208 | } 209 | ``` 210 | 211 | #### Retrieve a Vector 212 | 213 | - **Endpoint**: `/vector` 214 | - **Method**: `GET` 215 | - **Query Parameter**: `index` (the index of the vector to retrieve). 216 | - **Query Parameter**: `uuid` (the uuid of the vector to retrieve). 217 | 218 | ```sh 219 | curl "http://localhost:8888/vector?index=0" 220 | curl "http://localhost:8888/vector?uuid=0" 221 | ``` 222 | 223 | **Response**: 224 | 225 | ```json 226 | { 227 | "index": 2, 228 | "vector": [1.0, 2.0, 3.0, 4.08993, 5.937,6.389, 1.39], 229 | "uuid": F07243B9-58D1-4A33-9670-C14FFA9050EF, 230 | } 231 | ``` 232 | 233 | 234 | #### Update a Vector 235 | 236 | - **Endpoint**: `/vector` 237 | - **Method**: `PUT` 238 | - **Query Parameter**: `index` (the index of the vector to update). 239 | - **Request Body**: JSON array of float64 values. 240 | 241 | ```sh 242 | curl -X PUT -H "Content-Type: application/json" -d '[1.5, 2.5, 3.5, 4.5]' "http://localhost:8888/vector?index=0" 243 | ``` 244 | 245 | #### Delete a Vector 246 | 247 | - **Endpoint**: `/vector` 248 | - **Method**: `DELETE` 249 | - **Query Parameter**: `index` (the index of the vector to delete). 250 | 251 | ```sh 252 | curl -X DELETE "http://localhost:8888/vector?index=0" 253 | ``` 254 | 255 | #### Compare Vectors 256 | 257 | - **Endpoint**: `/compare/cosine_similarity` 258 | - **Method**: `GET` 259 | - **Query Parameters**: `index1` and `index2` (the indices of the vectors to compare). 260 | 261 | ```sh 262 | curl "http://localhost:8888/compare/cosine_similarity?index1=0&index2=1" 263 | ``` 264 | 265 | - **Endpoint**: `/compare/euclidean_distance` 266 | - **Method**: `GET` 267 | - **Query Parameters**: `index1` and `index2` (the indices of the vectors to compare). 268 | 269 | ```sh 270 | curl "http://localhost:8888/compare/euclidean_distance?index1=0&index2=1" 271 | ``` 272 | 273 | - **Endpoint**: `/compare/dot_product` 274 | - **Method**: `GET` 275 | - **Query Parameters**: `index1` and `index2` (the indices of the vectors to compare). 276 | 277 | ```sh 278 | curl "http://localhost:8888/compare/dot_product?index1=0&index2=1" 279 | ``` 280 | 281 | #### Find Nearest Vector 282 | 283 | - **Endpoint**: `/nearest` 284 | - **Method**: `POST` 285 | - **Content-Type**: `application/json` 286 | - **Request Body**: JSON array representing the input vector. 287 | - **Optional query parameter**: `number=(int)` The number of nearest vectors to return - default is 1. 288 | 289 | The `/nearest` endpoint uses a KD-tree for indexing, which allows for more efficient nearest neighbor searches. All vectors in the database must have the same dimension. During vector insertion, a point is added to the KD-tree, and during vector updates, the KD-tree is modified to reflect the changes. 290 | 291 | ```sh 292 | curl -X POST -H "Content-Type: application/json" -d '[7,3.00003,6.32,4.5,8,5,1.842,4.929066,7.94764,6.16051,6.946,4.71,4.3,1.704,2.321,5.9,6.74227,7.365,5.31,4.1705]' "http://localhost:8888/nearest" 293 | ``` 294 | 295 | **Response**: 296 | 297 | ```json 298 | { 299 | "index": 2, 300 | "vector": [1.0, 2.0, 3.0, 4.08993, 5.937,6.389, 1.39], 301 | "uuid": F07243B9-58D1-4A33-9670-C14FFA9050EF, 302 | } 303 | ``` 304 | 305 | This response indicates that the nearest vector is at index 2, and it includes the vector and its median point. 306 | 307 | ## Build and Run 308 | 309 | To build and run Simple Vector DB, execute the following commands: 310 | 311 | ```sh 312 | # Build the project 313 | make 314 | 315 | # Start the server 316 | ./executable/vector_db_server 317 | ``` 318 | 319 | To specify a custom port: 320 | 321 | ```sh 322 | ./executable/vector_db_server -p 8080 323 | ``` 324 | 325 | ## Contributing 326 | 327 | We welcome contributions to Simple Vector DB! Please fork the repository, create a new branch for your feature or bugfix, and submit a pull request. 328 | 329 | 1. Fork the repository. 330 | 2. Create your feature branch: `git checkout -b my-new-feature` 331 | 3. Commit your changes: `git commit -am 'Add some new feature'` 332 | 4. Push to the branch: `git push origin my-new-feature` 333 | 5. Submit a pull request. 334 | 335 | ## License 336 | 337 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /src/put_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include "cjson/cJSON.h" 7 | 8 | #include "../include/vector_database.h" 9 | #include "../include/connection_data.h" 10 | #include "../include/put_handler.h" 11 | 12 | /** 13 | * @struct PostHandlerData 14 | * @brief Structure to hold data for the POST handler 15 | */ 16 | typedef struct { 17 | VectorDatabase* db; 18 | size_t db_vector_size; 19 | } PostHandlerData; 20 | 21 | /** 22 | * @brief Callback function to handle the PUT request data. 23 | * 24 | * @param cls User-defined data, in this case, the handler data. 25 | * @param connection MHD_Connection object. 26 | * @param url URL of the request. 27 | * @param method HTTP method (should be "PUT"). 28 | * @param version HTTP version. 29 | * @param upload_data Data being uploaded in the request. 30 | * @param upload_data_size Size of the upload data. 31 | * @param con_cls Connection-specific data. 32 | * @return MHD_Result indicating the success or failure of the operation. 33 | */ 34 | static enum MHD_Result put_handler_callback(void* cls, struct MHD_Connection* connection, 35 | const char* url, const char* method, 36 | const char* version, const char* upload_data, 37 | size_t* upload_data_size, void** con_cls); 38 | 39 | /** 40 | * @brief Function to handle PUT requests. 41 | * 42 | * @param cls User-defined data, in this case, the handler data. 43 | * @param connection MHD_Connection object. 44 | * @param url URL of the request. 45 | * @param method HTTP method (should be "PUT"). 46 | * @param version HTTP version. 47 | * @param upload_data Data being uploaded in the request. 48 | * @param upload_data_size Size of the upload data. 49 | * @param con_cls Connection-specific data. 50 | * @return MHD_Result indicating the success or failure of the operation. 51 | */ 52 | enum MHD_Result put_handler(void* cls, struct MHD_Connection* connection, 53 | const char* url, const char* method, 54 | const char* version, const char* upload_data, 55 | size_t* upload_data_size, void** con_cls) { 56 | // Initialize connection data if it's the first call for this connection 57 | if (*con_cls == NULL) { 58 | ConnectionData *con_data = (ConnectionData *)malloc(sizeof(ConnectionData)); 59 | if (con_data == NULL) { 60 | return MHD_NO; 61 | } 62 | con_data->data = NULL; 63 | con_data->data_size = 0; 64 | *con_cls = (void *)con_data; 65 | return MHD_YES; 66 | } 67 | 68 | // Retrieve the handler data 69 | PostHandlerData* handler_data = (PostHandlerData*)cls; 70 | return put_handler_callback(handler_data, connection, url, method, version, 71 | upload_data, upload_data_size, con_cls); 72 | } 73 | 74 | /** 75 | * @brief Callback function to handle PUT request data. 76 | * 77 | * @param cls User-defined data, in this case, the handler data. 78 | * @param connection MHD_Connection object. 79 | * @param url URL of the request. 80 | * @param method HTTP method (should be "PUT"). 81 | * @param version HTTP version. 82 | * @param upload_data Data being uploaded in the request. 83 | * @param upload_data_size Size of the upload data. 84 | * @param con_cls Connection-specific data. 85 | * @return MHD_Result indicating the success or failure of the operation. 86 | */ 87 | static enum MHD_Result put_handler_callback(void* cls, struct MHD_Connection* connection, 88 | const char* url, const char* method, 89 | const char* version, const char* upload_data, 90 | size_t* upload_data_size, void** con_cls) { 91 | ConnectionData *con_data = (ConnectionData *)*con_cls; 92 | PostHandlerData* handler_data = (PostHandlerData*)cls; 93 | VectorDatabase* db = handler_data->db; 94 | size_t expected_vector_size = handler_data->db_vector_size; 95 | 96 | // Check if there's data to be uploaded 97 | if (*upload_data_size != 0) { 98 | // Reallocate memory to accommodate the new data 99 | char *new_data = (char *)realloc(con_data->data, con_data->data_size + *upload_data_size + 1); 100 | if (new_data == NULL) { 101 | free(con_data->data); 102 | free(con_data); 103 | *con_cls = NULL; 104 | return MHD_NO; 105 | } 106 | con_data->data = new_data; 107 | // Copy the upload data to the connection data buffer 108 | memcpy(con_data->data + con_data->data_size, upload_data, *upload_data_size); 109 | con_data->data_size += *upload_data_size; 110 | con_data->data[con_data->data_size] = '\0'; // Null-terminate the data 111 | *upload_data_size = 0; // Reset the upload data size 112 | return MHD_YES; 113 | } 114 | 115 | // Check if the connection data buffer is empty 116 | if (con_data->data_size == 0) { 117 | const char* error_msg = "{\"error\": \"Empty data\"}"; 118 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 119 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 120 | if (response == NULL) { 121 | free(con_data->data); 122 | free(con_data); 123 | *con_cls = NULL; 124 | return MHD_NO; 125 | } 126 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 127 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 128 | MHD_destroy_response(response); 129 | free(con_data->data); 130 | free(con_data); 131 | *con_cls = NULL; 132 | return ret == MHD_YES ? MHD_YES : MHD_NO; 133 | } 134 | 135 | // Retrieve the 'index' query parameter 136 | const char* index_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "index"); 137 | if (!index_str) { 138 | // Respond with an error if 'index' is missing 139 | const char* error_msg = "{\"error\": \"Missing 'index' query parameter\"}"; 140 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 141 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 142 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 143 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 144 | MHD_destroy_response(response); 145 | free(con_data->data); 146 | free(con_data); 147 | *con_cls = NULL; 148 | return ret == MHD_YES ? MHD_YES : MHD_NO; 149 | } 150 | 151 | // Convert the 'index' query parameter to a size_t value 152 | size_t index = atoi(index_str); 153 | 154 | // Check if the index is out of bounds 155 | if (index >= db->size) { 156 | const char* error_msg = "{\"error\": \"Index out of bounds\"}"; 157 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 158 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 159 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 160 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 161 | MHD_destroy_response(response); 162 | free(con_data->data); 163 | free(con_data); 164 | *con_cls = NULL; 165 | return ret == MHD_YES ? MHD_YES : MHD_NO; 166 | } 167 | 168 | printf("put_handler_callback: Parsing JSON data\n"); 169 | 170 | // Parse the JSON data from the request 171 | cJSON *json = cJSON_Parse(con_data->data); 172 | if (json == NULL) { 173 | const char* error_msg = "{\"error\": \"Invalid JSON\"}"; 174 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 175 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 176 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 177 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 178 | MHD_destroy_response(response); 179 | free(con_data->data); 180 | free(con_data); 181 | *con_cls = NULL; 182 | return ret == MHD_YES ? MHD_YES : MHD_NO; 183 | } 184 | 185 | printf("put_handler_callback: JSON parsed successfully\n"); 186 | 187 | // Get the dimension of the vector from the JSON array size 188 | size_t dimension = cJSON_GetArraySize(json); 189 | printf("put_handler_callback: Vector dimension: %zu\n", dimension); 190 | 191 | // Check if the vector size matches the expected size 192 | if (dimension != expected_vector_size) { 193 | const char* error_msg = "{\"error\": \"Vector size mismatch\"}"; 194 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 195 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 196 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 197 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 198 | MHD_destroy_response(response); 199 | cJSON_Delete(json); 200 | free(con_data->data); 201 | free(con_data); 202 | *con_cls = NULL; 203 | return ret == MHD_YES ? MHD_YES : MHD_NO; 204 | } 205 | 206 | Vector vec; 207 | vec.dimension = dimension; 208 | // Allocate memory for the vector data 209 | vec.data = (double*)malloc(dimension * sizeof(double)); 210 | if (!vec.data) { 211 | const char* error_msg = "{\"error\": \"Memory allocation failed\"}"; 212 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 213 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 214 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 215 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 216 | MHD_destroy_response(response); 217 | cJSON_Delete(json); 218 | free(con_data->data); 219 | free(con_data); 220 | *con_cls = NULL; 221 | return ret == MHD_YES ? MHD_YES : MHD_NO; 222 | } 223 | 224 | // Extract the vector data from the JSON array 225 | for (size_t i = 0; i < dimension; i++) { 226 | cJSON *item = cJSON_GetArrayItem(json, i); 227 | if (!cJSON_IsNumber(item)) { 228 | const char* error_msg = "{\"error\": \"Invalid vector data\"}"; 229 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 230 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 231 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 232 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 233 | MHD_destroy_response(response); 234 | cJSON_Delete(json); 235 | free(vec.data); 236 | free(con_data->data); 237 | free(con_data); 238 | *con_cls = NULL; 239 | return ret == MHD_YES ? MHD_YES : MHD_NO; 240 | } 241 | // Store the vector data 242 | vec.data[i] = item->valuedouble; 243 | } 244 | printf("put_handler_callback: Extracted vector data, dimension: %zu\n", dimension); 245 | 246 | // Update the vector in the database 247 | vector_db_update(db, index, vec); 248 | 249 | // Clean up JSON data and connection data 250 | cJSON_Delete(json); 251 | free(con_data->data); 252 | free(con_data); 253 | *con_cls = NULL; 254 | 255 | // Respond with an empty response to indicate success 256 | struct MHD_Response* response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); 257 | int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); 258 | MHD_destroy_response(response); 259 | return ret == MHD_YES ? MHD_YES : MHD_NO; 260 | } 261 | -------------------------------------------------------------------------------- /src/vector_database.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include // Include pthread library 6 | 7 | #include "../include/vector_database.h" 8 | #include "../include/kdtree.h" 9 | 10 | /** 11 | * @brief Initialize a vector database with a given initial capacity and dimension. 12 | * 13 | * @param initial_capacity The initial capacity of the database. 14 | * @param dimension The dimension of the vectors. 15 | * @return VectorDatabase* Pointer to the initialized vector database, or NULL on failure. 16 | */ 17 | VectorDatabase* vector_db_init(size_t initial_capacity, size_t dimension) { 18 | VectorDatabase* db = (VectorDatabase*)malloc(sizeof(VectorDatabase)); 19 | if (!db) { 20 | fprintf(stderr, "Failed to allocate memory for database\n"); 21 | return NULL; 22 | } 23 | 24 | db->size = 0; 25 | db->capacity = initial_capacity > 0 ? initial_capacity : 10; 26 | db->vectors = (Vector*)malloc(db->capacity * sizeof(Vector)); 27 | if (!db->vectors) { 28 | fprintf(stderr, "Failed to allocate memory for vectors\n"); 29 | free(db); 30 | return NULL; 31 | } 32 | 33 | db->kdtree = kdtree_create(dimension); 34 | if (!db->kdtree) { 35 | fprintf(stderr, "Failed to create KDTree\n"); 36 | free(db->vectors); 37 | free(db); 38 | return NULL; 39 | } else { 40 | printf("KDTree initialized\n"); 41 | } 42 | 43 | // Initialize the mutex 44 | if (pthread_mutex_init(&db->mutex, NULL) != 0) { 45 | fprintf(stderr, "Failed to initialize mutex\n"); 46 | kdtree_free(db->kdtree); 47 | free(db->vectors); 48 | free(db); 49 | return NULL; 50 | } 51 | 52 | printf("Database initialized with capacity: %zu\n", db->capacity); 53 | return db; 54 | } 55 | 56 | /** 57 | * @brief Free the memory allocated for the vector database. 58 | * 59 | * @param db Pointer to the vector database. 60 | */ 61 | void vector_db_free(VectorDatabase* db) { 62 | if (db) { 63 | for (size_t i = 0; i < db->size; ++i) { 64 | free(db->vectors[i].data); 65 | } 66 | kdtree_free(db->kdtree); 67 | free(db->vectors); 68 | // Destroy the mutex 69 | pthread_mutex_destroy(&db->mutex); 70 | free(db); 71 | } 72 | } 73 | 74 | /** 75 | * @brief Insert a vector into the vector database. 76 | * 77 | * @param db Pointer to the vector database. 78 | * @param vec The vector to insert. 79 | * @return size_t The index of the inserted vector, or (size_t)-1 on failure. 80 | */ 81 | size_t vector_db_insert(VectorDatabase* db, Vector vec) { 82 | pthread_mutex_lock(&db->mutex); // Lock the mutex 83 | 84 | printf("Inserting vector, current size: %zu, current capacity: %zu\n", db->size, db->capacity); 85 | if (db->size >= db->capacity) { 86 | size_t new_capacity = db->capacity > SIZE_MAX / 2 ? SIZE_MAX : db->capacity * 2; 87 | printf("Current capacity: %zu\n", db->capacity); 88 | printf("Requested new capacity: %zu\n", new_capacity); 89 | printf("Maximum size_t value: %zu\n", SIZE_MAX); 90 | if (new_capacity <= db->capacity || new_capacity > SIZE_MAX / sizeof(Vector)) { 91 | fprintf(stderr, "Capacity overflow detected, unable to allocate more memory for vectors\n"); 92 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 93 | return (size_t)-1; 94 | } 95 | Vector* new_vectors = (Vector*)realloc(db->vectors, new_capacity * sizeof(Vector)); 96 | if (!new_vectors) { 97 | fprintf(stderr, "Failed to allocate more memory for vectors\n"); 98 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 99 | return (size_t)-1; 100 | } 101 | db->vectors = new_vectors; 102 | db->capacity = new_capacity; 103 | } 104 | 105 | strncpy(db->vectors[db->size].uuid, vec.uuid, UUID_SIZE - 1); 106 | db->vectors[db->size].uuid[UUID_SIZE - 1] = '\0'; 107 | 108 | if (!db->kdtree) { 109 | fprintf(stderr, "KDTree is NULL before inserting\n"); 110 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 111 | return (size_t)-1; 112 | } 113 | db->vectors[db->size] = vec; 114 | kdtree_insert(db->kdtree, vec.data, db->size); 115 | size_t index = db->size++; 116 | 117 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 118 | return index; 119 | } 120 | 121 | /** 122 | * @brief Read a vector from the vector database at a given index. 123 | * 124 | * @param db Pointer to the vector database. 125 | * @param index The index of the vector to read. 126 | * @return Vector* Pointer to the vector, or NULL if the index is out of range. 127 | */ 128 | Vector* vector_db_read(VectorDatabase* db, size_t index) { 129 | pthread_mutex_lock(&db->mutex); // Lock the mutex 130 | Vector* vec = NULL; 131 | if (index < db->size) { 132 | vec = &db->vectors[index]; 133 | } 134 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 135 | return vec; 136 | } 137 | 138 | /** 139 | * @brief Read a vector from the vector database at a given UUID. 140 | * 141 | * @param db Pointer to the vector database. 142 | * @param uuid The UUID of the vector to read. 143 | * @return Vector* Pointer to the vector, or NULL if the index is out of range. 144 | */ 145 | Vector* vector_db_read_by_uuid(VectorDatabase* db, const char* uuid) { 146 | pthread_mutex_lock(&db->mutex); // Lock the mutex 147 | 148 | Vector* vec = NULL; 149 | for (size_t i = 0; i < db->size; ++i) { 150 | if (strncmp(db->vectors[i].uuid, uuid, UUID_SIZE) == 0) { 151 | vec = &db->vectors[i]; 152 | break; 153 | } 154 | } 155 | 156 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 157 | return vec; 158 | } 159 | 160 | 161 | 162 | /** 163 | * @brief Update a vector in the vector database at a given index. 164 | * 165 | * @param db Pointer to the vector database. 166 | * @param index The index of the vector to update. 167 | * @param vec The new vector data. 168 | */ 169 | void vector_db_update(VectorDatabase* db, size_t index, Vector vec) { 170 | pthread_mutex_lock(&db->mutex); // Lock the mutex 171 | if (index < db->size) { 172 | free(db->vectors[index].data); 173 | db->vectors[index] = vec; 174 | kdtree_insert(db->kdtree, vec.data, index); 175 | } 176 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 177 | } 178 | 179 | /** 180 | * @brief Delete a vector from the vector database at a given index. 181 | * 182 | * @param db Pointer to the vector database. 183 | * @param index The index of the vector to delete. 184 | */ 185 | void vector_db_delete(VectorDatabase* db, size_t index) { 186 | pthread_mutex_lock(&db->mutex); // Lock the mutex 187 | if (index < db->size) { 188 | free(db->vectors[index].data); 189 | for (size_t i = index; i < db->size - 1; ++i) { 190 | db->vectors[i] = db->vectors[i + 1]; 191 | } 192 | db->size--; 193 | } 194 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 195 | } 196 | 197 | /** 198 | * @brief Save the vector database to a file. 199 | * 200 | * @param db Pointer to the vector database. 201 | * @param filename The name of the file to save the database to. 202 | */ 203 | void vector_db_save(VectorDatabase* db, const char* filename) { 204 | pthread_mutex_lock(&db->mutex); // Lock the mutex 205 | FILE* file = fopen(filename, "wb"); 206 | if (!file) { 207 | perror("Failed to open file for writing"); 208 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 209 | return; 210 | } 211 | 212 | printf("Saving database of size %zu\n", db->size); 213 | fwrite(&db->size, sizeof(size_t), 1, file); 214 | for (size_t i = 0; i < db->size; ++i) { 215 | if (db->vectors[i].dimension == 0 || db->vectors[i].data == NULL) { 216 | fprintf(stderr, "Invalid vector at index %zu, skipping\n", i); 217 | continue; 218 | } 219 | printf("Saving vector at index %zu with dimension %zu\n", i, db->vectors[i].dimension); 220 | fwrite(db->vectors[i].uuid, sizeof(char), 37, file); // Assuming UUID is stored as a 36-char string + NULL terminator 221 | fwrite(&db->vectors[i].dimension, sizeof(size_t), 1, file); 222 | fwrite(db->vectors[i].data, sizeof(double), db->vectors[i].dimension, file); 223 | } 224 | 225 | fclose(file); 226 | pthread_mutex_unlock(&db->mutex); // Unlock the mutex 227 | printf("Database saved to %s\n", filename); 228 | } 229 | 230 | /** 231 | * @brief Load a vector database from a file. 232 | * 233 | * @param filename The name of the file to load the database from. 234 | * @param dimension The dimension of the vectors. 235 | * @return VectorDatabase* Pointer to the loaded vector database, or NULL on failure. 236 | */ 237 | VectorDatabase* vector_db_load(const char* filename, size_t dimension) { 238 | FILE* file = fopen(filename, "rb"); 239 | if (!file) { 240 | perror("Failed to open file for reading"); 241 | return NULL; 242 | } 243 | VectorDatabase* db = (VectorDatabase*)malloc(sizeof(VectorDatabase)); 244 | if (!db) { 245 | fprintf(stderr, "Failed to allocate memory for database\n"); 246 | fclose(file); 247 | return NULL; 248 | } 249 | fread(&db->size, sizeof(size_t), 1, file); 250 | db->capacity = db->size > 0 ? db->size : 10; 251 | db->vectors = (Vector*)malloc(db->capacity * sizeof(Vector)); 252 | if (!db->vectors) { 253 | fprintf(stderr, "Failed to allocate memory for vectors\n"); 254 | free(db); 255 | fclose(file); 256 | return NULL; 257 | } 258 | for (size_t i = 0; i < db->size; ++i) { 259 | fread(db->vectors[i].uuid, sizeof(char), 37, file); // Assuming UUID is stored as a 36-char string + NULL terminator 260 | fread(&db->vectors[i].dimension, sizeof(size_t), 1, file); 261 | db->vectors[i].data = (double*)malloc(db->vectors[i].dimension * sizeof(double)); 262 | if (!db->vectors[i].data) { 263 | fprintf(stderr, "Failed to allocate memory for vector data\n"); 264 | vector_db_free(db); 265 | fclose(file); 266 | return NULL; 267 | } 268 | fread(db->vectors[i].data, sizeof(double), db->vectors[i].dimension, file); 269 | } 270 | db->kdtree = kdtree_create(dimension); 271 | if (!db->kdtree) { 272 | fprintf(stderr, "Failed to create KDTree\n"); 273 | vector_db_free(db); 274 | fclose(file); 275 | return NULL; 276 | } 277 | for (size_t i = 0; i < db->size; ++i) { 278 | kdtree_insert(db->kdtree, db->vectors[i].data, i); 279 | } 280 | 281 | // Initialize the mutex 282 | if (pthread_mutex_init(&db->mutex, NULL) != 0) { 283 | fprintf(stderr, "Failed to initialize mutex\n"); 284 | vector_db_free(db); 285 | fclose(file); 286 | return NULL; 287 | } 288 | 289 | fclose(file); 290 | printf("Database loaded with size: %zu, capacity: %zu\n", db->size, db->capacity); 291 | return db; 292 | } 293 | 294 | /** 295 | * @brief Calculate the cosine similarity between two vectors. 296 | * 297 | * @param vec1 The first vector. 298 | * @param vec2 The second vector. 299 | * @return float The cosine similarity, or -1.0 if the dimensions do not match. 300 | */ 301 | float cosine_similarity(Vector vec1, Vector vec2) { 302 | if (vec1.dimension != vec2.dimension) { 303 | fprintf(stderr, "Vectors have different dimensions\n"); 304 | return -1.0; 305 | } 306 | float dot_product = 0.0, norm_a = 0.0, norm_b = 0.0; 307 | for (size_t i = 0; i < vec1.dimension; i++) { 308 | dot_product += vec1.data[i] * vec2.data[i]; 309 | norm_a += vec1.data[i] * vec1.data[i]; 310 | norm_b += vec2.data[i] * vec2.data[i]; 311 | } 312 | return dot_product / (sqrt(norm_a) * sqrt(norm_b)); 313 | } 314 | 315 | /** 316 | * @brief Calculate the Euclidean distance between two vectors. 317 | * 318 | * @param vec1 The first vector. 319 | * @param vec2 The second vector. 320 | * @return float The Euclidean distance, or -1.0 if the dimensions do not match. 321 | */ 322 | float euclidean_distance(Vector vec1, Vector vec2) { 323 | if (vec1.dimension != vec2.dimension) { 324 | fprintf(stderr, "Vectors have different dimensions\n"); 325 | return -1.0; 326 | } 327 | float sum = 0.0; 328 | for (size_t i = 0; i < vec1.dimension; i++) { 329 | float diff = vec1.data[i] - vec2.data[i]; 330 | sum += diff * diff; 331 | } 332 | return sqrt(sum); 333 | } 334 | 335 | /** 336 | * @brief Calculate the dot product of two vectors. 337 | * 338 | * @param vec1 The first vector. 339 | * @param vec2 The second vector. 340 | * @return float The dot product, or -1.0 if the dimensions do not match. 341 | */ 342 | float dot_product(Vector vec1, Vector vec2) { 343 | if (vec1.dimension != vec2.dimension) { 344 | fprintf(stderr, "Vectors have different dimensions\n"); 345 | return -1.0; 346 | } 347 | float result = 0.0; 348 | for (size_t i = 0; i < vec1.dimension; i++) { 349 | result += vec1.data[i] * vec2.data[i]; 350 | } 351 | return result; 352 | } 353 | 354 | /** 355 | * @brief Compare two double values (for use with qsort). 356 | * 357 | * @param a Pointer to the first double value. 358 | * @param b Pointer to the second double value. 359 | * @return int -1 if the first value is less, 1 if the first value is greater, 0 if they are equal. 360 | */ 361 | int compare(const void* a, const void* b) { 362 | double arg1 = *(const double*)a; 363 | double arg2 = *(const double*)b; 364 | if (arg1 < arg2) return -1; 365 | if (arg1 > arg2) return 1; 366 | return 0; 367 | } 368 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "../include/vector_database.h" 11 | #include "../include/get_handler.h" 12 | #include "../include/post_handler.h" 13 | #include "../include/put_handler.h" 14 | #include "../include/delete_handler.h" 15 | #include "../include/compare_handler.h" 16 | 17 | #define DEFAULT_PORT 8888 18 | #define DEFAULT_DB_FILENAME "vector_database.db" 19 | #define DEFAULT_KD_TREE_DIMENSION 3 20 | #define DEFAULT_DB_VECTOR_SIZE 128 21 | #define DEFAULT_CONFIG_FILENAME "config.json" 22 | 23 | /** 24 | * @struct Config 25 | * @brief Config file informations such as filename, listening port, kd_tree dimension deep and db_vector_size 26 | */ 27 | typedef struct Config { 28 | char *db_filename; 29 | int port; 30 | size_t kd_tree_dimension; 31 | size_t db_vector_size; 32 | } Config; 33 | 34 | Config config = {DEFAULT_DB_FILENAME, DEFAULT_PORT, DEFAULT_KD_TREE_DIMENSION, DEFAULT_DB_VECTOR_SIZE}; 35 | 36 | /** 37 | * @brief Load the configuration from a JSON file. 38 | * 39 | * @param filename The path to the configuration file. 40 | * @param config The config structure to populate. 41 | */ 42 | void load_config(const char *filename, Config *config) { 43 | FILE *file = fopen(filename, "r"); 44 | if (!file) { 45 | fprintf(stderr, "Config file not found, using default or command-line values\n"); 46 | return; // Use default or command-line values if config file not found 47 | } 48 | 49 | fseek(file, 0, SEEK_END); 50 | long length = ftell(file); 51 | fseek(file, 0, SEEK_SET); 52 | char *data = (char *)malloc(length + 1); 53 | fread(data, 1, length, file); 54 | fclose(file); 55 | data[length] = '\0'; 56 | 57 | cJSON *json = cJSON_Parse(data); 58 | if (!json) { 59 | fprintf(stderr, "Error parsing config file: %s\n", cJSON_GetErrorPtr()); 60 | free(data); 61 | exit(EXIT_FAILURE); 62 | } 63 | 64 | cJSON *db_filename = cJSON_GetObjectItem(json, "DB_FILENAME"); 65 | if (cJSON_IsString(db_filename)) { 66 | config->db_filename = strdup(db_filename->valuestring); 67 | } 68 | 69 | cJSON *port = cJSON_GetObjectItem(json, "DEFAULT_PORT"); 70 | if (cJSON_IsNumber(port)) { 71 | config->port = port->valueint; 72 | } 73 | 74 | cJSON *dimension = cJSON_GetObjectItem(json, "DEFAULT_KD_TREE_DIMENSION"); 75 | if (cJSON_IsNumber(dimension)) { 76 | config->kd_tree_dimension = (size_t)dimension->valueint; 77 | } 78 | 79 | cJSON *db_vector_size = cJSON_GetObjectItem(json, "DB_VECTOR_SIZE"); 80 | if (cJSON_IsNumber(db_vector_size)) { 81 | config->db_vector_size = (size_t)db_vector_size->valueint; 82 | } 83 | 84 | cJSON_Delete(json); 85 | free(data); 86 | } 87 | 88 | /** 89 | * @struct ConnectionData 90 | * @brief Structure to hold connection data. 91 | */ 92 | typedef struct ConnectionData { 93 | char *data; ///< Pointer to data buffer 94 | size_t data_size; ///< Size of the data buffer 95 | } ConnectionData; 96 | 97 | /** 98 | * @brief Callback function called when a request is completed. 99 | * 100 | * @param cls User-defined data. 101 | * @param connection The connection object. 102 | * @param con_cls Connection-specific data. 103 | * @param toe Termination code. 104 | */ 105 | static void request_completed_callback(void* cls, struct MHD_Connection* connection, 106 | void** con_cls, enum MHD_RequestTerminationCode toe); 107 | 108 | /** 109 | * @brief Handler function for GET requests. 110 | * 111 | * @param cls User-defined data. 112 | * @param connection The connection object. 113 | * @param url The URL of the request. 114 | * @param method The HTTP method. 115 | * @param version The HTTP version. 116 | * @param upload_data Data being uploaded in the request. 117 | * @param upload_data_size Size of the upload data. 118 | * @param con_cls Connection-specific data. 119 | * @return MHD_Result indicating the success or failure of the operation. 120 | */ 121 | static enum MHD_Result ahc_get(void *cls, struct MHD_Connection *connection, 122 | const char *url, const char *method, 123 | const char *version, const char *upload_data, 124 | size_t *upload_data_size, void **con_cls) { 125 | return get_handler(cls, connection, url, method, version, upload_data, upload_data_size, con_cls); 126 | } 127 | 128 | /** 129 | * @brief Handler function for POST requests. 130 | * 131 | * @param cls User-defined data. 132 | * @param connection The connection object. 133 | * @param url The URL of the request. 134 | * @param method The HTTP method. 135 | * @param version The HTTP version. 136 | * @param upload_data Data being uploaded in the request. 137 | * @param upload_data_size Size of the upload data. 138 | * @param con_cls Connection-specific data. 139 | * @return MHD_Result indicating the success or failure of the operation. 140 | */ 141 | static enum MHD_Result ahc_post(void *cls, struct MHD_Connection *connection, 142 | const char *url, const char *method, 143 | const char *version, const char *upload_data, 144 | size_t *upload_data_size, void **con_cls) { 145 | return post_handler(cls, connection, url, method, version, upload_data, upload_data_size, con_cls); 146 | } 147 | 148 | /** 149 | * @brief Handler function for PUT requests. 150 | * 151 | * @param cls User-defined data. 152 | * @param connection The connection object. 153 | * @param url The URL of the request. 154 | * @param method The HTTP method. 155 | * @param version The HTTP version. 156 | * @param upload_data Data being uploaded in the request. 157 | * @param upload_data_size Size of the upload data. 158 | * @param con_cls Connection-specific data. 159 | * @return MHD_Result indicating the success or failure of the operation. 160 | */ 161 | static enum MHD_Result ahc_put(void *cls, struct MHD_Connection *connection, 162 | const char *url, const char *method, 163 | const char *version, const char *upload_data, 164 | size_t *upload_data_size, void **con_cls) { 165 | return put_handler(cls, connection, url, method, version, upload_data, upload_data_size, con_cls); 166 | } 167 | 168 | /** 169 | * @brief Handler function for DELETE requests. 170 | * 171 | * @param cls User-defined data. 172 | * @param connection The connection object. 173 | * @param url The URL of the request. 174 | * @param method The HTTP method. 175 | * @param version The HTTP version. 176 | * @param upload_data Data being uploaded in the request. 177 | * @param upload_data_size Size of the upload data. 178 | * @param con_cls Connection-specific data. 179 | * @return MHD_Result indicating the success or failure of the operation. 180 | */ 181 | static enum MHD_Result ahc_delete(void *cls, struct MHD_Connection* connection, 182 | const char *url, const char *method, 183 | const char *version, const char *upload_data, 184 | size_t *upload_data_size, void **con_cls) { 185 | return delete_handler(cls, connection, url, method, version, upload_data, upload_data_size, con_cls); 186 | } 187 | 188 | /** 189 | * @brief Handler function for compare requests. 190 | * 191 | * @param cls User-defined data. 192 | * @param connection The connection object. 193 | * @param url The URL of the request. 194 | * @param method The HTTP method. 195 | * @param version The HTTP version. 196 | * @param upload_data Data being uploaded in the request. 197 | * @param upload_data_size Size of the upload data. 198 | * @param con_cls Connection-specific data. 199 | * @return MHD_Result indicating the success or failure of the operation. 200 | */ 201 | static enum MHD_Result ahc_compare(void *cls, struct MHD_Connection *connection, 202 | const char *url, const char *method, 203 | const char *version, const char *upload_data, 204 | size_t *upload_data_size, void **con_cls) { 205 | return compare_handler(cls, connection, url, method, version, upload_data, upload_data_size, con_cls); 206 | } 207 | 208 | /** 209 | * @brief Handler function for nearest neighbor search requests. 210 | * 211 | * @param cls User-defined data. 212 | * @param connection The connection object. 213 | * @param url The URL of the request. 214 | * @param method The HTTP method. 215 | * @param version The HTTP version. 216 | * @param upload_data Data being uploaded in the request. 217 | * @param upload_data_size Size of the upload data. 218 | * @param con_cls Connection-specific data. 219 | * @return MHD_Result indicating the success or failure of the operation. 220 | */ 221 | static enum MHD_Result ahc_nearest(void *cls, struct MHD_Connection *connection, 222 | const char *url, const char *method, 223 | const char *version, const char *upload_data, 224 | size_t *upload_data_size, void **con_cls) { 225 | return nearest_handler(cls, connection, url, method, version, upload_data, upload_data_size, con_cls); 226 | } 227 | 228 | /** 229 | * @brief Main access handler function to route HTTP requests. 230 | * 231 | * @param cls User-defined data. 232 | * @param connection The connection object. 233 | * @param url The URL of the request. 234 | * @param method The HTTP method. 235 | * @param version The HTTP version. 236 | * @param upload_data Data being uploaded in the request. 237 | * @param upload_data_size Size of the upload data. 238 | * @param con_cls Connection-specific data. 239 | * @return MHD_Result indicating the success or failure of the operation. 240 | */ 241 | static enum MHD_Result access_handler(void *cls, struct MHD_Connection *connection, 242 | const char *url, const char *method, 243 | const char *version, const char *upload_data, 244 | size_t *upload_data_size, void **con_cls) { 245 | PostHandlerData* handler_data = (PostHandlerData*)cls; 246 | 247 | // Handle GET requests 248 | if (strcmp(method, "GET") == 0) { 249 | if (strcmp(url, "/vector") == 0) { 250 | return ahc_get(handler_data, connection, url, method, version, upload_data, upload_data_size, con_cls); 251 | } else if (strcmp(url, "/compare/cosine_similarity") == 0 || strcmp(url, "/compare/euclidean_distance") == 0 || strcmp(url, "/compare/dot_product") == 0) { 252 | return ahc_compare(handler_data, connection, url, method, version, upload_data, upload_data_size, con_cls); 253 | } 254 | } 255 | // Handle POST requests 256 | else if (strcmp(method, "POST") == 0) { 257 | if (strcmp(url, "/vector") == 0) { 258 | return ahc_post(handler_data, connection, url, method, version, upload_data, upload_data_size, con_cls); 259 | } else if (strcmp(url, "/nearest") == 0) { 260 | return ahc_nearest(handler_data, connection, url, method, version, upload_data, upload_data_size, con_cls); 261 | } 262 | } 263 | // Handle PUT requests 264 | else if (strcmp(method, "PUT") == 0 && strcmp(url, "/vector") == 0) { 265 | return ahc_put(handler_data, connection, url, method, version, upload_data, upload_data_size, con_cls); 266 | } 267 | // Handle DELETE requests 268 | else if (strcmp(method, "DELETE") == 0 && strcmp(url, "/vector") == 0) { 269 | return ahc_delete(handler_data, connection, url, method, version, upload_data, upload_data_size, con_cls); 270 | } 271 | 272 | // Return 404 Not Found for unrecognized URLs 273 | const char *error_msg = "404 Not Found"; 274 | struct MHD_Response *response = MHD_create_response_from_buffer(strlen(error_msg), (void *)error_msg, MHD_RESPMEM_PERSISTENT); 275 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain"); 276 | int ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); 277 | MHD_destroy_response(response); 278 | return ret == MHD_YES ? MHD_YES : MHD_NO; 279 | } 280 | 281 | /** 282 | * @brief Callback function called when a request is completed. 283 | * 284 | * @param cls User-defined data. 285 | * @param connection The connection object. 286 | * @param con_cls Connection-specific data. 287 | * @param toe Termination code. 288 | */ 289 | static void request_completed_callback(void* cls, struct MHD_Connection* connection, 290 | void** con_cls, enum MHD_RequestTerminationCode toe) { 291 | ConnectionData *con_data = (ConnectionData*)*con_cls; 292 | if (con_data != NULL) { 293 | free(con_data->data); 294 | free(con_data); 295 | *con_cls = NULL; 296 | } 297 | } 298 | 299 | /** 300 | * @brief Main function to start the server and handle incoming requests. 301 | * 302 | * @param argc Argument count. 303 | * @param argv Argument vector. 304 | * @return int Status code. 305 | */ 306 | int main(int argc, char* argv[]) { 307 | char *config_path = NULL; 308 | int port = DEFAULT_PORT; 309 | size_t kd_tree_dimension = DEFAULT_KD_TREE_DIMENSION; 310 | size_t db_vector_size = DEFAULT_DB_VECTOR_SIZE; 311 | char *db_filename = DEFAULT_DB_FILENAME; 312 | 313 | // Parse command-line arguments for port and dimension 314 | int opt; 315 | while ((opt = getopt(argc, argv, "p:d:s:f:c:")) != -1) { 316 | switch (opt) { 317 | case 'p': 318 | port = atoi(optarg); 319 | break; 320 | case 'd': 321 | kd_tree_dimension = (size_t)atoi(optarg); 322 | break; 323 | case 's': 324 | db_vector_size = (size_t)atoi(optarg); 325 | break; 326 | case 'f': 327 | db_filename = optarg; 328 | break; 329 | case 'c': 330 | config_path = optarg; 331 | break; 332 | default: 333 | fprintf(stderr, "Usage: %s [-p port] [-d dimension] [-s vector_size] [-f db_filename] [-c config]\n", argv[0]); 334 | exit(EXIT_FAILURE); 335 | } 336 | } 337 | 338 | // If a config path is provided, load the configuration from the file 339 | if (config_path) { 340 | load_config(config_path, &config); 341 | } else { 342 | // Use command-line arguments or defaults 343 | config.port = port; 344 | config.kd_tree_dimension = kd_tree_dimension; 345 | config.db_vector_size = db_vector_size; 346 | config.db_filename = db_filename; 347 | } 348 | 349 | VectorDatabase *db = vector_db_load(config.db_filename, config.kd_tree_dimension); 350 | if (db == NULL) { 351 | db = vector_db_init(0, config.kd_tree_dimension); 352 | if (!db) { 353 | fprintf(stderr, "Failed to initialize vector database\n"); 354 | return 1; 355 | } 356 | } 357 | 358 | PostHandlerData handler_data; 359 | handler_data.db = db; 360 | handler_data.db_vector_size = config.db_vector_size; 361 | 362 | // Test initialization and reading of vectors 363 | for (size_t i = 0; i < db->size; i++) { 364 | Vector *vec = vector_db_read(db, i); 365 | if (vec) { 366 | printf("Read vector at index %zu: (", i); 367 | for (size_t j = 0; j < vec->dimension; j++) { 368 | printf("%f", vec->data[j]); 369 | if (j < vec->dimension - 1) { 370 | printf(", "); 371 | } 372 | } 373 | printf(")\n"); 374 | } else { 375 | printf("Failed to read vector at index %zu\n", i); 376 | } 377 | } 378 | 379 | struct MHD_Daemon *daemon; 380 | 381 | // Start the HTTP daemon 382 | daemon = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_THREAD_PER_CONNECTION, config.port, NULL, NULL, 383 | &access_handler, &handler_data, 384 | MHD_OPTION_NOTIFY_COMPLETED, request_completed_callback, NULL, 385 | MHD_OPTION_END); 386 | if (!daemon) { 387 | fprintf(stderr, "Failed to start server\n"); 388 | vector_db_free(db); 389 | return 1; 390 | } 391 | 392 | printf("Server running on port %d\n", config.port); 393 | 394 | // Wait for user input to terminate the server 395 | getchar(); 396 | 397 | // Save the database to file before shutting down 398 | vector_db_save(db, config.db_filename); 399 | 400 | // Stop the HTTP daemon and free the database 401 | MHD_stop_daemon(daemon); 402 | vector_db_free(db); 403 | 404 | return 0; 405 | } 406 | -------------------------------------------------------------------------------- /src/post_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include "cjson/cJSON.h" 7 | 8 | #include "../include/vector_database.h" 9 | #include "../include/connection_data.h" 10 | #include "../include/post_handler.h" 11 | 12 | 13 | /** 14 | * @brief Callback function to handle the POST request data. 15 | * 16 | * @param cls User-defined data, in this case, the database. 17 | * @param connection MHD_Connection object. 18 | * @param url URL of the request. 19 | * @param method HTTP method (should be "POST"). 20 | * @param version HTTP version. 21 | * @param upload_data Data being uploaded in the request. 22 | * @param upload_data_size Size of the upload data. 23 | * @param con_cls Connection-specific data. 24 | * @return MHD_Result indicating the success or failure of the operation. 25 | */ 26 | static enum MHD_Result post_handler_callback(void* cls, struct MHD_Connection* connection, 27 | const char* url, const char* method, 28 | const char* version, const char* upload_data, 29 | size_t* upload_data_size, void** con_cls); 30 | 31 | /** 32 | * @brief Function to handle POST requests. 33 | * 34 | * @param cls User-defined data, in this case, the database. 35 | * @param connection MHD_Connection object. 36 | * @param url URL of the request. 37 | * @param method HTTP method (should be "POST"). 38 | * @param version HTTP version. 39 | * @param upload_data Data being uploaded in the request. 40 | * @param upload_data_size Size of the upload data. 41 | * @param con_cls Connection-specific data. 42 | * @return MHD_Result indicating the success or failure of the operation. 43 | */ 44 | enum MHD_Result post_handler(void* cls, struct MHD_Connection* connection, 45 | const char* url, const char* method, 46 | const char* version, const char* upload_data, 47 | size_t* upload_data_size, void** con_cls) { 48 | printf("post_handler: Entered\n"); 49 | 50 | // Initialize connection data if it's the first call for this connection 51 | if (*con_cls == NULL) { 52 | ConnectionData *con_data = (ConnectionData *)malloc(sizeof(ConnectionData)); 53 | if (con_data == NULL) { 54 | fprintf(stderr, "post_handler: Failed to allocate memory for ConnectionData\n"); 55 | return MHD_NO; 56 | } 57 | con_data->data = NULL; 58 | con_data->data_size = 0; 59 | *con_cls = (void *)con_data; 60 | printf("post_handler: Initialized ConnectionData\n"); 61 | return MHD_YES; 62 | } 63 | 64 | // Retrieve the handler data 65 | PostHandlerData* handler_data = (PostHandlerData*)cls; 66 | printf("post_handler: Retrieved handler_data\n"); 67 | return post_handler_callback(handler_data, connection, url, method, version, 68 | upload_data, upload_data_size, con_cls); 69 | } 70 | 71 | /** 72 | * @brief Callback function to handle POST request data. 73 | * 74 | * @param cls User-defined data, in this case, the database. 75 | * @param connection MHD_Connection object. 76 | * @param url URL of the request. 77 | * @param method HTTP method (should be "POST"). 78 | * @param version HTTP version. 79 | * @param upload_data Data being uploaded in the request. 80 | * @param upload_data_size Size of the upload data. 81 | * @param con_cls Connection-specific data. 82 | * @return MHD_Result indicating the success or failure of the operation. 83 | */ 84 | static enum MHD_Result post_handler_callback(void* cls, struct MHD_Connection* connection, 85 | const char* url, const char* method, 86 | const char* version, const char* upload_data, 87 | size_t* upload_data_size, void** con_cls) { 88 | printf("post_handler_callback: Entered\n"); 89 | 90 | ConnectionData *con_data = (ConnectionData *)*con_cls; 91 | PostHandlerData* handler_data = (PostHandlerData*)cls; 92 | VectorDatabase* db = handler_data->db; 93 | size_t expected_vector_size = handler_data->db_vector_size; 94 | 95 | if (!db) { 96 | fprintf(stderr, "post_handler_callback: VectorDatabase is NULL\n"); 97 | return MHD_NO; 98 | } 99 | 100 | if (*upload_data_size != 0) { 101 | char *new_data = (char *)realloc(con_data->data, con_data->data_size + *upload_data_size + 1); 102 | if (new_data == NULL) { 103 | fprintf(stderr, "post_handler_callback: Failed to reallocate memory for data\n"); 104 | free(con_data->data); 105 | free(con_data); 106 | *con_cls = NULL; 107 | return MHD_NO; 108 | } 109 | con_data->data = new_data; 110 | memcpy(con_data->data + con_data->data_size, upload_data, *upload_data_size); 111 | con_data->data_size += *upload_data_size; 112 | con_data->data[con_data->data_size] = '\0'; // Null-terminate the data 113 | *upload_data_size = 0; 114 | return MHD_YES; 115 | } 116 | 117 | if (con_data->data_size == 0) { 118 | fprintf(stderr, "post_handler_callback: Empty data received\n"); 119 | const char* error_msg = "{\"error\": \"Empty data\"}"; 120 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 121 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 122 | if (response == NULL) { 123 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 124 | free(con_data->data); 125 | free(con_data); 126 | *con_cls = NULL; 127 | return MHD_NO; 128 | } 129 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 130 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 131 | MHD_destroy_response(response); 132 | free(con_data->data); 133 | free(con_data); 134 | *con_cls = NULL; 135 | return ret == MHD_YES ? MHD_YES : MHD_NO; 136 | } 137 | 138 | printf("post_handler_callback: Parsing JSON data\n"); 139 | 140 | cJSON *json = cJSON_Parse(con_data->data); 141 | if (json == NULL) { 142 | fprintf(stderr, "post_handler_callback: Invalid JSON received\n"); 143 | const char* error_msg = "{\"error\": \"Invalid JSON\"}"; 144 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 145 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 146 | if (response == NULL) { 147 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 148 | free(con_data->data); 149 | free(con_data); 150 | *con_cls = NULL; 151 | return MHD_NO; 152 | } 153 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 154 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 155 | MHD_destroy_response(response); 156 | free(con_data->data); 157 | free(con_data); 158 | *con_cls = NULL; 159 | return ret == MHD_YES ? MHD_YES : MHD_NO; 160 | } 161 | 162 | // Get the UUID from the JSON object 163 | cJSON *uuid_item = cJSON_GetObjectItem(json, "uuid"); 164 | if (uuid_item == NULL || !cJSON_IsString(uuid_item)) { 165 | fprintf(stderr, "post_handler_callback: UUID is missing or invalid\n"); 166 | const char* error_msg = "{\"error\": \"UUID is missing or invalid\"}"; 167 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 168 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 169 | if (response == NULL) { 170 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 171 | cJSON_Delete(json); 172 | free(con_data->data); 173 | free(con_data); 174 | *con_cls = NULL; 175 | return MHD_NO; 176 | } 177 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 178 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 179 | MHD_destroy_response(response); 180 | cJSON_Delete(json); 181 | free(con_data->data); 182 | free(con_data); 183 | *con_cls = NULL; 184 | return ret == MHD_YES ? MHD_YES : MHD_NO; 185 | } 186 | 187 | const char *uuid = uuid_item->valuestring; 188 | 189 | // Get the vector from the JSON object 190 | cJSON *vector_item = cJSON_GetObjectItem(json, "vector"); 191 | if (vector_item == NULL || !cJSON_IsArray(vector_item)) { 192 | fprintf(stderr, "post_handler_callback: Vector is missing or invalid\n"); 193 | const char* error_msg = "{\"error\": \"Vector is missing or invalid\"}"; 194 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 195 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 196 | if (response == NULL) { 197 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 198 | cJSON_Delete(json); 199 | free(con_data->data); 200 | free(con_data); 201 | *con_cls = NULL; 202 | return MHD_NO; 203 | } 204 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 205 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 206 | MHD_destroy_response(response); 207 | cJSON_Delete(json); 208 | free(con_data->data); 209 | free(con_data); 210 | *con_cls = NULL; 211 | return ret == MHD_YES ? MHD_YES : MHD_NO; 212 | } 213 | 214 | size_t dimension = cJSON_GetArraySize(vector_item); 215 | printf("post_handler_callback: Vector dimension: %zu\n", dimension); 216 | 217 | if (dimension != expected_vector_size) { 218 | fprintf(stderr, "post_handler_callback: Vector size mismatch. Expected %zu, got %zu\n", expected_vector_size, dimension); 219 | const char* error_msg = "{\"error\": \"Vector size mismatch\"}"; 220 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 221 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 222 | if (response == NULL) { 223 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 224 | cJSON_Delete(json); 225 | free(con_data->data); 226 | free(con_data); 227 | *con_cls = NULL; 228 | return MHD_NO; 229 | } 230 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 231 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 232 | MHD_destroy_response(response); 233 | cJSON_Delete(json); 234 | free(con_data->data); 235 | free(con_data); 236 | *con_cls = NULL; 237 | return ret == MHD_YES ? MHD_YES : MHD_NO; 238 | } 239 | 240 | Vector vec; 241 | strncpy(vec.uuid, uuid, sizeof(vec.uuid) - 1); 242 | vec.uuid[sizeof(vec.uuid) - 1] = '\0'; // Ensure null-termination 243 | vec.dimension = dimension; 244 | vec.data = (double*)malloc(dimension * sizeof(double)); 245 | if (!vec.data) { 246 | fprintf(stderr, "post_handler_callback: Memory allocation for vector data failed\n"); 247 | const char* error_msg = "{\"error\": \"Memory allocation failed\"}"; 248 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 249 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 250 | if (response == NULL) { 251 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 252 | cJSON_Delete(json); 253 | free(con_data->data); 254 | free(con_data); 255 | *con_cls = NULL; 256 | return MHD_NO; 257 | } 258 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 259 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 260 | MHD_destroy_response(response); 261 | cJSON_Delete(json); 262 | free(con_data->data); 263 | free(con_data); 264 | *con_cls = NULL; 265 | return ret == MHD_YES ? MHD_YES : MHD_NO; 266 | } 267 | 268 | // Extract the vector data from the JSON array 269 | for (size_t i = 0; i < dimension; i++) { 270 | cJSON *item = cJSON_GetArrayItem(vector_item, i); 271 | if (!cJSON_IsNumber(item)) { 272 | fprintf(stderr, "post_handler_callback: Invalid vector data at index %zu\n", i); 273 | const char* error_msg = "{\"error\": \"Invalid vector data\"}"; 274 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 275 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 276 | if (response == NULL) { 277 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 278 | cJSON_Delete(json); 279 | free(vec.data); 280 | free(con_data->data); 281 | free(con_data); 282 | *con_cls = NULL; 283 | return MHD_NO; 284 | } 285 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 286 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 287 | MHD_destroy_response(response); 288 | cJSON_Delete(json); 289 | free(vec.data); 290 | free(con_data->data); 291 | free(con_data); 292 | *con_cls = NULL; 293 | return ret == MHD_YES ? MHD_YES : MHD_NO; 294 | } 295 | vec.data[i] = item->valuedouble; 296 | } 297 | printf("post_handler_callback: Extracted vector data, dimension: %zu\n", dimension); 298 | 299 | if (db->kdtree == NULL) { 300 | fprintf(stderr, "post_handler_callback: KDTree is NULL before inserting\n"); 301 | if (db->size > 0 && db->vectors && db->vectors[0].data) { 302 | db->kdtree = kdtree_create(db->vectors[0].dimension); 303 | } else { 304 | db->kdtree = kdtree_create(dimension); 305 | } 306 | if (db->kdtree == NULL) { 307 | fprintf(stderr, "post_handler_callback: Failed to initialize KDTree\n"); 308 | const char* error_msg = "{\"error\": \"Failed to initialize KDTree\"}"; 309 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 310 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 311 | if (response == NULL) { 312 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 313 | cJSON_Delete(json); 314 | free(vec.data); 315 | free(con_data->data); 316 | free(con_data); 317 | *con_cls = NULL; 318 | return MHD_NO; 319 | } 320 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 321 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 322 | MHD_destroy_response(response); 323 | cJSON_Delete(json); 324 | free(vec.data); 325 | free(con_data->data); 326 | free(con_data); 327 | *con_cls = NULL; 328 | return ret == MHD_YES ? MHD_YES : MHD_NO; 329 | } 330 | } 331 | 332 | printf("post_handler_callback: Inserting vector into database\n"); 333 | size_t index = vector_db_insert(db, vec); 334 | if (index == (size_t)-1) { 335 | fprintf(stderr, "post_handler_callback: Failed to insert vector\n"); 336 | const char* error_msg = "{\"error\": \"Failed to insert vector\"}"; 337 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 338 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 339 | if (response == NULL) { 340 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 341 | cJSON_Delete(json); 342 | free(vec.data); 343 | free(con_data->data); 344 | free(con_data); 345 | *con_cls = NULL; 346 | return MHD_NO; 347 | } 348 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 349 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 350 | MHD_destroy_response(response); 351 | cJSON_Delete(json); 352 | free(vec.data); 353 | free(con_data->data); 354 | free(con_data); 355 | *con_cls = NULL; 356 | return ret == MHD_YES ? MHD_YES : MHD_NO; 357 | } 358 | 359 | printf("post_handler_callback: Vector inserted at index %zu\n", index); 360 | 361 | if (json) { 362 | printf("post_handler_callback: Deleting JSON\n"); 363 | cJSON_Delete(json); 364 | } 365 | if (con_data && con_data->data) { 366 | printf("post_handler_callback: Freeing con_data->data\n"); 367 | free(con_data->data); 368 | } 369 | if (con_data) { 370 | printf("post_handler_callback: Freeing con_data\n"); 371 | free(con_data); 372 | } 373 | *con_cls = NULL; 374 | 375 | cJSON *response_json = cJSON_CreateObject(); 376 | cJSON_AddStringToObject(response_json, "uuid", vec.uuid); 377 | cJSON_AddNumberToObject(response_json, "index", index); 378 | cJSON *vector_array = cJSON_CreateDoubleArray(vec.data, vec.dimension); 379 | cJSON_AddItemToObject(response_json, "vector", vector_array); 380 | char *response_str = cJSON_PrintUnformatted(response_json); 381 | cJSON_Delete(response_json); 382 | 383 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(response_str), 384 | (void*)response_str, MHD_RESPMEM_MUST_FREE); 385 | if (response == NULL) { 386 | fprintf(stderr, "post_handler_callback: Failed to create response\n"); 387 | return MHD_NO; 388 | } 389 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 390 | int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); 391 | MHD_destroy_response(response); 392 | return ret == MHD_YES ? MHD_YES : MHD_NO; 393 | } 394 | -------------------------------------------------------------------------------- /src/compare_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "cjson/cJSON.h" 7 | 8 | #include "../include/compare_handler.h" 9 | #include "../include/vector_database.h" 10 | #include "../include/connection_data.h" 11 | 12 | /** 13 | * @struct PostHandlerData 14 | * @brief Structure to hold data for the POST handler 15 | */ 16 | typedef struct { 17 | VectorDatabase* db; 18 | size_t db_vector_size; 19 | } PostHandlerData; 20 | 21 | /** 22 | * @brief Callback function to handle comparison requests. 23 | * 24 | * @param cls User-defined data, in this case, the handler data. 25 | * @param connection Pointer to MHD_Connection object. 26 | * @param url URL of the request. 27 | * @param method HTTP method (should be "GET"). 28 | * @param version HTTP version. 29 | * @param upload_data Data being uploaded in the request. 30 | * @param upload_data_size Size of the upload data. 31 | * @param con_cls Connection-specific data. 32 | * @return MHD_Result indicating the success or failure of the operation. 33 | */ 34 | static enum MHD_Result compare_handler_callback(void* cls, struct MHD_Connection* connection, 35 | const char* url, const char* method, 36 | const char* version, const char* upload_data, 37 | size_t* upload_data_size, void** con_cls); 38 | 39 | /** 40 | * @brief Function to handle comparison requests. 41 | * 42 | * @param cls User-defined data, in this case, the handler data. 43 | * @param connection Pointer to MHD_Connection object. 44 | * @param url URL of the request. 45 | * @param method HTTP method (should be "GET"). 46 | * @param version HTTP version. 47 | * @param upload_data Data being uploaded in the request. 48 | * @param upload_data_size Size of the upload data. 49 | * @param con_cls Connection-specific data. 50 | * @return MHD_Result indicating the success or failure of the operation. 51 | */ 52 | enum MHD_Result compare_handler(void* cls, struct MHD_Connection* connection, 53 | const char* url, const char* method, 54 | const char* version, const char* upload_data, 55 | size_t* upload_data_size, void** con_cls) { 56 | PostHandlerData* handler_data = (PostHandlerData*)cls; 57 | return compare_handler_callback(handler_data, connection, url, method, version, 58 | upload_data, upload_data_size, con_cls); 59 | } 60 | 61 | /** 62 | * @brief Callback function to handle comparison requests. 63 | * 64 | * @param cls User-defined data, in this case, the handler data. 65 | * @param connection Pointer to MHD_Connection object. 66 | * @param url URL of the request. 67 | * @param method HTTP method (should be "GET"). 68 | * @param version HTTP version. 69 | * @param upload_data Data being uploaded in the request. 70 | * @param upload_data_size Size of the upload data. 71 | * @param con_cls Connection-specific data. 72 | * @return MHD_Result indicating the success or failure of the operation. 73 | */ 74 | static enum MHD_Result compare_handler_callback(void* cls, struct MHD_Connection* connection, 75 | const char* url, const char* method, 76 | const char* version, const char* upload_data, 77 | size_t* upload_data_size, void** con_cls) { 78 | PostHandlerData* handler_data = (PostHandlerData*)cls; 79 | VectorDatabase* db = handler_data->db; 80 | size_t expected_vector_size = handler_data->db_vector_size; 81 | 82 | // Retrieve 'index1' and 'index2' query parameters 83 | const char* index1_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "index1"); 84 | const char* index2_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "index2"); 85 | 86 | if (!index1_str || !index2_str) { 87 | // Respond with an error if 'index1' or 'index2' is missing 88 | const char* error_msg = "{\"error\": \"Missing 'index1' or 'index2' query parameter\"}"; 89 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 90 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 91 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 92 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 93 | MHD_destroy_response(response); 94 | return ret == MHD_YES ? MHD_YES : MHD_NO; 95 | } 96 | 97 | // Convert 'index1' and 'index2' to size_t values 98 | size_t index1 = atoi(index1_str); 99 | size_t index2 = atoi(index2_str); 100 | 101 | if (index1 >= db->size || index2 >= db->size) { 102 | // Respond with an error if the indices are out of bounds 103 | const char* error_msg = "{\"error\": \"Index out of bounds\"}"; 104 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 105 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 106 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 107 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 108 | MHD_destroy_response(response); 109 | return ret == MHD_YES ? MHD_YES : MHD_NO; 110 | } 111 | 112 | // Retrieve the vectors from the database 113 | Vector* vec1 = vector_db_read(db, index1); 114 | Vector* vec2 = vector_db_read(db, index2); 115 | 116 | if (!vec1 || !vec2) { 117 | // Respond with an error if one of the vectors is not found 118 | const char* error_msg = "{\"error\": \"Vector not found\"}"; 119 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 120 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 121 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 122 | int ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); 123 | MHD_destroy_response(response); 124 | return ret == MHD_YES ? MHD_YES : MHD_NO; 125 | } 126 | 127 | if (vec1->dimension != vec2->dimension) { 128 | // Respond with an error if the vectors have different dimensions 129 | const char* error_msg = "{\"error\": \"Vectors have different dimensions\"}"; 130 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 131 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 132 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 133 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 134 | MHD_destroy_response(response); 135 | return ret == MHD_YES ? MHD_YES : MHD_NO; 136 | } 137 | 138 | // Check if the vectors match the expected size 139 | if (vec1->dimension != expected_vector_size || vec2->dimension != expected_vector_size) { 140 | const char* error_msg = "{\"error\": \"Vector size mismatch\"}"; 141 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 142 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 143 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 144 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 145 | MHD_destroy_response(response); 146 | return ret == MHD_YES ? MHD_YES : MHD_NO; 147 | } 148 | 149 | // Perform the comparison based on the URL 150 | double result = 0.0; 151 | const char* key = NULL; 152 | if (strcmp(url, "/compare/cosine_similarity") == 0) { 153 | result = cosine_similarity(*vec1, *vec2); 154 | key = "cosine_similarity"; 155 | } else if (strcmp(url, "/compare/euclidean_distance") == 0) { 156 | result = euclidean_distance(*vec1, *vec2); 157 | key = "euclidean_distance"; 158 | } else if (strcmp(url, "/compare/dot_product") == 0) { 159 | result = dot_product(*vec1, *vec2); 160 | key = "dot_product"; 161 | } else { 162 | // Respond with an error if the comparison method is unknown 163 | const char* error_msg = "{\"error\": \"Unknown comparison method\"}"; 164 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 165 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 166 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 167 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 168 | MHD_destroy_response(response); 169 | return ret == MHD_YES ? MHD_YES : MHD_NO; 170 | } 171 | 172 | // Create the JSON response 173 | cJSON *json_response = cJSON_CreateObject(); 174 | if (json_response == NULL) { 175 | const char* error_msg = "{\"error\": \"Internal server error\"}"; 176 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 177 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 178 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 179 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 180 | MHD_destroy_response(response); 181 | return ret == MHD_YES ? MHD_YES : MHD_NO; 182 | } 183 | 184 | cJSON_AddNumberToObject(json_response, key, result); 185 | char *json_string = cJSON_PrintUnformatted(json_response); 186 | cJSON_Delete(json_response); 187 | 188 | // Send the JSON response 189 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(json_string), 190 | (void*)json_string, MHD_RESPMEM_MUST_FREE); 191 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 192 | int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); 193 | MHD_destroy_response(response); 194 | return ret == MHD_YES ? MHD_YES : MHD_NO; 195 | } 196 | 197 | /** 198 | * @brief Callback function to handle nearest neighbor requests. 199 | * 200 | * @param cls User-defined data, in this case, the handler data. 201 | * @param connection Pointer to MHD_Connection object. 202 | * @param url URL of the request. 203 | * @param method HTTP method (should be "POST"). 204 | * @param version HTTP version. 205 | * @param upload_data Data being uploaded in the request. 206 | * @param upload_data_size Size of the upload data. 207 | * @param con_cls Connection-specific data. 208 | * @return MHD_Result indicating the success or failure of the operation. 209 | */ 210 | static enum MHD_Result nearest_handler_callback(void* cls, struct MHD_Connection* connection, 211 | const char* url, const char* method, 212 | const char* version, const char* upload_data, 213 | size_t* upload_data_size, void** con_cls); 214 | 215 | /** 216 | * @brief Function to handle nearest neighbor requests. 217 | * 218 | * @param cls User-defined data, in this case, the handler data. 219 | * @param connection Pointer to MHD_Connection object. 220 | * @param url URL of the request. 221 | * @param method HTTP method (should be "POST"). 222 | * @param version HTTP version. 223 | * @param upload_data Data being uploaded in the request. 224 | * @param upload_data_size Size of the upload data. 225 | * @param con_cls Connection-specific data. 226 | * @return MHD_Result indicating the success or failure of the operation. 227 | */ 228 | enum MHD_Result nearest_handler(void* cls, struct MHD_Connection* connection, 229 | const char* url, const char* method, 230 | const char* version, const char* upload_data, 231 | size_t* upload_data_size, void** con_cls) { 232 | // Initialize connection data if it's the first call for this connection 233 | if (*con_cls == NULL) { 234 | ConnectionData *con_data = (ConnectionData *)malloc(sizeof(ConnectionData)); 235 | if (con_data == NULL) { 236 | return MHD_NO; 237 | } 238 | con_data->data = NULL; 239 | con_data->data_size = 0; 240 | *con_cls = (void *)con_data; 241 | return MHD_YES; 242 | } 243 | 244 | // Retrieve the handler data 245 | PostHandlerData* handler_data = (PostHandlerData*)cls; 246 | return nearest_handler_callback(handler_data, connection, url, method, version, 247 | upload_data, upload_data_size, con_cls); 248 | } 249 | 250 | /** 251 | * @brief Callback function to handle nearest neighbor requests. 252 | * 253 | * @param cls User-defined data, in this case, the handler data. 254 | * @param connection Pointer to MHD_Connection object. 255 | * @param url URL of the request. 256 | * @param method HTTP method (should be "POST"). 257 | * @param version HTTP version. 258 | * @param upload_data Data being uploaded in the request. 259 | * @param upload_data_size Size of the upload data. 260 | * @param con_cls Connection-specific data. 261 | * @return MHD_Result indicating the success or failure of the operation. 262 | */ 263 | static enum MHD_Result nearest_handler_callback(void* cls, struct MHD_Connection* connection, 264 | const char* url, const char* method, 265 | const char* version, const char* upload_data, 266 | size_t* upload_data_size, void** con_cls) { 267 | ConnectionData *con_data = (ConnectionData *)*con_cls; 268 | PostHandlerData* handler_data = (PostHandlerData*)cls; 269 | VectorDatabase* db = handler_data->db; 270 | size_t expected_vector_size = handler_data->db_vector_size; 271 | 272 | // Check if there's data to be uploaded 273 | if (*upload_data_size != 0) { 274 | // Reallocate memory to accommodate the new data 275 | con_data->data = (char *)realloc(con_data->data, con_data->data_size + *upload_data_size + 1); 276 | if (con_data->data == NULL) { 277 | return MHD_NO; 278 | } 279 | // Copy the upload data to the connection data buffer 280 | memcpy(con_data->data + con_data->data_size, upload_data, *upload_data_size); 281 | con_data->data_size += *upload_data_size; 282 | con_data->data[con_data->data_size] = '\0'; // Null-terminate the data 283 | *upload_data_size = 0; // Reset the upload data size 284 | return MHD_YES; 285 | } 286 | 287 | // Check if the connection data buffer is empty 288 | if (con_data->data_size == 0) { 289 | // Respond with an error if there's no data 290 | const char* error_msg = "{\"error\": \"Empty data\"}"; 291 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 292 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 293 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 294 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 295 | MHD_destroy_response(response); 296 | return ret == MHD_YES ? MHD_YES : MHD_NO; 297 | } 298 | 299 | // Parse the JSON data from the request 300 | cJSON *json = cJSON_Parse(con_data->data); 301 | if (json == NULL) { 302 | // Respond with an error if the JSON is invalid 303 | const char* error_msg = "{\"error\": \"Invalid JSON\"}"; 304 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 305 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 306 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 307 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 308 | MHD_destroy_response(response); 309 | free(con_data->data); 310 | free(con_data); 311 | *con_cls = NULL; 312 | return ret == MHD_YES ? MHD_YES : MHD_NO; 313 | } 314 | 315 | // Get the dimension of the vector from the JSON array size 316 | size_t dimension = cJSON_GetArraySize(json); 317 | printf("nearest_handler_callback: Vector dimension: %zu\n", dimension); 318 | 319 | // Check if the vector size matches the expected size 320 | if (dimension != expected_vector_size) { 321 | fprintf(stderr, "nearest_handler_callback: Vector size mismatch. Expected %zu, got %zu\n", expected_vector_size, dimension); 322 | const char* error_msg = "{\"error\": \"Vector size mismatch\"}"; 323 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 324 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 325 | if (response == NULL) { 326 | fprintf(stderr, "nearest_handler_callback: Failed to create response\n"); 327 | cJSON_Delete(json); 328 | free(con_data->data); 329 | free(con_data); 330 | *con_cls = NULL; 331 | return MHD_NO; 332 | } 333 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 334 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 335 | MHD_destroy_response(response); 336 | cJSON_Delete(json); 337 | free(con_data->data); 338 | free(con_data); 339 | *con_cls = NULL; 340 | return ret == MHD_YES ? MHD_YES : MHD_NO; 341 | } 342 | 343 | Vector vec; 344 | vec.dimension = dimension; 345 | // Allocate memory for the vector data 346 | vec.data = (double*)malloc(dimension * sizeof(double)); 347 | if (!vec.data) { 348 | // Respond with an error if memory allocation fails 349 | const char* error_msg = "{\"error\": \"Memory allocation failed\"}"; 350 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 351 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 352 | if (response == NULL) { 353 | fprintf(stderr, "nearest_handler_callback: Failed to create response\n"); 354 | cJSON_Delete(json); 355 | free(con_data->data); 356 | free(con_data); 357 | *con_cls = NULL; 358 | return MHD_NO; 359 | } 360 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 361 | int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); 362 | MHD_destroy_response(response); 363 | cJSON_Delete(json); 364 | free(con_data->data); 365 | free(con_data); 366 | *con_cls = NULL; 367 | return ret == MHD_YES ? MHD_YES : MHD_NO; 368 | } 369 | 370 | // Extract the vector data from the JSON array 371 | for (size_t i = 0; i < dimension; i++) { 372 | cJSON *item = cJSON_GetArrayItem(json, i); 373 | if (!cJSON_IsNumber(item)) { 374 | // Respond with an error if the vector data is invalid 375 | const char* error_msg = "{\"error\": \"Invalid vector data\"}"; 376 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(error_msg), 377 | (void*)error_msg, MHD_RESPMEM_PERSISTENT); 378 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 379 | int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); 380 | MHD_destroy_response(response); 381 | cJSON_Delete(json); 382 | free(vec.data); 383 | free(con_data->data); 384 | free(con_data); 385 | *con_cls = NULL; 386 | return ret == MHD_YES ? MHD_YES : MHD_NO; 387 | } 388 | // Store the vector data 389 | vec.data[i] = item->valuedouble; 390 | } 391 | 392 | // Debug: Print out the vector 393 | printf("Received vector for nearest neighbor search: ["); 394 | for (size_t i = 0; i < dimension; ++i) { 395 | printf("%f", vec.data[i]); 396 | if (i < dimension - 1) { 397 | printf(", "); 398 | } 399 | } 400 | printf("]\n"); 401 | 402 | // Use KD-Tree to find the nearest neighbor 403 | size_t nearest_index = kdtree_nearest(db->kdtree, vec.data); 404 | 405 | // Debug: Print the nearest index 406 | printf("Nearest neighbor index: %zu\n", nearest_index); 407 | 408 | // Create the JSON response 409 | cJSON* json_response = cJSON_CreateObject(); 410 | if (nearest_index != (size_t)-1) { 411 | Vector* nearest_vector = vector_db_read(db, nearest_index); 412 | if (nearest_vector) { 413 | cJSON* vector_array = cJSON_CreateDoubleArray(nearest_vector->data, nearest_vector->dimension); 414 | cJSON_AddNumberToObject(json_response, "index", nearest_index); 415 | cJSON_AddItemToObject(json_response, "vector", vector_array); 416 | cJSON_AddStringToObject(json_response, "uuid", nearest_vector->uuid); 417 | } else { 418 | cJSON_AddStringToObject(json_response, "error", "Nearest neighbor not found"); 419 | } 420 | } else { 421 | cJSON_AddStringToObject(json_response, "error", "No nearest neighbor found"); 422 | } 423 | 424 | // Convert the JSON response to a string 425 | char* response_str = cJSON_PrintUnformatted(json_response); 426 | cJSON_Delete(json_response); 427 | 428 | // Free allocated memory 429 | free(vec.data); 430 | free(con_data->data); 431 | free(con_data); 432 | *con_cls = NULL; 433 | 434 | // Send the JSON response 435 | struct MHD_Response* response = MHD_create_response_from_buffer(strlen(response_str), 436 | (void*)response_str, MHD_RESPMEM_MUST_FREE); 437 | MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); 438 | int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); 439 | MHD_destroy_response(response); 440 | return ret == MHD_YES ? MHD_YES : MHD_NO; 441 | } 442 | --------------------------------------------------------------------------------