diff --git a/AUTHORS b/AUTHORS index c95229e4..6bae6f1a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,3 +12,4 @@ Ashutosh Sharma Henrique de Carvalho Yihe Lu Eugenio Gigante +Haoran Zhang diff --git a/src/include/art.h b/src/include/art.h new file mode 100644 index 00000000..cd70d52a --- /dev/null +++ b/src/include/art.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PGAGROAL_ART_H +#define PGAGROAL_ART_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +#define MAX_PREFIX_LEN 10 + +typedef int (*art_callback)(void* data, const unsigned char* key, uint32_t key_len, struct value* value); + +typedef void (*value_destroy_callback)(void* value); + +/** @struct art + * The ART tree + */ +struct art +{ + struct art_node* root; /**< The root node of ART */ + uint64_t size; /**< The size of the ART */ +}; + +/** @struct art_iterator + * Defines an art_iterator + */ +struct art_iterator +{ + struct deque* que; /**< The deque */ + struct art* tree; /**< The ART */ + uint32_t count; /**< The count of the iterator */ + unsigned char* key; /**< The key */ + struct value* value; /**< The value */ +}; + +/** + * Initializes an adaptive radix tree + * @param tree [out] The tree + * @return 0 on success, 1 if otherwise + */ +int +pgagroal_art_create(struct art** tree); + +/** + * inserts a new value into the art tree, note that the key is copied + * @param t The tree + * @param key The key + * @param key_len The length of the key + * @param value The value data + * @param type The value type + * @return 0 if the item was successfully inserted, otherwise 1 + */ +int +pgagroal_art_insert(struct art* t, unsigned char* key, uint32_t key_len, uintptr_t value, enum value_type type); + +/** + * Check if a key exists in the ART tree + * @param t The tree + * @param key The key + * @param key_len The length of the key + * @return true if the key exists, false if otherwise + */ +bool +pgagroal_art_contains_key(struct art* t, unsigned char* key, uint32_t key_len); + +/** + * Searches for a value in the ART tree + * @param t The tree + * @param key The key + * @param key_len The length of the key + * @return NULL if the item was not found, otherwise the value pointer is returned + */ +uintptr_t +pgagroal_art_search(struct art* t, unsigned char* key, uint32_t key_len); + +/** + * Deletes a value from the ART tree + * @param t The tree + * @param key The key + * @param key_len The length of the key + * @return 0 if success or value not found, 1 if otherwise + */ +int +pgagroal_art_delete(struct art* t, unsigned char* key, uint32_t key_len); + +/** + * Get the next key value pair into iterator + * @param iter The iterator + * @return true if iterator has next, otherwise false + */ +bool +pgagroal_art_iterator_next(struct art_iterator* iter); + +/** + * Create an art iterator + * @param t The tree + * @param iter [out] The iterator + * @return 0 if success, otherwise 1 + */ +int +pgagroal_art_iterator_create(struct art* t, struct art_iterator** iter); + +/** + * Destroy the iterator + * @param iter The iterator + */ +void +pgagroal_art_iterator_destroy(struct art_iterator* iter); + +/** + * Convert the ART tree to string + * @param t The ART tree + * @param format The format + * @param tag The optional tag + * @param indent The indent + * @return The string + */ +char* +pgagroal_art_to_string(struct art* t, int32_t format, char* tag, int indent); + +/** + * Destroys an ART tree + * @return 0 on success, 1 if otherwise + */ +int +pgagroal_art_destroy(struct art* tree); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/include/deque.h b/src/include/deque.h new file mode 100644 index 00000000..472888b3 --- /dev/null +++ b/src/include/deque.h @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef PGAGROAL_DEQUE_H +#define PGAGROAL_DEQUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include + +/** @struct deque_node + * Defines a deque node + */ +struct deque_node +{ + struct value* data; /**< The value */ + char* tag; /**< The tag */ + struct deque_node* next; /**< The next pointer */ + struct deque_node* prev; /**< The previous pointer */ +}; + +/** @struct deque + * Defines a deque + */ +struct deque +{ + uint32_t size; /**< The size of the deque */ + bool thread_safe; /**< If the deque is thread safe */ + pthread_rwlock_t mutex; /**< The mutex of the deque */ + struct deque_node* start; /**< The start node */ + struct deque_node* end; /**< The end node */ +}; + +/** @struct deque_iterator + * Defines a deque iterator + */ +struct deque_iterator +{ + struct deque* deque; /**< The deque */ + struct deque_node* cur; /**< The current deque node */ + char* tag; /**< The current tag */ + struct value* value; /**< The current value */ +}; + +/** + * Create a deque + * @param thread_safe If the deque needs to be thread safe + * @param deque The deque + * @return 0 if success, otherwise 1 + */ +int +pgagroal_deque_create(bool thread_safe, struct deque** deque); + +/** + * Add a node to deque's tail, the tag will be copied, but the data will not + * This function is thread safe + * @param deque The deque + * @param tag The tag,optional + * @param data The data + * @param type The data type + * @return 0 if success, otherwise 1 + */ +int +pgagroal_deque_add(struct deque* deque, char* tag, uintptr_t data, enum value_type type); + +/** + * Retrieve value and remove the node from deque's head. + * Note that if the value was copied into node, + * this function will return the original value and tag + * rather than making a copy of it. + * This function is thread safe, but the returned value is not protected + * @param deque The deque + * @param tag [out] Optional, tag will be returned through if not NULL + * @return The value data if deque's not empty, otherwise 0 + */ +uintptr_t +pgagroal_deque_poll(struct deque* deque, char** tag); + +/** + * Retrieve value without removing the node from deque's head. + * Note that if the value was copied into node, + * this function will return the original value and tag + * rather than making a copy of it. + * This function is thread safe, but the returned value is not protected + * @param deque The deque + * @param tag [out] Optional, tag will be returned through if not NULL + * @return The value data if deque's not empty, otherwise 0 + */ +uintptr_t +pgagroal_deque_peek(struct deque* deque, char** tag); + +/** + * Get the data for the specified tag + * @param deque The deque + * @param tag The tag + * @return The data, or 0 + */ +uintptr_t +pgagroal_deque_get(struct deque* deque, char* tag); + +/** + * Create a deque iterator + * @param deque The deque + * @param iter [out] The iterator + * @return 0 on success, 1 if otherwise + */ +int +pgagroal_deque_iterator_create(struct deque* deque, struct deque_iterator** iter); + +/** + * Get the next deque value + * @param iter The iterator + * @return true if has next, false if otherwise + */ +bool +pgagroal_deque_iterator_next(struct deque_iterator* iter); + +/** + * Remove the current node iterator points to and place the iterator to the previous node + * @param iter The iterator + */ +void +pgagroal_deque_iterator_remove(struct deque_iterator* iter); + +/** + * Destroy a deque iterator + * @param iter The iterator + */ +void +pgagroal_deque_iterator_destroy(struct deque_iterator* iter); + +/** + * Get the size of the deque + * @param deque The deque + * @return The size + */ +uint32_t +pgagroal_deque_size(struct deque* deque); + +/** + * Check if the deque is empty + * @param deque The deque + * @return true if deque size is 0, otherwise false + */ +bool +pgagroal_deque_empty(struct deque* deque); + +/** + * List the nodes in the deque + * @param deque The deque + */ +void +pgagroal_deque_list(struct deque* deque); + +/** + * Convert what's inside deque to string + * @param deque The deque + * @param format The format + * @param tag [Optional] The tag, which will be applied before the content if not null + * @param indent The current indentation + * @return The string + */ +char* +pgagroal_deque_to_string(struct deque* deque, int32_t format, char* tag, int indent); + +/** + * Destroy the deque and free its and its nodes' memory + * @param deque The deque + */ +void +pgagroal_deque_destroy(struct deque* deque); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/include/json.h b/src/include/json.h index 00746481..20d79cab 100644 --- a/src/include/json.h +++ b/src/include/json.h @@ -26,150 +26,170 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifndef PGAGROAL_JSON_H +#define PGAGROAL_JSON_H + +#ifdef __cplusplus +extern "C" { +#endif + /* pgagroal */ #include +#include +#include -#include +enum json_type { + JSONUnknown, + JSONItem, + JSONArray +}; -/** - * JSON related command tags, used to build and retrieve - * a JSON piece of information related to a single command +/** @struct json + * Defines a JSON structure */ -#define JSON_TAG_COMMAND "command" -#define JSON_TAG_COMMAND_NAME "name" -#define JSON_TAG_COMMAND_STATUS "status" -#define JSON_TAG_COMMAND_ERROR "error" -#define JSON_TAG_COMMAND_OUTPUT "output" -#define JSON_TAG_COMMAND_EXIT_STATUS "exit-status" +struct json +{ + // a json object can only be item or array + enum json_type type; /**< The json object type */ + // if the object is an array, it can have at most one json element + void* elements; /**< The json elements, could be an array or some kv pairs */ +}; -#define JSON_TAG_APPLICATION_NAME "name" -#define JSON_TAG_APPLICATION_VERSION_MAJOR "major" -#define JSON_TAG_APPLICATION_VERSION_MINOR "minor" -#define JSON_TAG_APPLICATION_VERSION_PATCH "patch" -#define JSON_TAG_APPLICATION_VERSION "version" - -#define JSON_TAG_ARRAY_NAME "list" +/** @struct json_iterator + * Defines a JSON iterator + */ +struct json_iterator +{ + void* iter; /**< The internal iterator */ + struct json* obj; /**< The json object */ + char* key; /**< The current key, if it's json item */ + struct value* value; /**< The current value or entry */ +}; /** - * JSON pre-defined values + * Create a json object + * @param item [out] The json item + * @return 0 if success, 1 if otherwise */ -#define JSON_STRING_SUCCESS "OK" -#define JSON_STRING_ERROR "KO" -#define JSON_BOOL_SUCCESS 0 -#define JSON_BOOL_ERROR 1 +int +pgagroal_json_create(struct json** object); /** - * Utility method to create a new JSON object that wraps a - * single command. This method should be called to initialize the - * object and then the other specific methods that read the - * answer from pgagroal should populate the object accordingly. - * - * Moreover, an 'application' object is placed to indicate from - * where the command has been launched (i.e., which executable) - * and at which version. - * - * @param command_name the name of the command this object wraps - * an answer for - * @param success true if the command is supposed to be succesfull - * @returns the new JSON object to use and populate - * @param executable_name the name of the executable that is creating this - * response object + * Put a key value pair into the json item, + * if the key exists, value will be overwritten, + * + * If the kv pair is put into an empty json object, it will be treated as json item, + * otherwise if the json object is an array, it will reject the kv pair + * @param item The json item + * @param key The json key + * @param val The value data + * @param type The value type + * @return 0 on success, otherwise 1 */ -cJSON* -pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name, char* executable_version); +int +pgagroal_json_put(struct json* item, char* key, uintptr_t val, enum value_type type); /** - * Utility method to "jump" to the output JSON object wrapped into - * a command object. - * - * The "output" object is the one that every single method that reads - * back an answer from pgagroal has to populate in a specific - * way according to the data received from pgagroal. - * - * @param json the command object that wraps the command - * @returns the pointer to the output object of NULL in case of an error + * Get the value data from json item + * @param item The item + * @param tag The tag + * @return The value data, 0 if not found */ -cJSON* -pgagroal_json_extract_command_output_object(cJSON* json); +uintptr_t +pgagroal_json_get(struct json* item, char* tag); /** - * Utility function to set a command JSON object as faulty, that - * means setting the 'error' and 'status' message accordingly. - * - * @param json the whole json object that must include the 'command' - * tag - * @param message the message to use to set the faulty diagnostic - * indication - * - * @param exit status - * - * @returns 0 on success - * - * Example: - * json_set_command_object_faulty( json, strerror( errno ) ); + * Append an entry into the json array + * If the entry is put into an empty json object, it will be treated as json array, + * otherwise if the json object is an item, it will reject the entry + * @param array The json array + * @param entry The entry data + * @param type The entry value type + * @return 0 is successful, + * otherwise when the json object is an array, value is null, or value type conflicts with old value, 1 will be returned */ int -pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status); +pgagroal_json_append(struct json* array, uintptr_t entry, enum value_type type); /** - * Utility method to inspect if a JSON object that wraps a command - * is faulty, that means if it has the error flag set to true. - * - * @param json the json object to analyzer - * @returns the value of the error flag in the object, or false if - * the object is not valid + * Get json array length + * @param array The json array + * @return The length + */ +uint32_t +pgagroal_json_array_length(struct json* array); + +/** + * Create a json iterator + * @param object The JSON object + * @param iter [out] The iterator + * @return 0 on success, 1 if otherwise + */ +int +pgagroal_json_iterator_create(struct json* object, struct json_iterator** iter); +/** + * Get the next kv pair/entry from JSON object + * @param iter The iterator + * @return true if has next, false if otherwise */ bool -pgagroal_json_is_command_object_faulty(cJSON* json); +pgagroal_json_iterator_next(struct json_iterator* iter); /** - * Utility method to extract the message related to the status - * of the command wrapped in the JSON object. - * - * @param json the JSON object to analyze - * #returns the status message or NULL in case the JSON object is not valid + * Destroy a iterator + * @param iter The iterator */ -const char* -pgagroal_json_get_command_object_status(cJSON* json); +void +pgagroal_json_iterator_destroy(struct json_iterator* iter); /** - * Utility method to check if a JSON object wraps a specific command name. - * - * @param json the JSON object to analyze - * @param command_name the name to search for - * @returns true if the command name matches, false otherwise and in case - * the JSON object is not valid or the command name is not valid + * Parse a string into json item + * @param str The string + * @param obj [out] The json object + * @return 0 if success, 1 if otherwise */ -bool -pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name); +int +pgagroal_json_parse_string(char* str, struct json** obj); /** - * Utility method to print out the JSON object - * on standard output. - * - * After the object has been printed, it is destroyed, so - * calling this method will make the pointer invalid - * and the jeon object cannot be used anymore. - * - * This should be the last method to be called - * when there is the need to print out the information - * contained in a json object. - * - * Since the JSON object will be invalidated, the method - * returns the status of the JSON command within it - * to be used. - * - * @param json the json object to print - * @return the command status within the JSON object + * Clone a json object + * @param from The from object + * @param to [out] The to object + * @return 0 if success, 1 if otherwise */ int -pgagroal_json_print_and_free_json_object(cJSON* json); +pgagroal_json_clone(struct json* from, struct json** to); /** - * Utility function to get the exit status of a given command wrapped in a JSON object. - * - * @param json the json object - * @returns the exit status of the command + * Convert a json to string + * @param object The json object + * @param format The format + * @param tag The optional tag + * @param indent The indent + * @return The json formatted string + */ +char* +pgagroal_json_to_string(struct json* object, int32_t format, char* tag, int indent); + +/** + * Print a json object + * @param object The object + * @param format The format + * @param indent_per_level The indent per level + */ +void +pgagroal_json_print(struct json* object, int32_t format); + +/** + * Destroy the json object + * @param item The json object + * @return 0 if success, 1 if otherwise */ int -pgagroal_json_command_object_exit_status(cJSON* json); +pgagroal_json_destroy(struct json* object); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/include/logging.h b/src/include/logging.h index f099a6ff..996ae6e0 100644 --- a/src/include/logging.h +++ b/src/include/logging.h @@ -104,6 +104,14 @@ pgagroal_log_line(int level, char* file, int line, char* fmt, ...); void pgagroal_log_mem(void* data, size_t size); +/** + * Is the logging level enabled + * @param level The level + * @return True if enabled, otherwise false + */ +bool +pgagroal_log_is_enabled(int level); + /** * Utility function to understand if log rotation * is enabled or not. diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index 14d2b23d..f9b2c9f7 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -175,6 +175,11 @@ extern "C" { #define EXIT_STATUS_CONNECTION_ERROR 1 #define EXIT_STATUS_DATA_ERROR 2 +#define INDENT_PER_LEVEL 2 +#define FORMAT_JSON 0 +#define FORMAT_TEXT 1 +#define BULLET_POINT "- " + #define likely(x) __builtin_expect (!!(x), 1) #define unlikely(x) __builtin_expect (!!(x), 0) diff --git a/src/include/utils.h b/src/include/utils.h index 8fb9b704..e0ba72b3 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -443,6 +443,34 @@ pgagroal_append_ulong(char* orig, unsigned long l); char* pgagroal_append_ullong(char* orig, unsigned long long l); +/** + * Append a char + * @param orig The original string + * @param s The string + * @return The resulting string + */ +char* +pgagroal_append_char(char* orig, char c); + +/** + * Indent a string + * @param str The string + * @param tag [Optional] The tag, which will be applied after indentation if not NULL + * @param indent The indent + * @return The indented string + */ +char* +pgagroal_indent(char* str, char* tag, int indent); + +/** + * Compare two strings + * @param str1 The first string + * @param str2 The second string + * @return true if the strings are the same, otherwise false + */ +bool +pgagroal_compare_string(const char* str1, const char* str2); + #ifdef DEBUG /** diff --git a/src/include/value.h b/src/include/value.h new file mode 100644 index 00000000..b9bece10 --- /dev/null +++ b/src/include/value.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef PGAGROAL_VALUE_H +#define PGAGROAL_VALUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void (*data_destroy_cb)(uintptr_t data); +typedef char* (*data_to_string_cb)(uintptr_t data, int32_t format, char* tag, int indent); + +enum value_type { + ValueInt8, + ValueUInt8, + ValueInt16, + ValueUInt16, + ValueInt32, + ValueUInt32, + ValueInt64, + ValueUInt64, + ValueChar, + ValueBool, + ValueString, + ValueFloat, + ValueDouble, + ValueJSON, + ValueDeque, + ValueART, + ValueRef, + ValueVerifyEntry, +}; + +/** + * @struct value + * Defines a universal value + */ +struct value +{ + enum value_type type; /**< The type of value data */ + uintptr_t data; /**< The data, could be passed by value or by reference */ + data_destroy_cb destroy_data; /**< The callback to destroy data */ + data_to_string_cb to_string; /**< The callback to convert data to string */ +}; + +/** + * Create a value based on the data and value type + * @param type The value type, use ValueRef if you are only storing pointers without the need to manage memory + * @param data The value data, type cast it to uintptr_t before passing into function + * @param value [out] The value + * @return 0 on success, 1 if otherwise + */ +int +pgagroal_value_create(enum value_type type, uintptr_t data, struct value** value); + +/** + * Destroy a value along with the data within + * @param value The value + * @return 0 on success, 1 if otherwise + */ +int +pgagroal_value_destroy(struct value* value); + +/** + * Get the raw data from the value, which can be casted back to its original type + * @param value The value + * @return The value data within + */ +uintptr_t +pgagroal_value_data(struct value* value); + +/** + * Convert a value to string + * @param value The value + * @param format The format + * @param tag The optional tag + * @param indent The indent + * @return The string + */ +char* +pgagroal_value_to_string(struct value* value, int32_t format, char* tag, int indent); + +/** + * Convert a double value to value data, since straight type cast discards the decimal part + * @param val The value + * @return The value data + */ +uintptr_t +pgagroal_value_from_double(double val); + +/** + * Convert a value data to double + * @param val The value + * @return + */ +double +pgagroal_value_to_double(uintptr_t val); + +/** + * Convert a float value to value data, since straight type cast discards the decimal part + * @param val The value + * @return The value data + */ +uintptr_t +pgagroal_value_from_float(float val); + +/** + * Convert a value data to float + * @param val The value + * @return + */ +float +pgagroal_value_to_float(uintptr_t val); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/libpgagroal/art.c b/src/libpgagroal/art.c new file mode 100644 index 00000000..f6790eb8 --- /dev/null +++ b/src/libpgagroal/art.c @@ -0,0 +1,1569 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include + +#define IS_LEAF(x) (((uintptr_t)(x) & 1)) +#define SET_LEAF(x) ((void*)((uintptr_t)(x) | 1)) +#define GET_LEAF(x) ((struct art_leaf*)((void*)((uintptr_t)(x) & ~1))) + +enum art_node_type { + Node4, + Node16, + Node48, + Node256 +}; + +/** + * This struct is included as part + * of all the various node sizes. + * All node types should be aligned + * because we need the last bit to be 0 as a flag bit for leaf. + * So that leaf can be treated as a node as well and stored in the children field, + * and only converted back when necessary + */ +struct art_node +{ + uint32_t prefix_len; /**< The actual length of the prefix segment */ + enum art_node_type type; /**< The node type */ + uint8_t num_children; /**< The number of children */ + unsigned char prefix[MAX_PREFIX_LEN]; /**< The (potentially partial) prefix, only record up to MAX_PREFIX_LEN characters */ +} __attribute__ ((aligned (64))); + +/** + * The ART leaf with key buffer of arbitrary size + */ +struct art_leaf +{ + struct value* value; + uint32_t key_len; + unsigned char key[]; +} __attribute__ ((aligned (64))); + +/** + * The ART node with only 4 children, + * the key character and the children pointer are stored + * in the same position of the corresponding array. + * The keys are stored in sorted order sequentially. + */ +struct art_node4 +{ + struct art_node node; + unsigned char keys[4]; + struct art_node* children[4]; +} __attribute__ ((aligned (64))); + +/** + * Similar structure as node4, but with 16 children. + * The keys are stored in sorted order sequentially. + */ +struct art_node16 +{ + struct art_node node; + unsigned char keys[16]; + struct art_node* children[16]; +} __attribute__ ((aligned (64))); + +/** + * A full key array that can be indexed by the key character directly, + * the array stores the index of the corresponding child in the array. + * Note that in practice 0 is used for invalid index, + * so the actual index is the index in key array - 1 + */ +struct art_node48 +{ + struct art_node node; + unsigned char keys[256]; + struct art_node* children[48]; +} __attribute__ ((aligned (64))); + +/** + * A direct array of children using key character as index + */ +struct art_node256 +{ + struct art_node node; + struct art_node* children[256]; +} __attribute__ ((aligned (64))); + +struct to_string_param +{ + char* str; + int indent; + int cnt; + char* tag; + struct art* t; +}; + +static struct art_node** +node_get_child(struct art_node* node, unsigned char ch); + +// Get the left most leaf of a child +static struct art_leaf* +node_get_minimum(struct art_node* node); + +static void +create_art_leaf(struct art_leaf** leaf, unsigned char* key, uint32_t key_len, uintptr_t value, enum value_type type); + +static void +create_art_node(struct art_node** node, enum art_node_type type); + +static void +create_art_node4(struct art_node4** node); + +static void +create_art_node16(struct art_node16** node); + +static void +create_art_node48(struct art_node48** node); + +static void +create_art_node256(struct art_node256** node); + +// Destroy ART nodes/leaves recursively +static void +destroy_art_node(struct art_node* node); + +static int +art_iterate(struct art* t, art_callback cb, void* data); + +/** + * Get where the keys diverge starting from depth. + * This function only compares within the partial prefix range + * @param node The node + * @param key The key + * @param depth The starting depth + * @param key_len The length of the key + * @return The length of the part of the prefix that matches + */ +static uint32_t +check_prefix_partial(struct art_node* node, unsigned char* key, uint32_t depth, uint32_t key_len); + +/** + * Get where the keys diverge starting from depth. + * This function compares with the complete key to determine if diverging point goes beyond current prefix or partial prefix + * @param node The node + * @param key The key + * @param depth The starting depth + * @param key_len The length of the key + * @return The length of the part of the prefix that matches + */ +static uint32_t +check_prefix(struct art_node* node, unsigned char* key, uint32_t depth, uint32_t key_len); + +/** + * Compare the key stored in leaf and the original key + * @param leaf + * @param key + * @param key_len + * @return true if the key matches + */ +static bool +leaf_match(struct art_leaf* leaf, unsigned char* key, uint32_t key_len); + +/** + * Find the index of the corresponding key character using binary search. + * If not found, the index of the largest element smaller than ch, + * or -1 if ch is the smallest, will be returned, + * @param ch + * @param keys + * @param length + * @return The index + */ +static int +find_index(unsigned char ch, const unsigned char* keys, int length); + +/** + * Insert a value into a node recursively, adopting lazy expansion and path compression -- + * Expand the leaf, or split inner node should keys diverge within node's prefix range + * @param node The node + * @param node_ref The reference to node pointer + * @param depth The depth into the node, which is the same as the total prefix length + * @param key The key + * @param key_len The length of the key + * @param value The value data + * @param type The value type + * @param new If the key value is newly inserted (not replaced) + * @return Old value if the key exists, otherwise NULL + */ +static struct value* +art_node_insert(struct art_node* node, struct art_node** node_ref, uint32_t depth, unsigned char* key, uint32_t key_len, uintptr_t value, enum value_type type, bool* new); + +/** + * Delete a value from a node recursively. + * @param node The node + * @param node_ref The reference to node pointer + * @param depth The depth into the node + * @param key The key + * @param key_len The length of the key + * @return Deleted value if the key exists, otherwise NULL + */ +static struct art_leaf* +art_node_delete(struct art_node* node, struct art_node** node_ref, uint32_t depth, unsigned char* key, uint32_t key_len); + +static int +art_node_iterate(struct art_node* node, art_callback cb, void* data); + +static void +node_add_child(struct art_node* node, struct art_node** node_ref, unsigned char ch, void* child); + +/** + * Add a child to the node. The function assumes node is not NULL, + * nor the key character already exists. + * If node is full, a new node of type node16 will be created. The old + * node will be replaced by new node through node_ref. + * @param node The node + * @param node_ref The reference of the node pointer + * @param ch The key character + * @param child The child + */ +static void +node4_add_child(struct art_node4* node, struct art_node** node_ref, unsigned char ch, void* child); + +static void +node16_add_child(struct art_node16* node, struct art_node** node_ref, unsigned char ch, void* child); + +static void +node48_add_child(struct art_node48* node, struct art_node** node_ref, unsigned char ch, void* child); + +static void +node256_add_child(struct art_node256* node, unsigned char ch, void* child); + +// All removal functions assume the child to remove is leaf, meaning they don't try removing anything recursively. +// They also do not free the leaf node for bookkeeping purpose. The key insight is that due to path compression, +// no node will have only one child, if node has only one child after deletion, it merges with this child +static void +node_remove_child(struct art_node* node, struct art_node** node_ref, unsigned char ch); + +static void +node4_remove_child(struct art_node4* node, struct art_node** node_ref, unsigned char ch); + +static void +node16_remove_child(struct art_node16* node, struct art_node** node_ref, unsigned char ch); + +static void +node48_remove_child(struct art_node48* node, struct art_node** node_ref, unsigned char ch); + +static void +node256_remove_child(struct art_node256* node, struct art_node** node_ref, unsigned char ch); + +static void +copy_header(struct art_node* dest, struct art_node* src); + +static uint32_t +min(uint32_t a, uint32_t b); + +static struct value* +art_search(struct art* t, unsigned char* key, uint32_t key_len); + +static int +art_to_json_string_cb(void* param, const unsigned char* key, uint32_t key_len, struct value* value); + +static int +art_to_text_string_cb(void* param, const unsigned char* key, uint32_t key_len, struct value* value); + +static char* +to_json_string(struct art* t, char* tag, int indent); + +static char* +to_text_string(struct art* t, char* tag, int indent); + +int +pgagroal_art_create(struct art** tree) +{ + struct art* t = NULL; + t = malloc(sizeof(struct art)); + t->size = 0; + t->root = NULL; + *tree = t; + return 0; +} + +int +pgagroal_art_destroy(struct art* tree) +{ + if (tree == NULL) + { + return 0; + } + destroy_art_node(tree->root); + free(tree); + return 0; +} + +uintptr_t +pgagroal_art_search(struct art* t, unsigned char* key, uint32_t key_len) +{ + struct value* val = art_search(t, key, key_len); + return pgagroal_value_data(val); +} + +bool +pgagroal_art_contains_key(struct art* t, unsigned char* key, uint32_t key_len) +{ + struct value* val = art_search(t, key, key_len); + return val != NULL; +} + +int +pgagroal_art_insert(struct art* t, unsigned char* key, uint32_t key_len, uintptr_t value, enum value_type type) +{ + struct value* old_val = NULL; + bool new = false; + if (t == NULL) + { + // c'mon, at least create a tree first... + goto error; + } + old_val = art_node_insert(t->root, &t->root, 0, key, key_len, value, type, &new); + pgagroal_value_destroy(old_val); + if (new) + { + t->size++; + } + return 0; +error: + return 1; +} + +int +pgagroal_art_delete(struct art* t, unsigned char* key, uint32_t key_len) +{ + struct art_leaf* l = NULL; + if (t == NULL) + { + return 1; + } + l = art_node_delete(t->root, &t->root, 0, key, key_len); + t->size--; + pgagroal_value_destroy(l->value); + free(l); + return 0; +} + +char* +pgagroal_art_to_string(struct art* t, int32_t format, char* tag, int indent) +{ + if (format == FORMAT_JSON) + { + return to_json_string(t, tag, indent); + } + else if (format == FORMAT_TEXT) + { + return to_text_string(t, tag, indent); + } + return NULL; +} + +static uint32_t +min(uint32_t a, uint32_t b) +{ + if (a >= b) + { + return b; + } + return a; +} + +static void +create_art_leaf(struct art_leaf** leaf, unsigned char* key, uint32_t key_len, uintptr_t value, enum value_type type) +{ + struct art_leaf* l = NULL; + l = malloc(sizeof(struct art_leaf) + key_len); + memset(l, 0, sizeof(struct art_leaf) + key_len); + pgagroal_value_create(type, value, &l->value); + l->key_len = key_len; + memcpy(l->key, key, key_len); + *leaf = l; +} + +static void +create_art_node(struct art_node** node, enum art_node_type type) +{ + struct art_node* n = NULL; + switch (type) + { + case Node4: + { + struct art_node4* n4 = malloc(sizeof(struct art_node4)); + memset(n4, 0, sizeof(struct art_node4)); + n4->node.type = Node4; + n = (struct art_node*) n4; + break; + } + case Node16: + { + struct art_node16* n16 = malloc(sizeof(struct art_node16)); + memset(n16, 0, sizeof(struct art_node16)); + n16->node.type = Node16; + n = (struct art_node*) n16; + break; + } + case Node48: + { + struct art_node48* n48 = malloc(sizeof(struct art_node48)); + memset(n48, 0, sizeof(struct art_node48)); + n48->node.type = Node48; + n = (struct art_node*) n48; + break; + } + case Node256: + { + struct art_node256* n256 = malloc(sizeof(struct art_node256)); + memset(n256, 0, sizeof(struct art_node256)); + n256->node.type = Node256; + n = (struct art_node*) n256; + break; + } + } + *node = n; +} + +static void +create_art_node4(struct art_node4** node) +{ + struct art_node* n = NULL; + create_art_node(&n, Node4); + *node = (struct art_node4*)n; +} + +static void +create_art_node16(struct art_node16** node) +{ + struct art_node* n = NULL; + create_art_node(&n, Node16); + *node = (struct art_node16*)n; +} + +static void +create_art_node48(struct art_node48** node) +{ + struct art_node* n = NULL; + create_art_node(&n, Node48); + *node = (struct art_node48*)n; +} + +static void +create_art_node256(struct art_node256** node) +{ + struct art_node* n = NULL; + create_art_node(&n, Node256); + *node = (struct art_node256*)n; +} + +static void +destroy_art_node(struct art_node* node) +{ + if (node == NULL) + { + return; + } + if (IS_LEAF(node)) + { + pgagroal_value_destroy(GET_LEAF(node)->value); + free(GET_LEAF(node)); + return; + } + switch (node->type) + { + case Node4: + { + struct art_node4* n = (struct art_node4*) node; + for (int i = 0; i < node->num_children; i++) + { + destroy_art_node(n->children[i]); + } + break; + } + case Node16: + { + struct art_node16* n = (struct art_node16*) node; + for (int i = 0; i < node->num_children; i++) + { + destroy_art_node(n->children[i]); + } + break; + } + case Node48: + { + struct art_node48* n = (struct art_node48*) node; + for (int i = 0; i < 256; i++) + { + int idx = n->keys[i]; + if (idx == 0) + { + continue; + } + destroy_art_node(n->children[idx - 1]); + } + break; + } + + case Node256: + { + struct art_node256* n = (struct art_node256*) node; + for (int i = 0; i < 256; i++) + { + if (n->children[i] == NULL) + { + continue; + } + destroy_art_node(n->children[i]); + } + break; + } + } + free(node); +} + +static struct art_node** +node_get_child(struct art_node* node, unsigned char ch) +{ + switch (node->type) + { + case Node4: + { + struct art_node4* n = (struct art_node4*)node; + int idx = find_index(ch, n->keys, n->node.num_children); + if (idx == -1 || n->keys[idx] != ch) + { + goto error; + } + return &n->children[idx]; + } + case Node16: + { + struct art_node16* n = (struct art_node16*)node; + int idx = find_index(ch, n->keys, n->node.num_children); + if (idx == -1 || n->keys[idx] != ch) + { + goto error; + } + return &n->children[idx]; + } + case Node48: + { + struct art_node48* n = (struct art_node48*)node; + if (n->keys[ch] == 0) + { + goto error; + } + return &n->children[n->keys[ch] - 1]; + } + case Node256: + { + struct art_node256* n = (struct art_node256*)node; + return &n->children[ch]; + } + } +error: + return NULL; +} + +static struct value* +art_node_insert(struct art_node* node, struct art_node** node_ref, uint32_t depth, unsigned char* key, uint32_t key_len, uintptr_t value, enum value_type type, bool* new) +{ + struct art_leaf* leaf = NULL; + struct art_leaf* min_leaf = NULL; + uint32_t idx = 0; + uint32_t diff_len = 0; // where the keys diverge + struct art_node* new_node = NULL; + struct art_node** next = NULL; + unsigned char* leaf_key = NULL; + void* old_val = NULL; + if (node == NULL) + { + // Lazy expansion, skip creating an inner node since it currently will have only this one leaf. + // We will compare keys when reach leaf anyway, the path doesn't need to 100% match the key along the way + create_art_leaf(&leaf, key, key_len, value, type); + *node_ref = SET_LEAF(leaf); + *new = true; + return NULL; + } + // base case, reaching leaf, either replace or expand + if (IS_LEAF(node)) + { + // Lazy expansion, expand the leaf node to an inner node with 2 leaves + // If the key already exists, replace with new value and return old value + if (leaf_match(GET_LEAF(node), key, key_len)) + { + old_val = GET_LEAF(node)->value; + pgagroal_value_create(type, value, &(GET_LEAF(node)->value)); + return old_val; + } + // If the key does not match with existing key, old key and new key diverged some point after depth + // Even if we merely store a partial prefix for each node, it couldn't have diverged before depth. + // The reason is that when we find it diverged outside the partial prefix range, + // we compare with the existing key in the left most leaf and find an exact diverging point to split the node (see details below). + // This way we inductively guarantee that all children to a parent share the same prefix even if it's only partially stored + leaf_key = GET_LEAF(node)->key; + create_art_node(&new_node, Node4); + create_art_leaf(&leaf, key, key_len, value, type); + // Get the diverging index after point of depth + for (idx = depth; idx < min(key_len, GET_LEAF(node)->key_len); idx++) + { + if (key[idx] != leaf_key[idx]) + { + break; + } + if (idx - depth < MAX_PREFIX_LEN) + { + new_node->prefix[idx - depth] = key[idx]; + } + } + new_node->prefix_len = idx - depth; + depth += new_node->prefix_len; + node_add_child(new_node, &new_node, key[depth], SET_LEAF(leaf)); + node_add_child(new_node, &new_node, leaf_key[depth], (void*)node); + // replace with new node + *node_ref = new_node; + *new = true; + return NULL; + } + + // There are several cases, + // 1. The key diverges outside the current prefix (diff_len >= prefix_len) + // 2. The key diverges within the current prefix (diff_len < prefix_len) + // 2.1. The key diverges within the partial prefix range (diff_len < MAX_PREFIX_LEN) + // 2.2. The key diverges outside the partial prefix range (MAX_PREFIX_LEN <= diff_len < prefix_len) + // For case 1, go to the next child to add node recursively, or add leaf to current node in place + // For case 2, split the current node and add child to new node. + // Note that it's tricky to check case 2.2, or in that case know the exact diverging point, + // since we merely store the first 10 (MAX_PREFIX_LEN) bytes of the prefix. + // In this case we use the key in the left most leaf of the node to determine the diverging point. + // Theoretically we inductively guarantee that all children to the same parent share the same prefixes. + // So we can use the key inside any leaf under this node to see if the diverging point goes beyond the current prefix, + // but it's most convenient and efficient to reach the left most key. + + diff_len = check_prefix(node, key, depth, key_len); + if (diff_len < node->prefix_len) + { + // case 2, split the node + create_art_node(&new_node, Node4); + create_art_leaf(&leaf, key, key_len, value, type); + new_node->prefix_len = diff_len; + memcpy(new_node->prefix, node->prefix, min(MAX_PREFIX_LEN, diff_len)); + // We need to know if new bytes that were once outside the partial prefix range will now come into the range + // If original key didn't fill up the partial prefix buffer in the first place, + // no new bytes will come into buffer when prefix shifts left + if (node->prefix_len <= MAX_PREFIX_LEN) + { + node->prefix_len = node->prefix_len - (diff_len + 1); + node_add_child(new_node, &new_node, key[depth + diff_len], SET_LEAF(leaf)); + node_add_child(new_node, &new_node, node->prefix[diff_len], node); + // Update node's prefix info since we move it downwards + // The first diverging character serves as the key byte in keys array, + // so we don't duplicate store it in the prefix. + // In other words, if prefix is the starting point, + // prefix + prefix_len - 1 is the last byte of the prefix, + // prefix + prefix_len is the indexing byte + // prefix + prefix_len + 1 is the starting point of the next prefix + memmove(node->prefix, node->prefix + diff_len + 1, node->prefix_len); + } + else + { + node->prefix_len = node->prefix_len - (diff_len + 1); + min_leaf = node_get_minimum(node); + node_add_child(new_node, &new_node, key[depth + diff_len], SET_LEAF(leaf)); + node_add_child(new_node, &new_node, min_leaf->key[depth + diff_len], node); + // node is moved downwards + memmove(node->prefix, min_leaf->key + depth + diff_len + 1, min(MAX_PREFIX_LEN, node->prefix_len)); + } + // replace + *node_ref = new_node; + *new = true; + return NULL; + } + else + { + // case 1 + depth += node->prefix_len; + next = node_get_child(node, key[depth]); + if (next != NULL) + { + // recursively add node + if (*next == NULL) + { + node->num_children++; + } + return art_node_insert(*next, next, depth + 1, key, key_len, value, type, new); + } + else + { + // add a child to current node since the spot is available + create_art_leaf(&leaf, key, key_len, value, type); + node_add_child(node, node_ref, key[depth], SET_LEAF(leaf)); + *new = true; + return NULL; + } + } +} + +static struct art_leaf* +art_node_delete(struct art_node* node, struct art_node** node_ref, uint32_t depth, unsigned char* key, uint32_t key_len) +{ + struct art_leaf* l = NULL; + struct art_node** child = NULL; + uint32_t diff_len = 0; + if (node == NULL) + { + return NULL; + } + // Only one way we encounter this case, the tree only has one leaf + if (IS_LEAF(node)) + { + if (leaf_match(GET_LEAF(node), key, key_len)) + { + l = GET_LEAF(node); + *node_ref = NULL; + return l; + } + return NULL; + } + diff_len = check_prefix_partial(node, key, depth, key_len); + if (diff_len != min(MAX_PREFIX_LEN, node->prefix_len)) + { + return NULL; + } + else + { + depth += node->prefix_len; + child = node_get_child(node, key[depth]); + if (child == NULL) + { + // dead end + return NULL; + } + if (IS_LEAF(*child)) + { + if (leaf_match(GET_LEAF(*child), key, key_len)) + { + l = GET_LEAF(*child); + node_remove_child(node, node_ref, key[depth]); + return l; + } + else + { + return NULL; + } + } + else + { + return art_node_delete(*child, child, depth + 1, key, key_len); + } + } +} + +static int +art_node_iterate(struct art_node* node, art_callback cb, void* data) +{ + struct art_leaf* l = NULL; + struct art_node* child = NULL; + int idx = 0; + int res = 0; + if (node == NULL) + { + return 0; + } + if (IS_LEAF(node)) + { + l = GET_LEAF(node); + return cb(data, l->key, l->key_len, l->value); + } + switch (node->type) + { + case Node4: + { + struct art_node4* n = (struct art_node4*) node; + for (int i = 0; i < node->num_children; i++) + { + child = n->children[i]; + res = art_node_iterate(child, cb, data); + if (res) + { + return res; + } + } + break; + } + case Node16: + { + struct art_node16* n = (struct art_node16*) node; + for (int i = 0; i < node->num_children; i++) + { + child = n->children[i]; + res = art_node_iterate(child, cb, data); + if (res) + { + return res; + } + } + break; + } + case Node48: + { + struct art_node48* n = (struct art_node48*) node; + for (int i = 0; i < 256; i++) + { + idx = n->keys[i]; + if (idx == 0) + { + continue; + } + child = n->children[idx - 1]; + res = art_node_iterate(child, cb, data); + if (res) + { + return res; + } + } + break; + } + case Node256: + { + struct art_node256* n = (struct art_node256*) node; + for (int i = 0; i < 256; i++) + { + if (n->children[i] == NULL) + { + continue; + } + child = n->children[i]; + res = art_node_iterate(child, cb, data); + if (res) + { + return res; + } + } + break; + } + } + return 0; +} + +static void +node_add_child(struct art_node* node, struct art_node** node_ref, unsigned char ch, void* child) +{ + switch (node->type) + { + case Node4: + node4_add_child((struct art_node4*) node, node_ref, ch, child); + break; + case Node16: + node16_add_child((struct art_node16*) node, node_ref, ch, child); + break; + case Node48: + node48_add_child((struct art_node48*) node, node_ref, ch, child); + break; + case Node256: + node256_add_child((struct art_node256*) node, ch, child); + break; + } +} + +static void +node4_add_child(struct art_node4* node, struct art_node** node_ref, unsigned char ch, void* child) +{ + if (node->node.num_children < 4) + { + int idx = find_index(ch, node->keys, node->node.num_children); + // right shift the right part to make space for the key, so that we keep the keys in order + memmove(node->keys + (idx + 1) + 1, node->keys + (idx + 1), node->node.num_children - (idx + 1)); + memmove(node->children + (idx + 1) + 1, node->children + (idx + 1), (node->node.num_children - (idx + 1)) * sizeof(void*)); + + node->keys[idx + 1] = ch; + node->children[idx + 1] = (struct art_node*)child; + node->node.num_children++; + } + else + { + // expand + struct art_node16* new_node = NULL; + create_art_node16(&new_node); + copy_header((struct art_node*)new_node, (struct art_node*)node); + memcpy(new_node->children, node->children, node->node.num_children * sizeof(void*)); + memcpy(new_node->keys, node->keys, node->node.num_children); + // replace the node through node reference + *node_ref = (struct art_node*)new_node; + free(node); + + node16_add_child(new_node, node_ref, ch, child); + } +} + +static void +node16_add_child(struct art_node16* node, struct art_node** node_ref, unsigned char ch, void* child) +{ + if (node->node.num_children < 16) + { + int idx = find_index(ch, node->keys, node->node.num_children); + // right shift the right part to make space for the key, so that we keep the keys in order + memmove(node->keys + (idx + 1) + 1, node->keys + (idx + 1), node->node.num_children - (idx + 1)); + memmove(node->children + (idx + 1) + 1, node->children + (idx + 1), (node->node.num_children - (idx + 1)) * sizeof(void*)); + + node->keys[idx + 1] = ch; + node->children[idx + 1] = (struct art_node*)child; + node->node.num_children++; + } + else + { + // expand + struct art_node48* new_node = NULL; + create_art_node48(&new_node); + copy_header((struct art_node*)new_node, (struct art_node*)node); + memcpy(new_node->children, node->children, node->node.num_children * sizeof(void*)); + for (int i = 0; i < node->node.num_children; i++) + { + new_node->keys[node->keys[i]] = i + 1; + } + // replace the node through node reference + *node_ref = (struct art_node*)new_node; + free(node); + node48_add_child(new_node, node_ref, ch, child); + } +} + +static void +node48_add_child(struct art_node48* node, struct art_node** node_ref, unsigned char ch, void* child) +{ + if (node->node.num_children < 48) + { + // we cannot simply append to last because delete could have caused fragmentation + int pos = 0; + while (node->children[pos] != NULL) + { + pos++; + } + node->children[pos] = (struct art_node*) child; + node->keys[ch] = pos + 1; + node->node.num_children++; + } + else + { + // expand + struct art_node256* new_node = NULL; + create_art_node256(&new_node); + copy_header((struct art_node*)new_node, (struct art_node*)node); + for (int i = 0; i < 256; i++) + { + if (node->keys[i] == 0) + { + continue; + } + new_node->children[i] = node->children[node->keys[i] - 1]; + } + // replace the node through node reference + *node_ref = (struct art_node*)new_node; + free(node); + node256_add_child(new_node, ch, child); + } +} + +static void +node256_add_child(struct art_node256* node, unsigned char ch, void* child) +{ + node->node.num_children++; + node->children[ch] = (struct art_node*)child; +} + +static int +find_index(unsigned char ch, const unsigned char* keys, int length) +{ + int left = 0; + int right = length - 1; + int mid = 0; + if (length == 0) + { + return -1; + } + while (left + 1 < right) + { + mid = (left + right) / 2; + if (keys[mid] == ch) + { + return mid; + } + if (keys[mid] < ch) + { + left = mid; + } + else + { + right = mid; + } + } + if (keys[right] <= ch) + { + return right; + } + else if (keys[left] <= ch) + { + return left; + } + return -1; +} + +static void +copy_header(struct art_node* dest, struct art_node* src) +{ + dest->num_children = src->num_children; + dest->prefix_len = src->prefix_len; + memcpy(dest->prefix, src->prefix, min(MAX_PREFIX_LEN, src->prefix_len)); +} + +static uint32_t +check_prefix_partial(struct art_node* node, unsigned char* key, uint32_t depth, uint32_t key_len) +{ + uint32_t len = 0; + uint32_t max_cmp = min(min(node->prefix_len, MAX_PREFIX_LEN), key_len - depth); + while (len < max_cmp && key[depth + len] == node->prefix[len]) + { + len++; + } + return len; +} + +static uint32_t +check_prefix(struct art_node* node, unsigned char* key, uint32_t depth, uint32_t key_len) +{ + uint32_t len = 0; + struct art_leaf* leaf = NULL; + uint32_t max_cmp = min(min(node->prefix_len, MAX_PREFIX_LEN), key_len - depth); + while (len < max_cmp && key[depth + len] == node->prefix[len]) + { + len++; + } + // diverge within partial prefix range + if (len < MAX_PREFIX_LEN) + { + return len; + } + + leaf = node_get_minimum(node); + max_cmp = min(leaf->key_len, key_len) - depth; + // continue comparing the real keys + while (len < max_cmp && leaf->key[depth + len] == key[depth + len]) + { + len++; + } + return len; +} + +static bool +leaf_match(struct art_leaf* leaf, unsigned char* key, uint32_t key_len) +{ + if (leaf->key_len != key_len) + { + return false; + } + return memcmp(leaf->key, key, key_len) == 0; +} + +static struct art_leaf* +node_get_minimum(struct art_node* node) +{ + if (node == NULL) + { + return NULL; + } + while (node != NULL && !IS_LEAF(node)) + { + switch (node->type) + { + case Node4: + { + struct art_node4* n = (struct art_node4*)node; + node = n->children[0]; + break; + } + case Node16: + { + struct art_node16* n = (struct art_node16*)node; + node = n->children[0]; + break; + } + case Node48: + { + struct art_node48* n = (struct art_node48*) node; + int idx = 0; + while (n->keys[idx] == 0) + { + idx++; + } + node = n->children[n->keys[idx] - 1]; + break; + } + case Node256: + { + struct art_node256* n = (struct art_node256*) node; + int idx = 0; + while (n->children[idx] == NULL) + { + idx++; + } + node = n->children[idx]; + break; + } + } + } + if (node == NULL) + { + return NULL; + } + return GET_LEAF(node); +} + +static void +node_remove_child(struct art_node* node, struct art_node** node_ref, unsigned char ch) +{ + switch (node->type) + { + case Node4: + node4_remove_child((struct art_node4*)node, node_ref, ch); + break; + case Node16: + node16_remove_child((struct art_node16*)node, node_ref, ch); + break; + case Node48: + node48_remove_child((struct art_node48*)node, node_ref, ch); + break; + case Node256: + node256_remove_child((struct art_node256*)node, node_ref, ch); + break; + } +} + +static void +node4_remove_child(struct art_node4* node, struct art_node** node_ref, unsigned char ch) +{ + int idx = 0; + uint32_t len = 0; + struct art_node* child = NULL; + idx = find_index(ch, node->keys, node->node.num_children); + memmove(node->keys + idx, node->keys + idx + 1, node->node.num_children - (idx + 1)); + memmove(node->children + idx, node->children + idx + 1, sizeof(void*) * (node->node.num_children - (idx + 1))); + node->node.num_children--; + // path compression, merge the node with its child + if (node->node.num_children == 1) + { + child = node->children[0]; + if (IS_LEAF(child)) + { + // replace directly + *node_ref = child; + return; + } + // parent prefix bytes + byte index to child + child prefix bytes + len = node->node.prefix_len; + if (len < MAX_PREFIX_LEN) + { + node->node.prefix[len] = node->keys[0]; + len++; + } + // keep filling as much as we can + for (uint32_t i = 0; len + i < MAX_PREFIX_LEN && i < child->prefix_len; i++) + { + node->node.prefix[len + i] = child->prefix[i]; + } + child->prefix_len = node->node.prefix_len + 1 + child->prefix_len; + memcpy(child->prefix, node->node.prefix, min(child->prefix_len, MAX_PREFIX_LEN)); + free(node); + // replace + *node_ref = child; + } +} + +static void +node16_remove_child(struct art_node16* node, struct art_node** node_ref, unsigned char ch) +{ + int idx = 0; + struct art_node4* new_node = NULL; + idx = find_index(ch, node->keys, node->node.num_children); + memmove(node->keys + idx, node->keys + idx + 1, node->node.num_children - (idx + 1)); + memmove(node->children + idx, node->children + idx + 1, sizeof(void*) * (node->node.num_children - (idx + 1))); + node->node.num_children--; + // downgrade node + // Trick from libart, do not downgrade immediately to avoid jumping on 4/5 boundary + if (node->node.num_children <= 3) + { + create_art_node4(&new_node); + copy_header((struct art_node*)new_node, (struct art_node*)node); + memcpy(new_node->keys, node->keys, node->node.num_children); + memcpy(new_node->children, node->children, node->node.num_children * sizeof(void*)); + free(node); + *node_ref = (struct art_node*)new_node; + } +} + +static void +node48_remove_child(struct art_node48* node, struct art_node** node_ref, unsigned char ch) +{ + int idx = node->keys[ch]; + int cnt = 0; + struct art_node16* new_node = NULL; + node->children[idx - 1] = NULL; + node->keys[ch] = 0; + node->node.num_children--; + + if (node->node.num_children <= 12) + { + create_art_node16(&new_node); + copy_header((struct art_node*)new_node, (struct art_node*)node); + for (int i = 0; i < 256; i++) + { + if (node->keys[i] != 0) + { + new_node->children[cnt] = node->children[node->keys[i] - 1]; + new_node->keys[cnt] = i; + cnt++; + } + } + free(node); + *node_ref = (struct art_node*)new_node; + } +} + +static void +node256_remove_child(struct art_node256* node, struct art_node** node_ref, unsigned char ch) +{ + int num = 0; + for (int i = 0; i < 48; i++) + { + if (node->children[i] != NULL) + { + num++; + } + } + if (num != node->node.num_children) + { + num++; + } + struct art_node48* new_node = NULL; + int cnt = 0; + node->children[ch] = NULL; + node->node.num_children--; + + if (node->node.num_children <= 37) + { + create_art_node48(&new_node); + copy_header((struct art_node*)new_node, (struct art_node*)node); + for (int i = 0; i < 256; i++) + { + if (node->children[i] != NULL) + { + new_node->keys[i] = cnt + 1; + new_node->children[cnt] = node->children[i]; + cnt++; + } + } + free(node); + *node_ref = (struct art_node*)new_node; + } +} + +int +pgagroal_art_iterator_create(struct art* t, struct art_iterator** iter) +{ + struct art_iterator* i = NULL; + if (t == NULL) + { + return 1; + } + i = malloc(sizeof(struct art_iterator)); + i->count = 0; + i->tree = t; + i->key = NULL; + i->value = NULL; + pgagroal_deque_create(false, &i->que); + *iter = i; + return 0; +} + +bool +pgagroal_art_iterator_next(struct art_iterator* iter) +{ + struct deque* que = NULL; + struct art* tree = NULL; + struct art_node* node = NULL; + struct art_node* child = NULL; + int idx = 0; + if (iter == NULL || iter->que == NULL || iter->tree == NULL || iter->count == iter->tree->size) + { + return false; + } + que = iter->que; + tree = iter->tree; + if (iter->count == 0) + { + pgagroal_deque_add(que, NULL, (uintptr_t)tree->root, ValueRef); + } + while (!pgagroal_deque_empty(que)) + { + node = (struct art_node*)pgagroal_deque_poll(que, NULL); + if (IS_LEAF(node)) + { + iter->count++; + iter->key = GET_LEAF(node)->key; + iter->value = GET_LEAF(node)->value; + return true; + } + switch (node->type) + { + case Node4: + { + struct art_node4* n = (struct art_node4*) node; + for (int i = 0; i < node->num_children; i++) + { + child = n->children[i]; + pgagroal_deque_add(que, NULL, (uintptr_t)child, ValueRef); + } + break; + } + case Node16: + { + struct art_node16* n = (struct art_node16*) node; + for (int i = 0; i < node->num_children; i++) + { + child = n->children[i]; + pgagroal_deque_add(que, NULL, (uintptr_t)child, ValueRef); + } + break; + } + case Node48: + { + struct art_node48* n = (struct art_node48*) node; + for (int i = 0; i < 256; i++) + { + idx = n->keys[i]; + if (idx == 0) + { + continue; + } + child = n->children[idx - 1]; + pgagroal_deque_add(que, NULL, (uintptr_t)child, ValueRef); + } + break; + } + case Node256: + { + struct art_node256* n = (struct art_node256*) node; + for (int i = 0; i < 256; i++) + { + if (n->children[i] == NULL) + { + continue; + } + child = n->children[i]; + pgagroal_deque_add(que, NULL, (uintptr_t)child, ValueRef); + } + break; + } + } + } + return false; +} + +void +pgagroal_art_iterator_destroy(struct art_iterator* iter) +{ + if (iter == NULL) + { + return; + } + pgagroal_deque_destroy(iter->que); + free(iter); +} + +void +pgagroal_art_destroy_value_noop(void* val) +{ + (void)val; +} + +void +pgagroal_art_destroy_value_default(void* val) +{ + free(val); +} + +static struct value* +art_search(struct art* t, unsigned char* key, uint32_t key_len) +{ + struct art_node* node = NULL; + struct art_node** child = NULL; + uint32_t depth = 0; + if (t == NULL || t->root == NULL) + { + return NULL; + } + node = t->root; + while (node != NULL) + { + if (IS_LEAF(node)) + { + if (!leaf_match(GET_LEAF(node), key, key_len)) + { + return NULL; + } + return GET_LEAF(node)->value; + } + // optimistically check the prefix, + // we move forward as long as up to MAX_PREFIX_LEN characters match + if (check_prefix_partial(node, key, depth, key_len) != min(node->prefix_len, MAX_PREFIX_LEN)) + { + return NULL; + } + depth += node->prefix_len; + // you can't dereference what the function returns directly since it could be null + child = node_get_child(node, key[depth]); + node = child != NULL ? *child : NULL; + // child is indexed by key[depth], so the next round we should skip this byte and start checking at the next + depth++; + } + return NULL; +} + +static int +art_to_json_string_cb(void* param, const unsigned char* key, uint32_t key_len, struct value* value) +{ + struct to_string_param* p = (struct to_string_param*) param; + char* str = NULL; + char* tag = NULL; + p->cnt++; + bool has_next = p->cnt < p->t->size; + tag = pgagroal_append_char(tag, '"'); + tag = pgagroal_append(tag, (char*)key); + tag = pgagroal_append_char(tag, '"'); + tag = pgagroal_append(tag, ": "); + str = pgagroal_value_to_string(value, FORMAT_JSON, tag, p->indent); + free(tag); + p->str = pgagroal_append(p->str, str); + p->str = pgagroal_append(p->str, has_next ? ",\n" : "\n"); + + free(str); + return 0; +} + +static int +art_to_text_string_cb(void* param, const unsigned char* key, uint32_t key_len, struct value* value) +{ + struct to_string_param* p = (struct to_string_param*) param; + char* str = NULL; + char* tag = NULL; + p->cnt++; + bool has_next = p->cnt < p->t->size; + tag = pgagroal_append(tag, (char*)key); + tag = pgagroal_append(tag, ": "); + if (value->type == ValueJSON) + { + tag = pgagroal_append(tag, "\n"); + } + if (pgagroal_compare_string(p->tag, BULLET_POINT)) + { + if (p->cnt == 1) + { + str = pgagroal_value_to_string(value, FORMAT_TEXT, tag, 0); + } + else + { + str = pgagroal_value_to_string(value, FORMAT_TEXT, tag, p->indent + INDENT_PER_LEVEL); + } + } + else + { + str = pgagroal_value_to_string(value, FORMAT_TEXT, tag, p->indent); + } + free(tag); + p->str = pgagroal_append(p->str, str); + p->str = pgagroal_append(p->str, has_next ? "\n" : ""); + + free(str); + return 0; +} + +static char* +to_json_string(struct art* t, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + if (t == NULL || t->size == 0) + { + ret = pgagroal_append(ret, "{}"); + return ret; + } + ret = pgagroal_append(ret, "{\n"); + struct to_string_param param = { + .indent = indent + INDENT_PER_LEVEL, + .str = ret, + .t = t, + .cnt = 0, + }; + art_iterate(t, art_to_json_string_cb, ¶m); + ret = param.str; + ret = pgagroal_indent(ret, NULL, indent); + ret = pgagroal_append(ret, "}"); + return ret; +} + +static char* +to_text_string(struct art* t, char* tag, int indent) +{ + char* ret = NULL; + int next_indent = indent; + if (tag != NULL && !pgagroal_compare_string(tag, BULLET_POINT)) + { + ret = pgagroal_indent(ret, tag, indent); + next_indent += INDENT_PER_LEVEL; + } + if (t == NULL || t->size == 0) + { + ret = pgagroal_append(ret, "{}"); + return ret; + } + struct to_string_param param = { + .indent = next_indent, + .str = ret, + .t = t, + .cnt = 0, + .tag = tag + }; + art_iterate(t, art_to_text_string_cb, ¶m); + ret = param.str; + return ret; +} + +static int +art_iterate(struct art* t, art_callback cb, void* data) +{ + return art_node_iterate(t->root, cb, data); +} \ No newline at end of file diff --git a/src/libpgagroal/deque.c b/src/libpgagroal/deque.c new file mode 100644 index 00000000..37e436f3 --- /dev/null +++ b/src/libpgagroal/deque.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include +#include + +// tag is copied if not NULL +static void +deque_offer(struct deque* deque, char* tag, uintptr_t data, enum value_type type); + +// tag is copied if not NULL +static void +deque_node_create(uintptr_t data, enum value_type type, char* tag, struct deque_node** node); + +// tag will always be freed +static void +deque_node_destroy(struct deque_node* node); + +static void +deque_read_lock(struct deque* deque); + +static void +deque_write_lock(struct deque* deque); + +static void +deque_unlock(struct deque* deque); + +static struct deque_node* +deque_next(struct deque* deque, struct deque_node* node); + +static struct deque_node* +deque_find(struct deque* deque, char* tag); + +static char* +to_json_string(struct deque* deque, char* tag, int indent); + +static char* +to_text_string(struct deque* deque, char* tag, int indent); + +static struct deque_node* +deque_remove(struct deque* deque, struct deque_node* node); + +int +pgagroal_deque_create(bool thread_safe, struct deque** deque) +{ + struct deque* q = NULL; + q = malloc(sizeof(struct deque)); + q->size = 0; + q->thread_safe = thread_safe; + if (thread_safe) + { + pthread_rwlock_init(&q->mutex, NULL); + } + deque_node_create(0, ValueInt32, NULL, &q->start); + deque_node_create(0, ValueInt32, NULL, &q->end); + q->start->next = q->end; + q->end->prev = q->start; + *deque = q; + return 0; +} + +int +pgagroal_deque_add(struct deque* deque, char* tag, uintptr_t data, enum value_type type) +{ + deque_offer(deque, tag, data, type); + return 0; +} + +uintptr_t +pgagroal_deque_poll(struct deque* deque, char** tag) +{ + struct deque_node* head = NULL; + struct value* val = NULL; + uintptr_t data = 0; + if (deque == NULL || pgagroal_deque_size(deque) == 0) + { + return 0; + } + deque_write_lock(deque); + head = deque->start->next; + // this should not happen when size is not 0, but just in case + if (head == deque->end) + { + deque_unlock(deque); + return 0; + } + // remove node + deque->start->next = head->next; + head->next->prev = deque->start; + deque->size--; + val = head->data; + if (tag != NULL) + { + *tag = head->tag; + } + free(head); + + data = pgagroal_value_data(val); + free(val); + + deque_unlock(deque); + return data; +} + +uintptr_t +pgagroal_deque_peek(struct deque* deque, char** tag) +{ + struct deque_node* head = NULL; + struct value* val = NULL; + if (deque == NULL || pgagroal_deque_size(deque) == 0) + { + return 0; + } + deque_read_lock(deque); + head = deque->start->next; + // this should not happen when size is not 0, but just in case + if (head == deque->end) + { + deque_unlock(deque); + return 0; + } + val = head->data; + if (tag != NULL) + { + *tag = head->tag; + } + deque_unlock(deque); + return pgagroal_value_data(val); +} + +uintptr_t +pgagroal_deque_get(struct deque* deque, char* tag) +{ + struct deque_node* n = NULL; + uintptr_t ret = 0; + deque_read_lock(deque); + n = deque_find(deque, tag); + if (n == NULL) + { + goto error; + } + ret = pgagroal_value_data(n->data); + deque_unlock(deque); + return ret; +error: + deque_unlock(deque); + return 0; +} + +bool +pgagroal_deque_empty(struct deque* deque) +{ + return pgagroal_deque_size(deque) == 0; +} + +void +pgagroal_deque_list(struct deque* deque) +{ + char* str = NULL; + if (pgagroal_log_is_enabled(PGAGROAL_LOGGING_LEVEL_DEBUG5)) + { + str = pgagroal_deque_to_string(deque, FORMAT_JSON, NULL, 0); + pgagroal_log_trace("Deque: %s", str); + free(str); + } +} + +void +pgagroal_deque_destroy(struct deque* deque) +{ + struct deque_node* n = NULL; + struct deque_node* next = NULL; + if (deque == NULL) + { + return; + } + n = deque->start; + while (n != NULL) + { + next = n->next; + deque_node_destroy(n); + n = next; + } + if (deque->thread_safe) + { + pthread_rwlock_destroy(&deque->mutex); + } + free(deque); +} + +char* +pgagroal_deque_to_string(struct deque* deque, int32_t format, char* tag, int indent) +{ + if (format == FORMAT_JSON) + { + return to_json_string(deque, tag, indent); + } + else if (format == FORMAT_TEXT) + { + return to_text_string(deque, tag, indent); + } + return NULL; +} + +uint32_t +pgagroal_deque_size(struct deque* deque) +{ + uint32_t size = 0; + if (deque == NULL) + { + return 0; + } + deque_read_lock(deque); + size = deque->size; + deque_unlock(deque); + return size; +} + +int +pgagroal_deque_iterator_create(struct deque* deque, struct deque_iterator** iter) +{ + struct deque_iterator* i = NULL; + if (deque == NULL) + { + return 1; + } + i = malloc(sizeof(struct deque_iterator)); + i->deque = deque; + i->cur = deque->start; + i->tag = NULL; + i->value = NULL; + *iter = i; + return 0; +} + +void +pgagroal_deque_iterator_remove(struct deque_iterator* iter) +{ + if (iter == NULL || iter->cur == NULL || iter->deque == NULL || + iter->cur == iter->deque->start || iter->cur == iter->deque->end) + { + return; + } + iter->cur = deque_remove(iter->deque, iter->cur); + if (iter->cur == iter->deque->start) + { + iter->value = NULL; + iter->tag = NULL; + return; + } + iter->value = iter->cur->data; + iter->tag = iter->cur->tag; + return; +} + +void +pgagroal_deque_iterator_destroy(struct deque_iterator* iter) +{ + if (iter == NULL) + { + return; + } + free(iter); +} + +bool +pgagroal_deque_iterator_next(struct deque_iterator* iter) +{ + if (iter == NULL) + { + return false; + } + iter->cur = deque_next(iter->deque, iter->cur); + if (iter->cur == NULL) + { + return false; + } + iter->value = iter->cur->data; + iter->tag = iter->cur->tag; + return true; +} + +static void +deque_offer(struct deque* deque, char* tag, uintptr_t data, enum value_type type) +{ + struct deque_node* n = NULL; + struct deque_node* last = NULL; + deque_node_create(data, type, tag, &n); + deque_write_lock(deque); + deque->size++; + last = deque->end->prev; + last->next = n; + n->prev = last; + n->next = deque->end; + deque->end->prev = n; + deque_unlock(deque); +} + +static void +deque_node_create(uintptr_t data, enum value_type type, char* tag, struct deque_node** node) +{ + struct deque_node* n = NULL; + n = malloc(sizeof(struct deque_node)); + memset(n, 0, sizeof(struct deque_node)); + pgagroal_value_create(type, data, &n->data); + if (tag != NULL) + { + n->tag = malloc(strlen(tag) + 1); + strcpy(n->tag, tag); + } + else + { + n->tag = NULL; + } + *node = n; +} + +static void +deque_node_destroy(struct deque_node* node) +{ + if (node == NULL) + { + return; + } + pgagroal_value_destroy(node->data); + free(node->tag); + free(node); +} + +static void +deque_read_lock(struct deque* deque) +{ + if (deque == NULL || !deque->thread_safe) + { + return; + } + pthread_rwlock_rdlock(&deque->mutex); +} + +static void +deque_write_lock(struct deque* deque) +{ + if (deque == NULL || !deque->thread_safe) + { + return; + } + pthread_rwlock_wrlock(&deque->mutex); +} + +static void +deque_unlock(struct deque* deque) +{ + if (deque == NULL || !deque->thread_safe) + { + return; + } + pthread_rwlock_unlock(&deque->mutex); +} + +static struct deque_node* +deque_next(struct deque* deque, struct deque_node* node) +{ + struct deque_node* next = NULL; + if (deque == NULL || deque->size == 0 || node == NULL) + { + return NULL; + } + if (node->next == deque->end) + { + return NULL; + } + next = node->next; + return next; +} + +static struct deque_node* +deque_find(struct deque* deque, char* tag) +{ + struct deque_node* n = NULL; + if (tag == NULL || strlen(tag) == 0 || deque == NULL || deque->size == 0) + { + return NULL; + } + n = deque_next(deque, deque->start); + + while (n != NULL) + { + if (pgagroal_compare_string(tag, n->tag)) + { + return n; + } + + n = deque_next(deque, n); + } + return NULL; +} + +static char* +to_json_string(struct deque* deque, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + struct deque_node* cur = NULL; + if (deque == NULL || pgagroal_deque_empty(deque)) + { + ret = pgagroal_append(ret, "[]"); + return ret; + } + deque_read_lock(deque); + ret = pgagroal_append(ret, "[\n"); + cur = deque_next(deque, deque->start); + while (cur != NULL) + { + bool has_next = cur->next != deque->end; + char* str = NULL; + char* t = NULL; + if (cur->tag != NULL) + { + t = pgagroal_append(t, cur->tag); + t = pgagroal_append(t, ": "); + } + str = pgagroal_value_to_string(cur->data, FORMAT_JSON, t, indent + INDENT_PER_LEVEL); + free(t); + ret = pgagroal_append(ret, str); + ret = pgagroal_append(ret, has_next ? ",\n" : "\n"); + free(str); + cur = deque_next(deque, cur); + } + ret = pgagroal_indent(ret, NULL, indent); + ret = pgagroal_append(ret, "]"); + deque_unlock(deque); + return ret; +} + +static char* +to_text_string(struct deque* deque, char* tag, int indent) +{ + char* ret = NULL; + int cnt = 0; + int next_indent = pgagroal_compare_string(tag, BULLET_POINT) ? 0 : indent; + // we have a tag and it's not the bullet point, so that means another line + if (tag != NULL && !pgagroal_compare_string(tag, BULLET_POINT)) + { + ret = pgagroal_indent(ret, tag, indent); + next_indent += INDENT_PER_LEVEL; + } + struct deque_node* cur = NULL; + if (deque == NULL || pgagroal_deque_empty(deque)) + { + ret = pgagroal_append(ret, "[]"); + return ret; + } + deque_read_lock(deque); + cur = deque_next(deque, deque->start); + while (cur != NULL) + { + bool has_next = cur->next != deque->end; + char* str = NULL; + str = pgagroal_value_to_string(cur->data, FORMAT_TEXT, BULLET_POINT, next_indent); + if (cnt == 0) + { + cnt++; + if (pgagroal_compare_string(tag, BULLET_POINT)) + { + next_indent = indent + INDENT_PER_LEVEL; + } + } + if (cur->data->type == ValueJSON) + { + ret = pgagroal_indent(ret, BULLET_POINT, next_indent); + } + ret = pgagroal_append(ret, str); + ret = pgagroal_append(ret, has_next ? "\n" : ""); + free(str); + cur = deque_next(deque, cur); + } + deque_unlock(deque); + return ret; +} + +static struct deque_node* +deque_remove(struct deque* deque, struct deque_node* node) +{ + if (deque == NULL || node == NULL || node == deque->start || node == deque->end) + { + return NULL; + } + struct deque_node* prev = node->prev; + struct deque_node* next = node->next; + prev->next = next; + next->prev = prev; + deque_node_destroy(node); + deque->size--; + return prev; +} diff --git a/src/libpgagroal/json.c b/src/libpgagroal/json.c index bd5ac403..ede7fded 100644 --- a/src/libpgagroal/json.c +++ b/src/libpgagroal/json.c @@ -28,255 +28,560 @@ /* pgagroal */ #include +#include +#include #include +#include +#include +/* System */ +#include #include +#include +#include -cJSON* -pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name, char* executable_version) -{ - // root of the JSON structure - cJSON* json = cJSON_CreateObject(); +static bool type_allowed(enum value_type type); +static char* item_to_string(struct json* item, int32_t format, char* tag, int indent); +static char* array_to_string(struct json* array, int32_t format, char* tag, int indent); +static int parse_string(char* str, uint64_t* index, struct json** obj); +static int json_add(struct json* obj, char* key, uintptr_t val, enum value_type type); +static int fill_value(char* str, char* key, uint64_t* index, struct json* o); +static bool value_start(char ch); - if (!json) +int +pgagroal_json_append(struct json* array, uintptr_t entry, enum value_type type) +{ + if (array != NULL && array->type == JSONUnknown) { - goto error; + array->type = JSONArray; + pgagroal_deque_create(false, (struct deque**)&array->elements); } - - // the command structure - cJSON* command = cJSON_CreateObject(); - if (!command) + if (array == NULL || array->type != JSONArray || !type_allowed(type)) { goto error; } + return pgagroal_deque_add(array->elements, NULL, entry, type); +error: + return 1; +} - // insert meta-data about the command - cJSON_AddStringToObject(command, JSON_TAG_COMMAND_NAME, command_name); - cJSON_AddStringToObject(command, JSON_TAG_COMMAND_STATUS, success ? JSON_STRING_SUCCESS : JSON_STRING_ERROR); - cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_ERROR, success ? JSON_BOOL_SUCCESS : JSON_BOOL_ERROR); - cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_EXIT_STATUS, success ? 0 : EXIT_STATUS_DATA_ERROR); - - // the output of the command, this has to be filled by the caller - cJSON* output = cJSON_CreateObject(); - if (!output) +int +pgagroal_json_put(struct json* item, char* key, uintptr_t val, enum value_type type) +{ + if (item != NULL && item->type == JSONUnknown) + { + item->type = JSONItem; + pgagroal_art_create((struct art**)&item->elements); + } + if (item == NULL || item->type != JSONItem || !type_allowed(type) || key == NULL || strlen(key) == 0) { goto error; } + return pgagroal_art_insert((struct art*)item->elements, (unsigned char*)key, strlen(key) + 1, val, type); +error: + return 1; +} - cJSON_AddItemToObject(command, JSON_TAG_COMMAND_OUTPUT, output); +int +pgagroal_json_create(struct json** object) +{ + struct json* o = malloc(sizeof(struct json)); + memset(o, 0, sizeof(struct json)); + o->type = JSONUnknown; + *object = o; + return 0; +} - // who has launched the command ? - cJSON* application = cJSON_CreateObject(); - if (!application) +int +pgagroal_json_destroy(struct json* object) +{ + if (object == NULL) { - goto error; + return 0; } - - long minor = strtol(&executable_version[2], NULL, 10); - if (errno == ERANGE || minor <= LONG_MIN || minor >= LONG_MAX) + if (object->type == JSONArray) { - goto error; + pgagroal_deque_destroy(object->elements); } - long patch = strtol(&executable_version[5], NULL, 10); - if (errno == ERANGE || patch <= LONG_MIN || patch >= LONG_MAX) + else if (object->type == JSONItem) { - goto error; + pgagroal_art_destroy(object->elements); } + free(object); + return 0; +} - cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_NAME, executable_name); - cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MAJOR, executable_version[0] - '0'); - cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MINOR, (int)minor); - cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_PATCH, (int)patch); - cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_VERSION, executable_version); - - // add objects to the whole json thing - cJSON_AddItemToObject(json, "command", command); - cJSON_AddItemToObject(json, "application", application); - - return json; - -error: - if (json) +char* +pgagroal_json_to_string(struct json* object, int32_t format, char* tag, int indent) +{ + char* str = NULL; + if (object == NULL || (object->type == JSONUnknown || object->elements == NULL)) { - cJSON_Delete(json); + str = pgagroal_indent(str, tag, indent); + if (format == FORMAT_JSON) + { + str = pgagroal_append(str, "{}"); + } + return str; } + if (object->type != JSONArray) + { + return item_to_string(object, format, tag, indent); + } + else + { + return array_to_string(object, format, tag, indent); + } +} - return NULL; - +void +pgagroal_json_print(struct json* object, int32_t format) +{ + char* str = pgagroal_json_to_string(object, format, NULL, 0); + printf("%s\n", str); + free(str); } -cJSON* -pgagroal_json_extract_command_output_object(cJSON* json) +uint32_t +pgagroal_json_array_length(struct json* array) { - cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); - if (!command) + if (array == NULL || array->type != JSONArray) { goto error; } - - return cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_OUTPUT); - + return pgagroal_deque_size(array->elements); error: - return NULL; - + return 0; } -bool -pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name) +uintptr_t +pgagroal_json_get(struct json* item, char* tag) { - if (!json || !command_name || strlen(command_name) <= 0) + if (item == NULL || item->type != JSONItem || tag == NULL || strlen(tag) == 0) { - goto error; + return 0; } + return pgagroal_art_search(item->elements, (unsigned char*)tag, strlen(tag) + 1); +} - cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); - if (!command) +int +pgagroal_json_iterator_create(struct json* object, struct json_iterator** iter) +{ + struct json_iterator* i = NULL; + if (object == NULL || object->type == JSONUnknown) { - goto error; + return 1; } - - cJSON* cName = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_NAME); - if (!cName || !cJSON_IsString(cName) || !cName->valuestring) + i = malloc(sizeof (struct json_iterator)); + memset(i, 0, sizeof (struct json_iterator)); + i->obj = object; + if (object->type == JSONItem) { - goto error; + pgagroal_art_iterator_create(object->elements, (struct art_iterator**)(&i->iter)); } - - return !strncmp(command_name, - cName->valuestring, - MISC_LENGTH); - -error: - return false; + else + { + pgagroal_deque_iterator_create(object->elements, (struct deque_iterator**)(&i->iter)); + } + *iter = i; + return 0; } -int -pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status) +void +pgagroal_json_iterator_destroy(struct json_iterator* iter) { - if (!json) + if (iter == NULL) { - goto error; + return; } - - cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); - if (!command) + if (iter->obj->type == JSONArray) { - goto error; + pgagroal_deque_iterator_destroy((struct deque_iterator*)iter->iter); } - - cJSON* current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); - if (!current) + else { - goto error; + pgagroal_art_iterator_destroy((struct art_iterator*)iter->iter); } + free(iter); +} - cJSON_SetValuestring(current, message); +bool +pgagroal_json_iterator_next(struct json_iterator* iter) +{ + bool has_next = false; + if (iter == NULL || iter->iter == NULL) + { + return false; + } + if (iter->obj->type == JSONArray) + { + has_next = pgagroal_deque_iterator_next((struct deque_iterator*)iter->iter); + if (has_next) + { + iter->value = ((struct deque_iterator*)iter->iter)->value; + } + } + else + { + has_next = pgagroal_art_iterator_next((struct art_iterator*)iter->iter); + if (has_next) + { + iter->value = ((struct art_iterator*)iter->iter)->value; + iter->key = (char*)((struct art_iterator*)iter->iter)->key; + } + } + return has_next; +} - current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); - if (!current) +int +pgagroal_json_parse_string(char* str, struct json** obj) +{ + uint64_t idx = 0; + if (str == NULL || strlen(str) < 2) { - goto error; + return 1; } - cJSON_SetIntValue(current, JSON_BOOL_ERROR); // cannot use cJSON_SetBoolValue unless cJSON >= 1.7.16 + return parse_string(str, &idx, obj); +} - current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); - if (!current) +int +pgagroal_json_clone(struct json* from, struct json** to) +{ + struct json* o = NULL; + char* str = NULL; + str = pgagroal_json_to_string(from, FORMAT_JSON, NULL, 0); + if (pgagroal_json_parse_string(str, &o)) { goto error; } - - cJSON_SetIntValue(current, exit_status); - + *to = o; + free(str); return 0; - error: + free(str); return 1; - } -bool -pgagroal_json_is_command_object_faulty(cJSON* json) +static int +parse_string(char* str, uint64_t* index, struct json** obj) { - if (!json) + enum json_type type; + struct json* o = NULL; + uint64_t idx = *index; + char ch = str[idx]; + char* key = NULL; + uint64_t len = strlen(str); + + if (ch == '{') { - goto error; + type = JSONItem; } - - cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); - if (!command) + else if (ch == '[') { - goto error; + type = JSONArray; } - - cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); - if (!status || !cJSON_IsNumber(status)) + else { goto error; } + idx++; + pgagroal_json_create(&o); + if (type == JSONItem) + { + while (idx < len) + { + // pre key + while (idx < len && isspace(str[idx])) + { + idx++; + } + if (idx == len) + { + goto error; + } + if (str[idx] == ',') + { + idx++; + } + else if (str[idx] == '}') + { + idx++; + break; + } + else if (!(str[idx] == '"' && o->type == JSONUnknown)) + { + // if it's first key we won't see comma, otherwise we must see comma + goto error; + } + while (idx < len && str[idx] != '"') + { + idx++; + } + if (idx == len) + { + goto error; + } + idx++; + // The key + while (idx < len && str[idx] != '"') + { + key = pgagroal_append_char(key, str[idx++]); + } + if (idx == len || key == NULL) + { + goto error; + } + // The lands between + while (idx < len && (str[idx] == '"' || isspace(str[idx]))) + { + idx++; + } + if (idx == len || str[idx] != ':') + { + goto error; + } + while (idx < len && (str[idx] == ':' || isspace(str[idx]))) + { + idx++; + } + if (idx == len) + { + goto error; + } + // The value + if (fill_value(str, key, &idx, o)) + { + goto error; + } + free(key); + key = NULL; + } + } + else + { + while (idx < len) + { + while (idx < len && isspace(str[idx])) + { + idx++; + } + if (idx == len) + { + goto error; + } + if (str[idx] == ',') + { + idx++; + } + else if (str[idx] == ']') + { + idx++; + break; + } + else if (!(value_start(str[idx]) && o->type == JSONUnknown)) + { + // if it's first key we won't see comma, otherwise we must see comma + goto error; + } + while (idx < len && !value_start(str[idx])) + { + idx++; + } + if (idx == len) + { + goto error; + } + + if (fill_value(str, key, &idx, o)) + { + goto error; + } + } + } - return status->valueint == JSON_BOOL_SUCCESS ? false : true; - + *index = idx; + *obj = o; + return 0; error: - return false; - + pgagroal_json_destroy(o); + free(key); + return 1; } -int -pgagroal_json_command_object_exit_status(cJSON* json) +static int +json_add(struct json* obj, char* key, uintptr_t val, enum value_type type) { - if (!json) + if (obj == NULL) { - goto error; + return 1; } - - cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); - if (!command) - { - goto error; - } - - cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); - if (!status || !cJSON_IsNumber(status)) + if (key == NULL) { - goto error; + return pgagroal_json_append(obj, val, type); } + return pgagroal_json_put(obj, key, val, type); +} - return status->valueint; - -error: - return EXIT_STATUS_DATA_ERROR; +static bool +value_start(char ch) +{ + return (isdigit(ch) || ch == '-' || ch == '+') || // number + (ch == '[') || // array + (ch == '{') || // item + (ch == '"' || ch == 'n') || // string or null string + (ch == 't' || ch == 'f'); // potential boolean value } -const char* -pgagroal_json_get_command_object_status(cJSON* json) +static int +fill_value(char* str, char* key, uint64_t* index, struct json* o) { - if (!json) + uint64_t idx = *index; + uint64_t len = strlen(str); + if (str[idx] == '"') { - goto error; + char* val = NULL; + idx++; + while (idx < len && str[idx] != '"') + { + val = pgagroal_append_char(val, str[idx++]); + } + if (idx == len) + { + goto error; + } + if (val == NULL) + { + json_add(o, key, (uintptr_t)"", ValueString); + } + else + { + json_add(o, key, (uintptr_t)val, ValueString); + } + idx++; + free(val); } - - cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); - if (!command) + else if (str[idx] == '-' || str[idx] == '+' || isdigit(str[idx])) { - goto error; + bool has_digit = false; + char* val_str = NULL; + while (idx < len && (isdigit(str[idx]) || str[idx] == '.' || str[idx] == '-' || str[idx] == '+')) + { + if (str[idx] == '.') + { + has_digit = true; + } + val_str = pgagroal_append_char(val_str, str[idx++]); + } + if (has_digit) + { + double val = 0.; + if (sscanf(val_str, "%lf", &val) != 1) + { + free(val_str); + goto error; + } + json_add(o, key, pgagroal_value_from_double(val), ValueDouble); + free(val_str); + } + else + { + int64_t val = 0; + if (sscanf(val_str, "%" PRId64, &val) != 1) + { + free(val_str); + goto error; + } + json_add(o, key, (uintptr_t)val, ValueInt64); + free(val_str); + } } - - cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); - if (!cJSON_IsString(status) || (status->valuestring == NULL)) + else if (str[idx] == '{') + { + struct json* val = NULL; + if (parse_string(str, &idx, &val)) + { + goto error; + } + json_add(o, key, (uintptr_t)val, ValueJSON); + } + else if (str[idx] == '[') + { + struct json* val = NULL; + if (parse_string(str, &idx, &val)) + { + goto error; + } + json_add(o, key, (uintptr_t)val, ValueJSON); + } + else if (str[idx] == 'n' || str[idx] == 't' || str[idx] == 'f') + { + char* val = NULL; + while (idx < len && str[idx] >= 'a' && str[idx] <= 'z') + { + val = pgagroal_append_char(val, str[idx++]); + } + if (pgagroal_compare_string(val, "null")) + { + json_add(o, key, 0, ValueString); + } + else if (pgagroal_compare_string(val, "true")) + { + json_add(o, key, true, ValueBool); + } + else if (pgagroal_compare_string(val, "false")) + { + json_add(o, key, false, ValueBool); + } + else + { + free(val); + goto error; + } + free(val); + } + else { goto error; } - - return status->valuestring; + *index = idx; + return 0; error: - return NULL; + return 1; +} +static bool +type_allowed(enum value_type type) +{ + switch (type) + { + case ValueInt8: + case ValueUInt8: + case ValueInt16: + case ValueUInt16: + case ValueInt32: + case ValueUInt32: + case ValueInt64: + case ValueUInt64: + case ValueBool: + case ValueString: + case ValueFloat: + case ValueDouble: + case ValueJSON: + return true; + default: + return false; + } } -int -pgagroal_json_print_and_free_json_object(cJSON* json) +static char* +item_to_string(struct json* item, int32_t format, char* tag, int indent) { - int status = pgagroal_json_command_object_exit_status(json); - printf("%s\n", cJSON_Print(json)); - cJSON_Delete(json); - return status; + return pgagroal_art_to_string(item->elements, format, tag, indent); } + +static char* +array_to_string(struct json* array, int32_t format, char* tag, int indent) +{ + return pgagroal_deque_to_string(array->elements, format, tag, indent); +} + diff --git a/src/libpgagroal/logging.c b/src/libpgagroal/logging.c index c11d3097..8cec5a06 100644 --- a/src/libpgagroal/logging.c +++ b/src/libpgagroal/logging.c @@ -514,3 +514,18 @@ pgagroal_log_mem(void* data, size_t size) } } } + +bool +pgagroal_log_is_enabled(int level) +{ + struct configuration* config; + + config = (struct configuration*)shmem; + + if (level >= config->log_level) + { + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 1e598220..cc259b12 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -38,6 +38,7 @@ #include /* system */ +#include #include #include #include @@ -55,6 +56,33 @@ #define MANAGEMENT_HEADER_SIZE 5 #define MANAGEMENT_INFO_SIZE 13 +/** + * JSON related command tags, used to build and retrieve + * a JSON piece of information related to a single command + */ +#define JSON_TAG_COMMAND "command" +#define JSON_TAG_COMMAND_NAME "name" +#define JSON_TAG_COMMAND_STATUS "status" +#define JSON_TAG_COMMAND_ERROR "error" +#define JSON_TAG_COMMAND_OUTPUT "output" +#define JSON_TAG_COMMAND_EXIT_STATUS "exit-status" + +#define JSON_TAG_APPLICATION_NAME "name" +#define JSON_TAG_APPLICATION_VERSION_MAJOR "major" +#define JSON_TAG_APPLICATION_VERSION_MINOR "minor" +#define JSON_TAG_APPLICATION_VERSION_PATCH "patch" +#define JSON_TAG_APPLICATION_VERSION "version" + +#define JSON_TAG_ARRAY_NAME "list" + +/** + * JSON pre-defined values + */ +#define JSON_STRING_SUCCESS "OK" +#define JSON_STRING_ERROR "KO" +#define JSON_BOOL_SUCCESS 0 +#define JSON_BOOL_ERROR 1 + #define S "S:" #define V ",V:" @@ -80,6 +108,14 @@ static int pgagroal_executable_version_number(char* version, size_t version_size static int pgagroal_executable_version_string(char** version_string, int version_number); static char* pgagroal_executable_name(int command); +static cJSON* pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name, char* executable_version); +static cJSON* pgagroal_json_extract_command_output_object(cJSON* json); +static int pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status); +static const char* pgagroal_json_get_command_object_status(cJSON* json); +static bool pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name); +static int pgagroal_json_print_and_free_json_object(cJSON* json); +static int pgagroal_json_command_object_exit_status(cJSON* json); + int pgagroal_management_read_header(int socket, signed char* id, int32_t* slot) { @@ -2881,3 +2917,226 @@ pgagroal_management_json_print_conf_ls(cJSON* json) return status; } + +static cJSON* +pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name, char* executable_version) +{ + // root of the JSON structure + cJSON* json = cJSON_CreateObject(); + + if (!json) + { + goto error; + } + + // the command structure + cJSON* command = cJSON_CreateObject(); + if (!command) + { + goto error; + } + + // insert meta-data about the command + cJSON_AddStringToObject(command, JSON_TAG_COMMAND_NAME, command_name); + cJSON_AddStringToObject(command, JSON_TAG_COMMAND_STATUS, success ? JSON_STRING_SUCCESS : JSON_STRING_ERROR); + cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_ERROR, success ? JSON_BOOL_SUCCESS : JSON_BOOL_ERROR); + cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_EXIT_STATUS, success ? 0 : EXIT_STATUS_DATA_ERROR); + + // the output of the command, this has to be filled by the caller + cJSON* output = cJSON_CreateObject(); + if (!output) + { + goto error; + } + + cJSON_AddItemToObject(command, JSON_TAG_COMMAND_OUTPUT, output); + + // who has launched the command ? + cJSON* application = cJSON_CreateObject(); + if (!application) + { + goto error; + } + + long minor = strtol(&executable_version[2], NULL, 10); + if (errno == ERANGE || minor <= LONG_MIN || minor >= LONG_MAX) + { + goto error; + } + long patch = strtol(&executable_version[5], NULL, 10); + if (errno == ERANGE || patch <= LONG_MIN || patch >= LONG_MAX) + { + goto error; + } + + cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_NAME, executable_name); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MAJOR, executable_version[0] - '0'); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MINOR, (int)minor); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_PATCH, (int)patch); + cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_VERSION, executable_version); + + // add objects to the whole json thing + cJSON_AddItemToObject(json, "command", command); + cJSON_AddItemToObject(json, "application", application); + + return json; + +error: + if (json) + { + cJSON_Delete(json); + } + + return NULL; + +} + +static cJSON* +pgagroal_json_extract_command_output_object(cJSON* json) +{ + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + return cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_OUTPUT); + +error: + return NULL; + +} + +static bool +pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name) +{ + if (!json || !command_name || strlen(command_name) <= 0) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* cName = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_NAME); + if (!cName || !cJSON_IsString(cName) || !cName->valuestring) + { + goto error; + } + + return !strncmp(command_name, + cName->valuestring, + MISC_LENGTH); + +error: + return false; +} + +static int +pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); + if (!current) + { + goto error; + } + + cJSON_SetValuestring(current, message); + + current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); + if (!current) + { + goto error; + } + + cJSON_SetIntValue(current, JSON_BOOL_ERROR); // cannot use cJSON_SetBoolValue unless cJSON >= 1.7.16 + + current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); + if (!current) + { + goto error; + } + + cJSON_SetIntValue(current, exit_status); + + return 0; + +error: + return 1; + +} + +static int +pgagroal_json_command_object_exit_status(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); + if (!status || !cJSON_IsNumber(status)) + { + goto error; + } + + return status->valueint; + +error: + return EXIT_STATUS_DATA_ERROR; +} + +static const char* +pgagroal_json_get_command_object_status(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); + if (!cJSON_IsString(status) || (status->valuestring == NULL)) + { + goto error; + } + + return status->valuestring; +error: + return NULL; + +} + +static int +pgagroal_json_print_and_free_json_object(cJSON* json) +{ + int status = pgagroal_json_command_object_exit_status(json); + printf("%s\n", cJSON_Print(json)); + cJSON_Delete(json); + return status; +} diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index 36bcac40..67e53e87 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -1121,3 +1121,43 @@ pgagroal_server_state_as_string(signed char state) return buf; } } + +char* +pgagroal_append_char(char* orig, char c) +{ + char str[2]; + + memset(&str[0], 0, sizeof(str)); + snprintf(&str[0], 2, "%c", c); + orig = pgagroal_append(orig, str); + + return orig; +} + +char* +pgagroal_indent(char* str, char* tag, int indent) +{ + for (int i = 0; i < indent; i++) + { + str = pgagroal_append(str, " "); + } + if (tag != NULL) + { + str = pgagroal_append(str, tag); + } + return str; +} + +bool +pgagroal_compare_string(const char* str1, const char* str2) +{ + if (str1 == NULL && str2 == NULL) + { + return true; + } + if ((str1 == NULL && str2 != NULL) || (str1 != NULL && str2 == NULL)) + { + return false; + } + return strcmp(str1, str2) == 0; +} \ No newline at end of file diff --git a/src/libpgagroal/value.c b/src/libpgagroal/value.c new file mode 100644 index 00000000..6f6ca9d2 --- /dev/null +++ b/src/libpgagroal/value.c @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* pgagroal */ +#include +#include +#include +#include +#include + +/* System */ +#include +#include +#include +#include + +static void noop_destroy_cb(uintptr_t data); +static void free_destroy_cb(uintptr_t data); +static void art_destroy_cb(uintptr_t data); +static void deque_destroy_cb(uintptr_t data); +static void json_destroy_cb(uintptr_t data); +static char* noop_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* int8_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* uint8_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* int16_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* uint16_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* int32_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* uint32_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* int64_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* uint64_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* float_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* double_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* string_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* char_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* bool_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* deque_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* art_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); +static char* json_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent); + +int +pgagroal_value_create(enum value_type type, uintptr_t data, struct value** value) +{ + struct value* val = NULL; + val = (struct value*) malloc(sizeof(struct value)); + if (val == NULL) + { + goto error; + } + val->data = 0; + val->type = type; + switch (type) + { + case ValueInt8: + val->to_string = int8_to_string_cb; + break; + case ValueUInt8: + val->to_string = uint8_to_string_cb; + break; + case ValueInt16: + val->to_string = int16_to_string_cb; + break; + case ValueUInt16: + val->to_string = uint16_to_string_cb; + break; + case ValueInt32: + val->to_string = int32_to_string_cb; + break; + case ValueUInt32: + val->to_string = uint32_to_string_cb; + break; + case ValueInt64: + val->to_string = int64_to_string_cb; + break; + case ValueUInt64: + val->to_string = uint64_to_string_cb; + break; + case ValueFloat: + val->to_string = float_to_string_cb; + break; + case ValueDouble: + val->to_string = double_to_string_cb; + break; + case ValueBool: + val->to_string = bool_to_string_cb; + break; + case ValueChar: + val->to_string = char_to_string_cb; + break; + case ValueString: + val->to_string = string_to_string_cb; + break; + case ValueJSON: + val->to_string = json_to_string_cb; + break; + case ValueDeque: + val->to_string = deque_to_string_cb; + break; + case ValueART: + val->to_string = art_to_string_cb; + break; + default: + val->to_string = noop_to_string_cb; + break; + } + switch (type) + { + case ValueString: + { + char* orig = NULL; + char* str = NULL; + + orig = (char*) data; + if (orig != NULL) + { + str = pgagroal_append(str, orig); + } + + val->data = (uintptr_t) str; + val->destroy_data = free_destroy_cb; + break; + } + case ValueJSON: + val->data = data; + val->destroy_data = json_destroy_cb; + break; + case ValueDeque: + val->data = data; + val->destroy_data = deque_destroy_cb; + break; + case ValueART: + val->data = data; + val->destroy_data = art_destroy_cb; + break; + default: + val->data = data; + val->destroy_data = noop_destroy_cb; + break; + } + *value = val; + return 0; + +error: + return 1; +} + +int +pgagroal_value_destroy(struct value* value) +{ + if (value == NULL) + { + return 0; + } + value->destroy_data(value->data); + free(value); + return 0; +} + +uintptr_t +pgagroal_value_data(struct value* value) +{ + if (value == NULL) + { + return 0; + } + return value->data; +} + +char* +pgagroal_value_to_string(struct value* value, int32_t format, char* tag, int indent) +{ + return value->to_string(value->data, format, tag, indent); +} + +uintptr_t +pgagroal_value_from_double(double val) +{ + union duni + { + double val; + uintptr_t data; + }; + union duni uni; + uni.val = val; + return uni.data; +} + +double +pgagroal_value_to_double(uintptr_t val) +{ + union duni + { + double val; + uintptr_t data; + }; + union duni uni; + uni.data = val; + return uni.val; +} + +uintptr_t +pgagroal_value_from_float(float val) +{ + union funi + { + float val; + uintptr_t data; + }; + union funi uni; + uni.val = val; + return uni.data; +} + +float +pgagroal_value_to_float(uintptr_t val) +{ + union funi + { + float val; + uintptr_t data; + }; + union funi uni; + uni.data = val; + return uni.val; +} + +static void +noop_destroy_cb(uintptr_t data) +{ + (void) data; +} + +static void +free_destroy_cb(uintptr_t data) +{ + free((void*) data); +} + +static void +art_destroy_cb(uintptr_t data) +{ + pgagroal_art_destroy((struct art*) data); +} + +static void +deque_destroy_cb(uintptr_t data) +{ + pgagroal_deque_destroy((struct deque*) data); +} + +static void +json_destroy_cb(uintptr_t data) +{ + pgagroal_json_destroy((struct json*) data); +} + +static char* +noop_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + (void) data; + (void) tag; + (void) indent; + (void) format; + return NULL; +} + +static char* +int8_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRId8, (int8_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +uint8_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRIu8, (uint8_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +int16_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRId16, (int16_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +uint16_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRIu16, (uint16_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +int32_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRId32, (int32_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +uint32_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRIu32, (uint32_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +int64_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRId64, (int64_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +uint64_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%" PRIu64, (uint64_t)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +float_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%f", pgagroal_value_to_float(data)); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +double_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "%f", pgagroal_value_to_double(data)); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +string_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char* str = (char*) data; + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + if (str == NULL) + { + if (format == FORMAT_JSON) + { + snprintf(buf, MISC_LENGTH, "null"); + } + } + else if (strlen(str) == 0) + { + if (format == FORMAT_JSON) + { + snprintf(buf, MISC_LENGTH, "\"%s\"", str); + } + else if (format == FORMAT_TEXT) + { + snprintf(buf, MISC_LENGTH, "''"); + } + } + else + { + if (format == FORMAT_JSON) + { + snprintf(buf, MISC_LENGTH, "\"%s\"", str); + } + else if (format == FORMAT_TEXT) + { + snprintf(buf, MISC_LENGTH, "%s", str); + } + } + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +bool_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + bool val = (bool) data; + ret = pgagroal_append(ret, val?"true":"false"); + return ret; +} + +static char* +char_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + char* ret = NULL; + ret = pgagroal_indent(ret, tag, indent); + char buf[MISC_LENGTH]; + memset(buf, 0, MISC_LENGTH); + snprintf(buf, MISC_LENGTH, "'%c'", (char)data); + ret = pgagroal_append(ret, buf); + return ret; +} + +static char* +deque_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + return pgagroal_deque_to_string((struct deque*)data, format, tag, indent); +} + +static char* +art_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + return pgagroal_art_to_string((struct art*) data, format, tag, indent); +} + +static char* +json_to_string_cb(uintptr_t data, int32_t format, char* tag, int indent) +{ + return pgagroal_json_to_string((struct json*)data, format, tag, indent); +} \ No newline at end of file