#include #include #include "postgres.h" #include "fmgr.h" #include "funcapi.h" #include "commands/prepare.h" /* format_timeval: Write a string representation of $tv into $buf, writing at most $max bytes. Return the number of bytes written, excluding the null terminator. On failure, return a negative value. */ static int format_timeval(char *buf, const size_t max, const struct timeval *const tv) { struct tm tm; size_t ret, s; if (!gmtime_r(&(tv->tv_sec), &tm)) return -1; if ((ret = strftime(buf, max, "%F %H:%M:%S", &tm)) < 0) return -1; /* Overwrite strftime's null terminator: The ($buf += $ret) and ($max - $ret) are correct: $ret is the number of characters written by strftime *excluding* the null terminator. Furthermore, we don't need to check for overflow here - it is guaranteed by strftime that (ret < max). In the extreme case of (ret = max - 1), (max - ret = 1), (buf + ret = buf + max - 1), and (buf[ret] = buf[max - 1] = '\0'), so snprintf simply overwrites the null terminator and returns. */ if ((s = snprintf(buf + ret, max - ret, ".%.3lu UTC", tv->tv_usec / 1000)) < 0) return -1; return (ret + s); } /* pg_prepared_query_plans: This function returns a set of records which, collectively, represent the state of the $prepared_queries hash table. */ PG_FUNCTION_INFO_V1(pg_prepared_query_plans); Datum pg_prepared_query_plans(PG_FUNCTION_ARGS) { TupleDesc tupdesc; TupleTableSlot *slot; AttInMetadata *attinmeta; FuncCallContext *funcctx; PreparedStatement *e; HASH_SEQ_STATUS *hash_status; HTAB *prepared_queries; /* First call: Set-returning functions act as forward iterators - they are called repeatedly, with each call returning a successive member of the set. If this is the first call, we need to set up our hash table iterator and gather the type information for our return tuple. */ if (SRF_IS_FIRSTCALL()) { MemoryContext old_context; funcctx = SRF_FIRSTCALL_INIT(); /* Locate prepared_queries hash table: Use a function from command/prepare.h to get a pointer to the prepared queries hash table. If the table hasn't been allocated yet, no prepares have been issued yet, and we have nothing to do. */ prepared_queries = FetchPreparedStatementHtab(); if (!prepared_queries) SRF_RETURN_DONE(funcctx); /* Switch memory context: By default, each function invocation gets an ephermeral memory pool which is freed upon its return. In order for us to allocate memory that will persist across multiple function invocations, we need to allocate from a different pool. Postgres provides one for us. */ old_context = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* Build tuple description and return slot: To return a composite type (e.g. a tuple), we need to fetch type information for the composite type, and allocate a return "slot" for the composite type instance to be placed in after it is built. We save the slot, as well as the composite type metadata - we need the latter to build the instance, and the former to return it. */ tupdesc = RelationNameGetTupleDesc("pg_prepared_query_plan"); attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = attinmeta; slot = TupleDescGetSlot(tupdesc); funcctx->slot = slot; /* Set up hash table iterator: The hash table iterator is the only state we need to keep across function calls. We store this in the provided "user context" pointer of Postgres' function call context structure. */ hash_status = (HASH_SEQ_STATUS *) palloc(sizeof(HASH_SEQ_STATUS)); hash_seq_init(hash_status, prepared_queries); funcctx->user_fctx = (void *) hash_status; /* Switch back: The remainder of memory allocations will use the ephemeral pool. */ MemoryContextSwitchTo(old_context); } /* Per-call setup: For every call, pull our hash iterator and tuple description out of the function call context - we need them to return results. */ funcctx = SRF_PERCALL_SETUP(); slot = funcctx->slot; attinmeta = funcctx->attinmeta; hash_status = (HASH_SEQ_STATUS *) funcctx->user_fctx; /* Finally, return a result: Iterate forward one hash entry, format a tuple, and return it. */ if ((e = (PreparedStatement *) hash_seq_search(hash_status)) != NULL) { char **values; HeapTuple tuple; Datum result; values = (char **) palloc(5 * sizeof(char *)); values[0] = e->stmt_name; values[1] = e->query_string; values[2] = palloc(16 * sizeof(char)); snprintf(values[2], 16, "%lu", e->exec_count); values[3] = palloc(32 * sizeof(char)); if (format_timeval(values[3], 32, &e->create_time) < 0) elog(ERROR, "unable to format create_time for output"); values[4] = palloc(32 * sizeof(char)); if (format_timeval(values[4], 32, &e->access_time) < 0) elog(ERROR, "unable to format access_time for output"); /* Create tuple */ tuple = BuildTupleFromCStrings(attinmeta, values); /* Bind to return slot */ result = TupleGetDatum(slot, tuple); /* Don't free $values: The memory pool cleanup will take care of this automatically. */ SRF_RETURN_NEXT(funcctx, result); } SRF_RETURN_DONE(funcctx); }