| /* ----------------------------------------------------------------------- *//** |
| * |
| * @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) |