blob: 41fcd47b99e97eaccee0b36d355cfc19f55dda77 [file] [log] [blame]
/* ----------------------------------------------------------------------- *//**
*
* @file AnyType_impl.hpp
*
*//* ----------------------------------------------------------------------- */
#ifndef MADLIB_POSTGRES_ANYTYPE_IMPL_HPP
#define MADLIB_POSTGRES_ANYTYPE_IMPL_HPP
namespace madlib {
namespace dbconnector {
namespace postgres {
inline void * AnyType::getUserFuncContext(){
return this->mSysInfo->user_fctx;
}
inline void AnyType::setUserFuncContext(void * user_fctx){
this->mSysInfo->user_fctx = user_fctx;
}
inline MemoryContext AnyType::getCacheMemoryContext(){
return this->mSysInfo->cacheContext;
}
inline
AnyType::AnyType(FunctionCallInfo inFnCallInfo)
: mContentType(FunctionComposite),
mContent(),
mToDatumFn(),
mDatum(0),
fcinfo(inFnCallInfo),
mSysInfo(SystemInformation::get(inFnCallInfo)),
mTupleHeader(NULL),
mTypeID(InvalidOid),
mTypeName(NULL),
mIsMutable(false)
{ }
inline
AnyType::AnyType(SystemInformation* inSysInfo,
HeapTupleHeader inTuple, Datum inDatum, Oid inTypeID)
: mContentType(NativeComposite),
mContent(),
mToDatumFn(),
mDatum(inDatum),
fcinfo(NULL),
mSysInfo(inSysInfo),
mTupleHeader(inTuple),
mTypeID(inTypeID),
mTypeName(inSysInfo->typeInformation(inTypeID)->getName()),
mIsMutable(false)
{ }
inline
AnyType::AnyType(SystemInformation* inSysInfo, Datum inDatum,
Oid inTypeID, bool inIsMutable)
: mContentType(Scalar),
mContent(),
mToDatumFn(),
mDatum(inDatum),
fcinfo(NULL),
mSysInfo(inSysInfo),
mTupleHeader(NULL),
mTypeID(inTypeID),
mTypeName(inSysInfo->typeInformation(inTypeID)->getName()),
mIsMutable(inIsMutable)
{ }
/**
* @brief Template constructor (will \b not be used as copy constructor)
*
* @param inValue The value to initialize this AnyType object with
* @param inForceLazyConversionToDatum If true, initialize this AnyType object
* as if <tt>lazyConversionToDatum() == true</tt>
*
* This constructor will be invoked when initializing an AnyType object with
* any scalar value (including arrays, but excluding composite types). This will
* typically only happen for preparing the return value of a user-defined
* function.
* This constructor immediately converts the object into a PostgreSQL Datum
* using the TypeTraits class. If memory has to be retained, it has to be done
* there.
*/
template <typename T>
inline
AnyType::AnyType(const T& inValue, bool inForceLazyConversionToDatum)
: mContentType(Scalar),
mContent(),
mToDatumFn(),
mDatum(0),
fcinfo(NULL),
mSysInfo(TypeTraits<T>::toSysInfo(inValue)),
mTupleHeader(NULL),
mTypeID(TypeTraits<T>::oid),
mTypeName(TypeTraits<T>::typeName()),
mIsMutable(TypeTraits<T>::isMutable) {
if (inForceLazyConversionToDatum || lazyConversionToDatum()) {
mContent = inValue;
mToDatumFn = std::bind(TypeTraits<T>::toDatum, inValue);
} else {
mDatum = TypeTraits<T>::toDatum(inValue);
}
}
/**
* @brief Default constructor, initializes AnyType object as Null
*
* This constructor initializes the object as Null. It must also be used for
* building a composite type. After construction, use operator<<() to append
* values to the composite object.
*/
inline
AnyType::AnyType()
: mContentType(Null),
mContent(),
mToDatumFn(),
mDatum(0),
fcinfo(NULL),
mSysInfo(NULL),
mTupleHeader(NULL),
mTypeID(InvalidOid),
mTypeName(NULL),
mIsMutable(false)
{ }
/**
* @brief Verify consistency of AnyType object. Throw exception if not.
*/
inline
void
AnyType::consistencyCheck() const {
const char *kMsg("Inconsistency detected while converting between "
"PostgreSQL and C++ types.");
madlib_assert(mContentType != Null || (mDatum == 0 && mContent.empty() &&
fcinfo == NULL && mSysInfo == NULL && mTupleHeader == NULL &&
mTypeID == InvalidOid && mTypeName == NULL && mChildren.empty()),
std::logic_error(kMsg));
madlib_assert(mContentType == Scalar || mContent.empty(),
std::logic_error(kMsg));
madlib_assert(mContentType != FunctionComposite || fcinfo != NULL,
std::logic_error(kMsg));
madlib_assert(mContentType != NativeComposite || mTupleHeader != NULL,
std::logic_error(kMsg));
madlib_assert(mContentType != ReturnComposite || (!mChildren.empty() &&
mTypeID == InvalidOid),
std::logic_error(kMsg));
madlib_assert(mContentType == ReturnComposite || mChildren.empty(),
std::logic_error(kMsg));
madlib_assert(
(mContentType != FunctionComposite && mContentType != NativeComposite)
|| mSysInfo != NULL,
std::logic_error(kMsg));
madlib_assert(mChildren.size() <= std::numeric_limits<uint16_t>::max(),
std::runtime_error("Too many fields in composite type."));
}
/**
* @brief Convert object to the type specified as template argument
*
* @tparam T Type to convert object to
*/
template <typename T>
inline
T
AnyType::getAs() const {
consistencyCheck();
if (isNull())
throw std::invalid_argument("Invalid type conversion. "
"Null where not expected.");
if (isComposite())
throw std::invalid_argument("Invalid type conversion. "
"Composite type where not expected.");
// Verify type OID
if (TypeTraits<T>::oid != InvalidOid && mTypeID != TypeTraits<T>::oid) {
std::stringstream errorMsg;
errorMsg << "Invalid type conversion. Expected type ID "
<< TypeTraits<T>::oid;
if (mSysInfo)
errorMsg << " ('"
<< mSysInfo->typeInformation(TypeTraits<T>::oid)->getName()
<< "')";
errorMsg << " but got " << mTypeID;
if (mSysInfo)
errorMsg << " ('"
<< mSysInfo->typeInformation(mTypeID)->getName() << "')";
errorMsg << '.';
throw std::invalid_argument(errorMsg.str());
}
// Verify type name
if (TypeTraits<T>::typeName() &&
std::strncmp(mTypeName, TypeTraits<T>::typeName(), NAMEDATALEN)) {
std::stringstream errorMsg;
errorMsg << "Invalid type conversion. Expected type '"
<< TypeTraits<T>::typeName() << "' but backend type name is '"
<< mTypeName << "' (ID " << mTypeID << ").";
throw std::invalid_argument(errorMsg.str());
}
if (mContent.empty()) {
bool needMutableClone = (TypeTraits<T>::isMutable && !mIsMutable);
return TypeTraits<T>::toCXXType(mDatum, needMutableClone, mSysInfo);
} else {
// any_cast<T*> will not throw but return a NULL pointer if of
// incorrect type
const T* value = boost::any_cast<T>(&mContent);
if (value == NULL) {
std::stringstream errorMsg;
errorMsg << "Invalid type conversion. Expected type '"
<< typeid(T).name() << "' but stored type is '"
<< mContent.type().name() << "'.";
throw std::runtime_error(errorMsg.str());
}
return *value;
}
}
/**
* @brief Return if object is Null
*/
inline
bool
AnyType::isNull() const {
return mContentType == Null;
}
/**
* @brief Return if object is of composite type (also called row type or
* user-defined type)
*/
inline
bool
AnyType::isComposite() const {
return mContentType == FunctionComposite ||
mContentType == NativeComposite || mContentType == ReturnComposite;
}
/**
* @brief Return the number of fields in a composite value.
*
* @returns The number of fields in a composite value. In the case of a scalar
* value, return 1. If the content is NULL, return 0.
*/
inline
uint16_t
AnyType::numFields() const {
consistencyCheck();
switch (mContentType) {
case Null: return 0;
case Scalar: return 1;
case ReturnComposite: return static_cast<uint16_t>(mChildren.size());
case FunctionComposite: return PG_NARGS();
case NativeComposite: return HeapTupleHeaderGetNatts(mTupleHeader);
default:
// This should never happen
throw std::logic_error("Unhandled case in AnyType::numFields().");
}
}
/**
* @brief Return the n-th element from a composite value
*
* To the user, AnyType is a fully recursive type: Each AnyType object can be a
* composite object and be composed of a number of other AnyType objects.
* Function written using the C++ abstraction layer have a single logical
* argument of type AnyType.
*/
inline
AnyType
AnyType::operator[](uint16_t inID) const {
consistencyCheck();
if (isNull()) {
// Handle case mContentType == NULL
throw std::invalid_argument("Invalid type conversion. "
"Null where not expected.");
}
if (!isComposite()) {
// Handle case mContentType == Scalar
throw std::invalid_argument("Invalid type conversion. "
"Composite type where not expected.");
}
if (mContentType == ReturnComposite)
return mChildren[inID];
// It holds now that mContentType is either FunctionComposite or
// NativeComposite. In this case, it is guaranteed that fcinfo != NULL
Oid typeID = 0;
bool isMutable = false;
Datum datum = 0;
if (mContentType == FunctionComposite) {
// This AnyType object represents to composite value consisting of all
// function arguments
if (inID >= size_t(PG_NARGS()))
throw std::out_of_range("Invalid type conversion. Access behind "
"end of argument list.");
if (PG_ARGISNULL(inID))
return AnyType();
typeID = mSysInfo->functionInformation(fcinfo->flinfo->fn_oid)
->getArgumentType(inID, fcinfo->flinfo);
if (inID == 0) {
// If we are called as an aggregate function, the first argument is
// the transition state. In that case, we are free to modify the
// data. In fact, for performance reasons, we *should* even do all
// modifications in-place. In all other cases, directly modifying
// memory is dangerous.
// See warning at:
// http://www.postgresql.org/docs/current/static/xfunc-c.html#XFUNC-C-BASETYPE
// BACKEND: AggCheckCallContext currently will never raise an
// exception
isMutable = AggCheckCallContext(fcinfo, NULL);
}
datum = PG_GETARG_DATUM(inID);
} else /* if (mContentType == NativeComposite) */ {
// This AnyType objects represents a tuple that was passed from the
// backend
TupleDesc tupdesc = mSysInfo
->typeInformation(HeapTupleHeaderGetTypeId(mTupleHeader))
->getTupleDesc(HeapTupleHeaderGetTypMod(mTupleHeader));
if (inID >= tupdesc->natts)
throw std::out_of_range("Invalid type conversion. Access behind "
"end of composite object.");
#if PG_VERSION_NUM >= 110000
typeID = TupleDescAttr(tupdesc, inID)->atttypid;
#else
typeID = tupdesc->attrs[inID]->atttypid;
#endif
bool isNull = false;
datum = madlib_GetAttributeByNum(mTupleHeader, inID, &isNull);
if (isNull)
return AnyType();
}
if (typeID == InvalidOid)
throw std::invalid_argument("Backend returned invalid type ID.");
return mSysInfo->typeInformation(typeID)->isCompositeType()
? AnyType(mSysInfo, madlib_DatumGetHeapTupleHeader(datum), datum,
typeID)
: AnyType(mSysInfo, datum, typeID, isMutable);
}
/**
* @brief Add an element to a composite value, for returning to the backend
*/
inline
AnyType&
AnyType::operator<<(const AnyType &inValue) {
consistencyCheck();
madlib_assert(mContentType == Null || mContentType == ReturnComposite,
std::logic_error("Internal inconsistency while creating composite "
"return value."));
mContentType = ReturnComposite;
mChildren.push_back(inValue);
return *this;
}
/**
* @brief Return a PostgreSQL Datum representing the current object
*
* If the current object is Null, we still return <tt>Datum(0)</tt>, i.e., we
* return a valid Datum. It is the responsibilty of the caller to separately
* call isNull().
*
* The only *conversion* taking place in this function is *combining* Datums
* into a tuple. At this place, we do not have to worry any more about retaining
* memory.
*
* @param inFnCallInfo The PostgreSQL FunctionCallInfo that was passed to the
* UDF. For polymorphic functions or functions that return RECORD, the
* function-call information (specifically, the expression parse tree)
* is necessary to dynamically resolve type information.
* @param inTargetTypeID PostgreSQL OID of the target type to convert to. If
* omitted the target type is the return type of the function specified by
* \c inFnCallInfo.
*
* @see getAsDatum(const FunctionCallInfo)
*/
inline
Datum
AnyType::getAsDatum(FunctionCallInfo inFnCallInfo,
Oid inTargetTypeID) const {
consistencyCheck();
// The default value to return in case of Null is 0. Note, however, that
// 0 can also be a perfectly valid (non-null) Datum. It is the caller's
// responsibility to call isNull() separately.
if (isNull())
return 0;
// Note: mSysInfo is NULL if this object was not an argument from the
// backend.
SystemInformation* sysInfo = SystemInformation::get(inFnCallInfo);
FunctionInformation* funcInfo = sysInfo
->functionInformation(inFnCallInfo->flinfo->fn_oid);
TupleDesc targetTupleDesc;
if (inTargetTypeID == InvalidOid) {
inTargetTypeID = funcInfo->getReturnType(inFnCallInfo);
// If inTargetTypeID is \c RECORDOID, the tuple description needs to be
// derived from the function call
targetTupleDesc = funcInfo->getReturnTupleDesc(inFnCallInfo);
} else {
// If we are here, we should not see inTargetTypeID == RECORDOID because
// that should only happen for the first non-recursive call of
// getAsDatum where inTargetTypeID == InvalidOid by default.
// If it would happen, then the following would return NULL and an
// exception would be raised a few line below. So no need to add a check
// here.
targetTupleDesc = sysInfo->typeInformation(inTargetTypeID)
->getTupleDesc();
}
bool targetIsComposite = targetTupleDesc != NULL;
Datum returnValue;
if (targetIsComposite && !isComposite())
throw std::runtime_error("Invalid type conversion. "
"Simple type supplied but backend expects composite type.");
if (!targetIsComposite && isComposite())
throw std::runtime_error("Invalid type conversion. "
"Composite type supplied but backend expects simple type.");
if (targetIsComposite) {
if (static_cast<size_t>(targetTupleDesc->natts) < mChildren.size())
throw std::runtime_error("Invalid type conversion. "
"Internal composite type has more elements than backend "
"composite type.");
Datum* values = new Datum[targetTupleDesc->natts];
bool* nulls = new bool[targetTupleDesc->natts];
for (size_t pos = 0; pos < mChildren.size(); ++pos) {
#if PG_VERSION_NUM >= 110000
Oid targetTypeID = TupleDescAttr(targetTupleDesc, pos)->atttypid;
#else
Oid targetTypeID = targetTupleDesc->attrs[pos]->atttypid;
#endif
values[pos] = mChildren[pos].getAsDatum(inFnCallInfo, targetTypeID);
nulls[pos] = mChildren[pos].isNull();
}
// All elements that have not been initialized will be set to Null
for (size_t pos = mChildren.size();
pos < static_cast<size_t>(targetTupleDesc->natts);
++pos) {
values[pos]=Datum(0);
nulls[pos]=true;
}
HeapTuple heapTuple = madlib_heap_form_tuple(targetTupleDesc,
values, nulls);
// BACKEND: HeapTupleGetDatum is a macro that will not cause exceptions
returnValue = HeapTupleGetDatum(heapTuple);
} else /* if (!targetIsComposite) */ {
if (mTypeID != InvalidOid && inTargetTypeID != mTypeID) {
std::stringstream errorMsg;
errorMsg << "Invalid type conversion. "
"Backend expects type ID " << inTargetTypeID << " ('"
<< sysInfo->typeInformation(inTargetTypeID)->getName() << "') "
"but supplied type ID is " << mTypeID << + " ('"
<< sysInfo->typeInformation(mTypeID)->getName() << "').";
throw std::invalid_argument(errorMsg.str());
}
if (mTypeName && std::strncmp(mTypeName,
sysInfo->typeInformation(inTargetTypeID)->getName(),
NAMEDATALEN)) {
std::stringstream errorMsg;
errorMsg << "Invalid type conversion. Backend expects type '"
<< sysInfo->typeInformation(inTargetTypeID)->getName() <<
"' (ID " << inTargetTypeID << ") but internal type name is '"
<< mTypeName << "'.";
throw std::invalid_argument(errorMsg.str());
}
returnValue = mContent.empty() ? mDatum : mToDatumFn();
}
return returnValue;
}
/**
* @brief Convert values to PostgreSQL Datum in AnyType constructor (or only
* when needed)?
*
* Usually, AnyType objects are only used to retrieve or return data from/to the
* backend. However, there are exceptions. For instance, when calling a
* FunctionHandle, data might be passed directly from one C++ function to
* another. In this case, it would be wasteful to convert arguments to
* PostgreSQL Datum type, and it is better to only lazily convert to Datum
* (i.e., only when needed by getAsDatum()).
*
* Since PostgreSQL is single-threaded, it is sufficient to maintain a global
* variable that contains whether lazy conversion is requested.
*/
inline
bool
AnyType::lazyConversionToDatum() {
return sLazyConversionToDatum;
}
inline
AnyType::LazyConversionToDatumOverride::LazyConversionToDatumOverride(
bool inLazyConversionToDatum) {
mOriginalValue = AnyType::sLazyConversionToDatum;
AnyType::sLazyConversionToDatum = inLazyConversionToDatum;
}
inline
AnyType::LazyConversionToDatumOverride::~LazyConversionToDatumOverride() {
AnyType::sLazyConversionToDatum = mOriginalValue;
}
/**
* @brief Return an AnyType object representing Null.
*
* @internal
* An object representing Null is not guaranteed to be unique. In fact, here
* we simply return an AnyType object initialized by the default
* constructor.
*
* @see AbstractionLayer::AnyType::AnyType()
*/
inline
AnyType
Null() {
return AnyType();
}
} // namespace postgres
} // namespace dbconnector
} // namespace madlib
#endif // defined(MADLIB_POSTGRES_ANYTYPE_IMPL_HPP)