#include "postgres.h" #include "utils/memutils.h" #include "pljs.h" /** * @brief #HTAB hash table for storing a #plvs_context_cache_value `user_id`. * * A javascript context is stored here by `user_id`. This allows for * javascript contexts to be separated by postgres user, keeping a * copy of the current javascript context and all functions defined in the * javascript context by `fn_oid`. */ HTAB *pljs_context_HashTable = NULL; /** * @brief #MemoryContext where all cached memory is allocated. */ MemoryContext cache_memory_context = NULL; /** * @brief Initializes the cache #HTAB along with the #MemoryContext * where cached memory is allocated. */ void pljs_cache_init(void) { // Create the memory context to store pljs_context_cache_value entries // along with memory allocations for the hashed values themselves. cache_memory_context = AllocSetContextCreate(TopMemoryContext, "PLJS Function and Context Cache", ALLOCSET_SMALL_SIZES); // Initialize context cache. HASHCTL context_ctl = {0}; // Key size for contexts, we're storing by user_id, which is an Oid. context_ctl.keysize = sizeof(Oid); context_ctl.entrysize = sizeof(pljs_context_cache_value); context_ctl.hcxt = cache_memory_context; // We pass 64 as an arbitrary maximum number of roles to store // cached contexts for. If we exceed this, it will expand the // hash table. pljs_context_HashTable = hash_create("PLJS Context Cache", 64, // Arbitrary guess at number of users/roles to cache. &context_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } /** * @brief Clears all caches and recreates them. */ void pljs_cache_reset(void) { hash_destroy(pljs_context_HashTable); MemoryContextDelete(cache_memory_context); pljs_cache_init(); } /** * @brief Adds a #pljs_context_cache_value for a `user_id`. * * Creates a #pljs_context_cache_value and fills it with the * javascript context and #HTAB storing #pljs_function_cache_value * entries by `fn_oid`. * * @param user_id #Oid - user ID for the #JSContext to be assigned to * @param ctx #JSContext - context to be added to the cache */ void pljs_cache_context_add(Oid user_id, JSContext *ctx) { bool found; // Ask for an empty #pljs_context_cache_value to fill. pljs_context_cache_value *hvalue = (pljs_context_cache_value *)hash_search( pljs_context_HashTable, (void *)&user_id, HASH_ENTER, &found); // If it found that means we're trying to create a context that // already exists for a `user_id`. This should never happen. if (found) { ereport( ERROR, errcode(ERRCODE_INTERNAL_ERROR), errmsg("a context cache entry already exists for user_id %d", user_id)); } hvalue->ctx = ctx; hvalue->user_id = user_id; HASHCTL function_ctl = {0}; // Create a #MemoryContext to store the function data. hvalue->function_memory_context = AllocSetContextCreate(cache_memory_context, "PLJS Function Cache Context", ALLOCSET_SMALL_SIZES); // The key is the `fn_oid`, so an #Oid. function_ctl.keysize = sizeof(Oid); function_ctl.entrysize = sizeof(pljs_function_cache_value); function_ctl.hcxt = hvalue->function_memory_context; hvalue->ctx = ctx; // Create a hash table for #pljs_function_cache_value entries, // stored by `fn_oid`. hvalue->function_hash_table = hash_create("PLJS Function Cache", 128, // Arbitrary guess at functions per user. &function_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } /** * @brief Removes a #pljs_context_cache_value for a `user_id`. * * Removes a cache entry from the cache by `user_id`. * @param user_id #Oid - user ID for which to remove the cache entries */ void pljs_cache_context_remove(Oid user_id) { bool found; pljs_context_cache_value *hvalue = (pljs_context_cache_value *)hash_search( pljs_context_HashTable, (void *)&user_id, HASH_REMOVE, &found); if (hvalue) { // Destroys the cache and its #MemoryContext in the process. hash_destroy(hvalue->function_hash_table); } } /** * @brief Finds a #pljs_context_cache_value for a `user_id`. * * @param user_id #Oid * @returns #pljs_context_cache_value that is found, or `NULL` if not found */ pljs_context_cache_value *pljs_cache_context_find(Oid user_id) { pljs_context_cache_value *value = (pljs_context_cache_value *)hash_search( pljs_context_HashTable, (void *)&user_id, HASH_FIND, NULL); return value; } /** * @brief Adds a javascript function in the cache for a `user_id` and `fn_oid`. * * Adds a function by creating a #pljs_function_cache_value and populating * it from a #pljs_context. * @param context Pointer to #pljs_context */ void pljs_cache_function_add(pljs_context *context) { bool found; pljs_context_cache_value *ctx_hvalue = (pljs_context_cache_value *)hash_search(pljs_context_HashTable, &context->function->user_id, HASH_FIND, &found); // If we are unable to find a context for the `user_id`, then that // is probably a bad sign and we should error out. if (!found) { ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), errmsg("unable to find context for user %d", context->function->user_id)); } // Ask the cache to create e new entry. pljs_function_cache_value *hvalue = (pljs_function_cache_value *)hash_search( ctx_hvalue->function_hash_table, &context->function->fn_oid, HASH_ENTER, &found); // If we found one, then we already have an entry for this function // and something has gone wrong, we should error out. if (found) { ereport(ERROR, errcode(ERRCODE_INTERNAL_ERROR), errmsg("function cache entry already exists for oid %d", context->function->fn_oid)); } // Switch to the cache memory context for this javascript context. MemoryContext old_memory_context = MemoryContextSwitchTo(ctx_hvalue->function_memory_context); // Fill the cache entry with the values in the context. pljs_context_to_function_cache(hvalue, context); // Switch back to the calling memory context. MemoryContextSwitchTo(old_memory_context); } /** * @brief Finds a #pljs_function_cache_value for a `user_id` and `fn_oid`. * * @param user_id #Oid * @param fn_oid #Oid * @returns #pljs_function_cache_value that is found, or `NULL` if not found */ pljs_function_cache_value *pljs_cache_function_find(Oid user_id, Oid fn_oid) { bool found; // Search for the context. pljs_context_cache_value *ctx_hvalue = (pljs_context_cache_value *)hash_search(pljs_context_HashTable, &user_id, HASH_FIND, &found); // If the context does not exists in the cache, that's probably ok. if (!found) { return NULL; } // Search for the function inside of the context. pljs_function_cache_value *value = (pljs_function_cache_value *)hash_search( ctx_hvalue->function_hash_table, &fn_oid, HASH_FIND, &found); return value; } /** * @brief Fills a #pljs_context from a #pljs_function_cache_value. * * Copies the data from a pljs context to a function cache value. * @param context Pointer to #pljs_context to copy from * @param function_entry Pointer to #pljs_function_cache_value to fill */ void pljs_function_cache_to_context(pljs_context *context, pljs_function_cache_value *function_entry) { context->ctx = function_entry->ctx; context->js_function = function_entry->fn; context->function = (pljs_func *)palloc(sizeof(pljs_func)); context->function->fn_oid = function_entry->fn_oid; context->function->user_id = function_entry->user_id; context->function->trigger = function_entry->trigger; context->function->is_srf = function_entry->is_srf; context->function->typeclass = function_entry->typeclass; context->js_function = function_entry->fn; context->function->inargs = function_entry->nargs; for (int i = 0; i < function_entry->nargs; i++) { context->function->argtypes[i] = function_entry->argtypes[i]; context->function->argmodes[i] = function_entry->argmodes[i]; } memcpy(context->function->proname, function_entry->proname, NAMEDATALEN); context->function->prosrc = (char *)palloc(NAMEDATALEN); memcpy(context->function->prosrc, function_entry->prosrc, NAMEDATALEN); } /** * @brief Fills a #pljs_function_cache_value from a #pljs_context * * Copies the data from a function cache value to a pljs context. * @param function_entry Pointer to #pljs_function_cache_value to copy from * @param context Pointer to #pljs_context to fill */ void pljs_context_to_function_cache(pljs_function_cache_value *function_entry, pljs_context *context) { MemoryContext old_context = MemoryContextSwitchTo(cache_memory_context); function_entry->ctx = context->ctx; function_entry->fn_oid = context->function->fn_oid; function_entry->user_id = context->function->user_id; function_entry->trigger = context->function->trigger; function_entry->is_srf = context->function->is_srf; function_entry->typeclass = context->function->typeclass; function_entry->fn = context->js_function; function_entry->nargs = context->function->inargs; for (int i = 0; i < function_entry->nargs; i++) { function_entry->argtypes[i] = context->function->argtypes[i]; function_entry->argmodes[i] = context->function->argmodes[i]; } memcpy(function_entry->proname, context->function->proname, NAMEDATALEN); function_entry->prosrc = (char *)palloc(strlen(context->function->prosrc) + 1); memcpy(function_entry->prosrc, context->function->prosrc, strlen(context->function->prosrc)); MemoryContextSwitchTo(old_context); }