blob: f7190b45d6a6b48982d2e913a8571d31b270737d [file] [log] [blame]
/* ----------------------------------------------------------------------- *//**
*
* @file SystemInformation_impl.hpp
*
*//* ----------------------------------------------------------------------- */
#ifndef MADLIB_POSTGRES_SYSTEMINFORMATION_IMPL_HPP
#define MADLIB_POSTGRES_SYSTEMINFORMATION_IMPL_HPP
namespace madlib {
namespace dbconnector {
namespace postgres {
namespace {
/**
* @brief Initialize an OID-to-data hash table
*/
inline
void
initializeOidHashTable(HTAB*& ioHashTable, MemoryContext inCacheContext,
size_t inEntrySize, const char* inTabName, uint32_t inMaxNElems) {
if (ioHashTable == NULL) {
HASHCTL ctl;
ctl.keysize = sizeof(Oid);
ctl.entrysize = inEntrySize;
ctl.hash = oid_hash;
ctl.hcxt = inCacheContext;
ioHashTable = madlib_hash_create(
/* tabname -- a name for the table (for debugging purposes) */
inTabName,
/* nelem -- maximum number of elements expected */
inMaxNElems,
/* info: additional table parameters, as indicated by flags */
&ctl,
/* flags -- bitmask indicating which parameters to take from *info */
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT
);
}
}
/**
* @brief Store cached system-catalog information in backend function handle
*
* @param inFmgrInfo Backend handle to the function
* @param inSysInfo Our system-catalog information that should be stored
*
* A set-returning function uses \c fn_extra to store cross-call information.
* See, e.g., init_MultiFuncCall() in funcapi.c. Fortunately, it stores a point
* to a <tt>struct FuncCallContext</tt> in \c fn_extra, which in turn allows to
* store user-defined data.
*/
inline
void
setSystemInformationInFmgrInfo(FmgrInfo* inFmgrInfo,
SystemInformation* inSysInfo) {
(inFmgrInfo->fn_retset
? static_cast<FuncCallContext*>(inFmgrInfo->fn_extra)->user_fctx
: inFmgrInfo->fn_extra)
= inSysInfo;
}
/**
* @brief Get cached system-catalog information from backend function handle
*
* @see setSystemInformationInFmgrInfo()
*/
inline
SystemInformation*
getSystemInformationFromFmgrInfo(FmgrInfo* inFmgrInfo) {
return static_cast<SystemInformation*>(inFmgrInfo->fn_retset
? static_cast<FuncCallContext*>(inFmgrInfo->fn_extra)->user_fctx
: inFmgrInfo->fn_extra);
}
/**
* @brief Get memory context from backend function handle
*
* The memory context returned may be used for storing user-defined data. In
* SystemInformation::get(), we will use it for allocating a
* <tt>struct SystemInformation</tt>.
*
* @see setSystemInformationInFmgrInfo()
*/
inline
MemoryContext
getMemoryContextFromFmgrInfo(FmgrInfo* inFmgrInfo) {
return inFmgrInfo->fn_retset
? static_cast<FuncCallContext*>(inFmgrInfo->fn_extra)
->multi_call_memory_ctx
: inFmgrInfo->fn_mcxt;
}
} // namespace
/**
* @brief Get (and cache) information from the PostgreSQL catalog
*
* @param inFmgrInfo System-catalog information about the function. If no
* SystemInformation is currently available in struct FmgrInfo, this
* function will store it (and thus change the FmgrInfo struct).
*/
inline
SystemInformation*
SystemInformation::get(FunctionCallInfo fcinfo) {
madlib_assert(fcinfo->flinfo,
std::invalid_argument("Incomplete FunctionCallInfoData."));
SystemInformation* sysInfo
= getSystemInformationFromFmgrInfo(fcinfo->flinfo);
if (!sysInfo) {
MemoryContext memCtxt = getMemoryContextFromFmgrInfo(fcinfo->flinfo);
sysInfo = static_cast<SystemInformation*>(
madlib_MemoryContextAllocZero(
memCtxt, sizeof(SystemInformation)));
sysInfo->entryFuncOID = fcinfo->flinfo->fn_oid;
sysInfo->cacheContext = memCtxt;
sysInfo->collationOID = PG_GET_COLLATION();
setSystemInformationInFmgrInfo(fcinfo->flinfo, sysInfo);
}
return sysInfo;
}
/**
* @brief Get (and cache) information about a PostgreSQL type
*
* @param inPGInfo An PGInformation structure containing all cached information
* @param inTypeID The OID of the type of interest
*/
inline
TypeInformation*
SystemInformation::typeInformation(Oid inTypeID) {
TypeInformation* cachedTypeInfo = NULL;
bool found = true;
MemoryContext oldContext;
HeapTuple tup;
Form_pg_type pgType;
// We arrange to look up info about types only once per series of
// calls, assuming the type info doesn't change underneath us.
initializeOidHashTable(types, cacheContext,
sizeof(TypeInformation),
"C++ AL / TypeInformation hash table",
12);
// BACKEND: Since we pass HASH_FIND, this function call will never perform
// an allocation. There is nothing in the code path that would raise an
// exception (including oid_hash()), so we are *not* wrapping in a PG_TRY()
// block for performance reasons.
cachedTypeInfo = static_cast<TypeInformation*>(
hash_search(types, &inTypeID, HASH_FIND, &found));
if (!found) {
cachedTypeInfo = static_cast<TypeInformation*>(
madlib_hash_search(types, &inTypeID, HASH_ENTER, &found));
// cachedTypeInfo.oid is already set
tup = madlib_SearchSysCache1(TYPEOID, ObjectIdGetDatum(inTypeID));
// BACKEND: HeapTupleIsValid is just a macro
if (!HeapTupleIsValid(tup)) {
throw std::runtime_error("Error while looking up a type in the "
"system catalog.");
} else {
// BACKEND: GETSTRUCT is just a macro
pgType = reinterpret_cast<Form_pg_type>(GETSTRUCT(tup));
std::strncpy(cachedTypeInfo->name, pgType->typname.data,
NAMEDATALEN);
cachedTypeInfo->len = pgType->typlen;
cachedTypeInfo->byval = pgType->typbyval;
cachedTypeInfo->type = pgType->typtype;
if (cachedTypeInfo->type == TYPTYPE_COMPOSITE) {
// BACKEND: MemoryContextSwitchTo just changes a global
// variable
oldContext = MemoryContextSwitchTo(cacheContext);
// Since type ID != RECORDOID, typmod will not be used and
// we can set it to -1
// (RECORDOID is a pseudo type and used for transient record
// types. They are identified by an index in array
// RecordCacheArray defined in typcache.c.)
cachedTypeInfo->tupdesc = madlib_lookup_rowtype_tupdesc_copy(
/* type_id */ inTypeID,
/* typmod */ -1);
MemoryContextSwitchTo(oldContext);
} else {
cachedTypeInfo->tupdesc = NULL;
}
madlib_ReleaseSysCache(tup);
}
}
return cachedTypeInfo;
}
/**
* @brief Get (and cache) information about a PostgreSQL function
*
* @param inFuncID The OID of the function of interest
* @return
*/
inline
FunctionInformation*
SystemInformation::functionInformation(Oid inFuncID) {
FunctionInformation* cachedFuncInfo = NULL;
bool found = true;
HeapTuple tup;
Form_pg_proc pgFunc;
// We arrange to look up info about functions only once per series of
// calls, assuming the function info doesn't change underneath us.
initializeOidHashTable(functions, cacheContext,
sizeof(FunctionInformation),
"C++ AL / FunctionInformation hash table",
8);
// BACKEND: Since we pass HASH_FIND, this function call will never perform
// an allocation. There is nothing in the code path that would raise an
// exception (including oid_hash()), so we are *not* wrapping in a PG_TRY()
// block for performance reasons.
cachedFuncInfo = static_cast<FunctionInformation*>(
hash_search(functions, &inFuncID, HASH_FIND, &found));
if (!found) {
cachedFuncInfo = static_cast<FunctionInformation*>(
madlib_hash_search(functions, &inFuncID, HASH_ENTER, &found));
// cachedFuncInfo.oid is already set
cachedFuncInfo->mSysInfo = this;
tup = madlib_SearchSysCache1(PROCOID, ObjectIdGetDatum(inFuncID));
if (!HeapTupleIsValid(tup)) {
throw std::runtime_error("Error while looking up a function in the "
"system catalog.");
} else {
// BACKEND: GETSTRUCT is just a macro
pgFunc = reinterpret_cast<Form_pg_proc>(GETSTRUCT(tup));
cachedFuncInfo->cxx_func = NULL;
cachedFuncInfo->flinfo.fn_oid = InvalidOid;
// The number of arguments (excluding OUT params)
cachedFuncInfo->nargs
= static_cast<uint16_t>(pgFunc->proargtypes.dim1);
cachedFuncInfo->polymorphic = false;
cachedFuncInfo->isstrict = pgFunc->proisstrict;
cachedFuncInfo->secdef = pgFunc->prosecdef;
Oid* allargs;
// We could use get_func_arg_info() but unfortunately that also
// copied names and modes
bool onlyINArguments = false;
Datum allargtypes = madlib_SysCacheGetAttr(PROCOID, tup,
Anum_pg_proc_proallargtypes, &onlyINArguments);
if (onlyINArguments) {
allargs = pgFunc->proargtypes.values;
} else {
// See get_func_arg_info(). We expect the arrays to be 1-D
// arrays of the right types; verify that.
// Ensure that array is not toasted. We do not worry about
// a possible memory leak here. This code will be run only
// once per entry function (into the C++ AL) and query, and
// memory will already be garbage collected once the currenty
// entry point is left.
ArrayType* arr = madlib_DatumGetArrayTypeP(allargtypes);
int numargs = ARR_DIMS(arr)[0];
madlib_assert(ARR_NDIM(arr) == 1 && ARR_DIMS(arr)[0] >= 0 &&
!ARR_HASNULL(arr) && ARR_ELEMTYPE(arr) == OIDOID &&
numargs >= pgFunc->pronargs,
std::runtime_error("In SystemInformation::"
"functionInformation(): proallargtypes is not a vaid "
"one-dimensional Oid array"));
allargs = reinterpret_cast<Oid*>(ARR_DATA_PTR(arr));
}
for (int i = 0; i < pgFunc->pronargs; ++i) {
// Note that pgFunc->pronargs is the number of all arguments
// (including OUT params)
if (typeInformation(allargs[i])->getType()
== TYPTYPE_PSEUDO) {
cachedFuncInfo->polymorphic = true;
break;
}
}
if (cachedFuncInfo->nargs == 0) {
cachedFuncInfo->argtypes = NULL;
} else {
cachedFuncInfo->argtypes = static_cast<Oid*>(
madlib_MemoryContextAlloc(cacheContext,
cachedFuncInfo->nargs * sizeof(Oid)));
std::memcpy(cachedFuncInfo->argtypes,
pgFunc->proargtypes.values,
cachedFuncInfo->nargs * sizeof(Oid));
}
cachedFuncInfo->rettype = pgFunc->prorettype;
// If the return type is RECORDOID, we cannot yet determine the
// tuple description, even if the function is not polymorphic.
// For that, the expression parse tree is required.
// If the return type is composite but not RECORDOID, the tuple
// description will be stored with the type information, so no
// need to have it here.
cachedFuncInfo->tupdesc = NULL;
madlib_ReleaseSysCache(tup);
}
}
return cachedFuncInfo;
}
/**
* @brief Retrieve tuple description for a composite type
*
* @param inTypeMod Transient record types have tye ID RECORDOID and are
* identified by an index in array RecordCacheArray (defined in typcache.c).
* This index is stored in the tdtypmod field of struct tupleDesc.
* See the description of \c tupleDesc in tupdesc.h.
*/
inline
TupleDesc
TypeInformation::getTupleDesc(int32_t inTypeMod) {
if (tupdesc != NULL)
// We have the tuple description in our cache, so return it
return tupdesc;
else if (oid == RECORDOID && inTypeMod >= 0) {
// It is an anonymous type, but PostgreSQL has it in its own cache
// In this case lookup_rowtype_tupdesc_noerror(RECORDOID, ...)
// does currently not perform any actions that could rise an exception.
// We might not want to rely on that, however (at the expense of
// performance).
TupleDesc pgCachedTupDesc = lookup_rowtype_tupdesc_noerror(oid,
inTypeMod, /* noerror */ true);
// The tupleDesc is in the cache (RecordCacheArray defined in
// typcache.c) even before lookup_rowtype... is called. There is no
// need to release the tupleDesc, though we do in order to avoid any
// side effect.
ReleaseTupleDesc(pgCachedTupDesc);
return pgCachedTupDesc;
}
return NULL;
}
/**
* @brief Determine whether a specified type is composite
*
* This is our cached replacement for type_is_rowtype() in lsyscache.c
*/
inline
bool
TypeInformation::isCompositeType() {
return oid == RECORDOID || type == TYPTYPE_COMPOSITE;
}
/**
* @brief Retrieve the name of the specified type
*/
inline
const char*
TypeInformation::getName() {
return name;
}
inline
bool
TypeInformation::isByValue() {
return byval;
}
inline
int16_t
TypeInformation::getLen() {
return len;
}
inline
char
TypeInformation::getType() {
return type;
}
/**
* @brief Retrieve the type of a function argument
*
* @param inArgID The ID of the argument (first argument is 0)
* @param inFmgrInfo System-catalog information about the function. For
* polymorphic functions, this should be non-NULL and contain the
* expression parse tree.
* @return The OID of the type of the argument. The type ID of polymorphic
* arguments is resolved if possible, i.e., if the expression parse tree
* is available in <tt>inFmgrInfo->fn_expr</tt>.
*/
inline
Oid
FunctionInformation::getArgumentType(uint16_t inArgID, FmgrInfo* inFmgrInfo) {
if (!inFmgrInfo)
inFmgrInfo = getFuncMgrInfo();
madlib_assert(inFmgrInfo && oid == inFmgrInfo->fn_oid, std::runtime_error(
"Invalid arguments passed to FunctionInformation::getArgumentType()."));
Oid typeID = argtypes[inArgID];
TypeInformation* cachedTypeInfo = mSysInfo->typeInformation(typeID);
if (cachedTypeInfo->getType() == TYPTYPE_PSEUDO
&& inFmgrInfo->fn_expr != NULL) {
// Type is a pseudotype, so the actual type information is given
// by the expression parse tree.
// This would fail if no expression parse tree is present, i.e., if
// inFmgrInfo->fn_expr == NULL.
typeID = madlib_get_fn_expr_argtype(inFmgrInfo, inArgID);
}
return typeID;
}
/**
* @brief Retrieve the function return type
*
* @param fcinfo Information about function call, including the function OID
* @return The actual return type (i.e., with resolved pseudo-types)
*/
inline
Oid
FunctionInformation::getReturnType(FunctionCallInfo fcinfo) {
madlib_assert(fcinfo->flinfo && oid == fcinfo->flinfo->fn_oid,
std::runtime_error("Invalid arguments passed to "
"FunctionInformation::getReturnType()."));
Oid returnType = rettype;
if (rettype != RECORDOID &&
mSysInfo->typeInformation(rettype)->type == TYPTYPE_PSEUDO) {
// The function is polymorphic, and the result type thus depends on the
// expression parse tree. Note that the condition in the if-clause is
// sufficient condition for cachedFuncInfo->polymorphic, but not a
// necessary condition. (A function could have input arguments with
// pseudo types, but a fixed return type.)
madlib_assert(polymorphic,
std::logic_error("Logical error: Function returns non-record "
"pseudo type but is not polymorphic."));
// This is not a composite type, so no need to pass anything for
// resultTupleDesc
madlib_get_call_result_type(fcinfo, &returnType,
/* resultTupleDesc */ NULL);
}
return returnType;
}
/**
* @brief Retrieve the tuple description of a function's return type
*
* @param fcinfo Information about function call, including the function OID
* @return The tuple description if the return type is composite and NULL
* otherwise
*/
inline
TupleDesc
FunctionInformation::getReturnTupleDesc(FunctionCallInfo fcinfo) {
madlib_assert(fcinfo->flinfo && oid == fcinfo->flinfo->fn_oid,
std::runtime_error("Invalid arguments passed to "
"FunctionInformation::getReturnTupleDesc()."));
TupleDesc returnTupDesc = tupdesc;
if (returnTupDesc == NULL) {
if (rettype == RECORDOID) {
MADLIB_PG_TRY {
// The return type is known, so no need to pass anything for
// resultTypeId
// get_call_result_type() creates the tuple description using
// lookup_rowtype_tupdesc_copy(), which is not reference-counted
// So there is no need to release the TupleDesc
get_call_result_type(fcinfo, /* resultTypeId */ NULL,
&returnTupDesc);
} MADLIB_PG_DEFAULT_CATCH_AND_END_TRY;
if (returnTupDesc == NULL) {
throw std::runtime_error("MADLIB-870: C++ abstract layer has "
"not supported UDFs that return RECORD type without "
"tuple described at call time");
}
MADLIB_PG_TRY {
if (!polymorphic) {
// Since the function is not polymorphic, we can store the
// tuple description for other calls
MemoryContext oldContext
= MemoryContextSwitchTo(mSysInfo->cacheContext);
tupdesc = CreateTupleDescCopyConstr(returnTupDesc);
MemoryContextSwitchTo(oldContext);
}
} MADLIB_PG_DEFAULT_CATCH_AND_END_TRY;
} else {
TypeInformation* cachedTypeInfo
= mSysInfo->typeInformation(rettype);
if (cachedTypeInfo->type == TYPTYPE_COMPOSITE)
returnTupDesc = cachedTypeInfo->tupdesc;
}
}
return returnTupDesc;
}
/**
* @brief Retrieve (cross-call) system-catalog information about the function
*
* @param inFuncID The OID of the function of interest
* @return A filled FmgrInfo struct which uses the current SystemInformation
* object as system-catalog cache
*
* If no FmgrInfo data is stored for the function of interest, this
* function creates a new struct FmgrInfo and stores it in the current
* FunctionInformation object. It also links the new FmgrInfo struct to the
* currently used SystemInformation object.
*
* @note Cached FmgrInfo data is *not* used for entry functions (i.e., the
* immediate call by the backend). For the entry function, a complete
* struct FunctionCallInfoData is passed by the backend itself, which is
* used instead (as it also contains the parse tree).
*/
inline
FmgrInfo*
FunctionInformation::getFuncMgrInfo() {
// Initially flinfo.fn_oid is set to InvalidOid
if (flinfo.fn_oid != oid) {
// Check permissions
if (madlib_pg_proc_aclcheck(oid, GetUserId(), ACL_EXECUTE)
!= ACLCHECK_OK) {
throw std::invalid_argument(std::string("No privilege to run "
"function '") + getFullName() + "'.");
}
// cacheContext will be set as fn_mcxt.
madlib_fmgr_info_cxt(oid, &flinfo, mSysInfo->cacheContext);
if (!secdef) {
// If the function is SECURITY DEFINER then fmgr_info_cxt() has
// set up flinfo so that what we will actually
// call fmgr_security_definer() in fmgr.c, which then calls the
// "real" function. Because of this additional layer, and since
// fmgr_security_definer() uses the fn_extra field of
// struct FmgrInfo in an opaque way (it points to a struct that is
// local to fmgr.c), we only initialize the cache if the function
// is *not* SECURITY DEFINER.
setSystemInformationInFmgrInfo(&flinfo, mSysInfo);
}
}
return &flinfo;
}
/**
* @brief Retrieve the full function name (including arguments)
*
* We currently do not cache this information because we expect this function
* to be primarily called by error handlers.
*/
inline
const char*
FunctionInformation::getFullName() {
return madlib_format_procedure(oid);
}
} // namespace postgres
} // namespace dbconnector
} // namespace madlib
#endif // defined(MADLIB_POSTGRES_SYSTEMINFORMATION_IMPL_HPP)