blob: f935eb6c192fd7b4057f7ce9a30936d552677bc2 [file] [log] [blame]
/****************************************************************************
* File: LmRoutineCppObj.cpp
* Description: LM routine for C++ routines using the object interface
* Created: 01/22/2015
* Language: C++
*
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
****************************************************************************
*/
#include "LmRoutineCppObj.h"
#include "LmParameter.h"
#include "LmLangManagerC.h"
#include "ComUser.h"
// Utility method to set up a "wall" before and after a buffer.
// This wall is set to a specific pattern. If someone accidentally
// writes beyond the buffer boundaries, the wall will get clobbered
// and a subsequent validation will raise an error.
#define WALL_STRING "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
#define WALL_STRING_LEN 8
//////////////////////////////////////////////////////////////////////
//
// Class LmRoutineCppObj
// If we get errors in creating this object, fill diagsArea and return
// error. Caller is responsbile to cleanup by calling destructor.
//
//////////////////////////////////////////////////////////////////////
LmRoutineCppObj::LmRoutineCppObj(
tmudr::UDRInvocationInfo *invocationInfo,
tmudr::UDRPlanInfo *planInfo,
tmudr::UDR *interfaceObj,
const char *sqlName,
const char *externalName,
const char *librarySqlName,
ComUInt32 maxResultSets,
ComRoutineTransactionAttributes transactionAttrs,
ComRoutineSQLAccess sqlAccessMode,
ComRoutineExternalSecurity externalSecurity,
Int32 routineOwnerId,
LmLanguageManagerC *lm,
LmContainer *container,
ComDiagsArea *diags)
: LmRoutine(container,
(LmHandle) interfaceObj,
sqlName,
externalName,
librarySqlName,
0,
maxResultSets,
COM_LANGUAGE_CPP,
COM_STYLE_CPP_OBJ,
transactionAttrs,
sqlAccessMode,
externalSecurity,
routineOwnerId,
invocationInfo->getQueryId().c_str(),
invocationInfo->par().getRecordLength(),
invocationInfo->out().getRecordLength(),
invocationInfo->getCurrentUser().c_str(),
invocationInfo->getSessionUser().c_str(),
NULL,
lm),
planInfos_(collHeap()),
invocationInfo_(invocationInfo),
interfaceObj_(interfaceObj),
paramRow_(NULL),
inputRows_(NULL),
outputRow_(NULL)
{
if (planInfo)
planInfos_.insertAt(0, planInfo);
interfaceObj_->getNextRowPtr_ = NULL; // set these with setFunctionPtrs()
interfaceObj_->emitRowPtr_ = NULL;
numInputTables_ = invocationInfo->getNumTableInputs();
if (inputParamRowLen_ > 0)
{
paramRow_ = new(collHeap()) char[inputParamRowLen_];
memset(paramRow_,0,inputParamRowLen_);
const_cast<tmudr::ParameterListInfo &>(invocationInfo->par()).setRowPtr(paramRow_);
}
// inputRows_ and outputRow_ will be allocated in setRuntimeInfo()
}
LmRoutineCppObj::~LmRoutineCppObj()
{
// assert that caller has called dealloc before destroying this object
LM_ASSERT(invocationInfo_ == NULL &&
interfaceObj_ == NULL &&
outputRow_ == NULL);
}
LmResult LmRoutineCppObj::dealloc(ComDiagsArea *diagsArea)
{
LmResult result = LM_OK;
delete invocationInfo_;
invocationInfo_ = NULL;
for (CollIndex i=0; i<planInfos_.getUsedLength(); i++)
if (planInfos_.used(i))
delete planInfos_[i];
planInfos_.clear();
if (paramRow_)
{
NADELETEBASIC(paramRow_, collHeap());
paramRow_ = NULL;
}
if (inputRows_)
{
for (int i=0; i<numInputTables_; i++)
if (inputRows_[i])
NADELETEBASIC((inputRows_[i]), collHeap());
NADELETEBASIC(inputRows_, collHeap());
inputRows_ = NULL;
}
if (outputRow_)
{
// actually allocated buffer started where the wall starts
NADELETEBASIC((outputRow_ - WALL_STRING_LEN), collHeap());
outputRow_ = NULL;
}
try
{
// delete the interface object, the virtual destructor may call user code
delete interfaceObj_;
}
catch (tmudr::UDRException e)
{
*diagsArea << DgSqlCode(-LME_UDR_METHOD_ERROR)
<< DgString0("destructor")
<< DgString1(getNameForDiags())
<< DgString2(e.getMessage().c_str());
result = LM_ERR;
}
catch (...)
{
*diagsArea << DgSqlCode(-LME_UDR_METHOD_ERROR)
<< DgString0("destructor")
<< DgString1(getNameForDiags())
<< DgString2("General exception.");
result = LM_ERR;
}
interfaceObj_ = NULL;
return result;
}
const char *LmRoutineCppObj::getParentQid()
{
return invocationInfo_->getQueryId().c_str();
}
LmResult LmRoutineCppObj::setRuntimeInfo(
const char *parentQid,
int totalNumInstances,
int myInstanceNum,
ComDiagsArea *da)
{
invocationInfo_->setQueryId(parentQid);
invocationInfo_->setTotalNumInstances(totalNumInstances);
invocationInfo_->setMyInstanceNum(myInstanceNum);
// allocate row buffers
if (numInputTables_ > 0)
{
inputRows_ = new (collHeap()) char *[numInputTables_];
for (int i=0; i<numInputTables_; i++)
{
int inputRowLength = invocationInfo_->in(i).recordLength_;
if (inputRowLength > 0)
{
inputRows_[i] = new(collHeap()) char[inputRowLength];
memset(inputRows_[i], 0, inputRowLength);
const_cast<tmudr::TableInfo &>(
invocationInfo_->in(i)).setRowPtr(inputRows_[i]);
}
else
// at compile time, input rows are not specified
inputRows_[i] = NULL;
}
}
else
{
inputRows_ = NULL;
}
if (outputRowLen_ > 0)
{
outputRow_ = new (collHeap()) char[outputRowLen_ + 2*WALL_STRING_LEN];
// remember the pointer to the actually usable
// buffer, not where the wall starts
outputRow_ += WALL_STRING_LEN;
memset(outputRow_, 0, outputRowLen_);
setUpWall(outputRow_, outputRowLen_);
invocationInfo_->out().setRowPtr(outputRow_);
}
else
outputRow_ = NULL;
return LM_OK;
}
LmResult LmRoutineCppObj::invokeRoutine(void *inputRow,
void *outputRow,
ComDiagsArea *da)
{
// This type of routine does not support the old C interface,
// must call invokeRoutineMethod instead
*da << DgSqlCode(-11111)
<< DgString0("Called LmRoutineCppObj::invokeRoutine()");
return LM_ERR;
}
LmResult LmRoutineCppObj::invokeRoutineMethod(
/* IN */ tmudr::UDRInvocationInfo::CallPhase phase,
/* IN */ const char *serializedInvocationInfo,
/* IN */ Int32 invocationInfoLen,
/* OUT */ Int32 *invocationInfoLenOut,
/* IN */ const char *serializedPlanInfo,
/* IN */ Int32 planInfoLen,
/* IN */ Int32 planNum,
/* OUT */ Int32 *planInfoLenOut,
/* IN */ char *inputParamRow,
/* IN */ Int32 inputParamRowLen,
/* OUT */ char *outputRow,
/* IN */ Int32 outputRowLen,
/* IN/OUT */ ComDiagsArea *da)
{
LmResult result = LM_OK;
// initialize out parameters
*invocationInfoLenOut = 0;
*planInfoLenOut = 0;
// some parameter checks
if (invocationInfoLen <= 0 &&
invocationInfo_ == NULL)
{
// we need to have an Invocation info
*da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
<< DgString0(getNameForDiags())
<< DgString1(tmudr::UDRInvocationInfo::callPhaseToString(
tmudr::UDRInvocationInfo::COMPILER_INITIAL_CALL))
<< DgString2("LmRoutineCppObj::invokeRoutineMethod()")
<< DgString3("Missing UDRInvocationInfo");
}
try
{
if (invocationInfoLen > 0)
{
if (invocationInfo_ == NULL)
invocationInfo_ = new tmudr::UDRInvocationInfo;
// unpack the invocation info
invocationInfo_->deserializeObj(serializedInvocationInfo,
invocationInfoLen);
}
if (planInfoLen > 0)
{
if (!planInfos_.used(planNum))
planInfos_.insertAt(planNum, new tmudr::UDRPlanInfo(invocationInfo_,
planNum));
// unpack the invocation info
planInfos_[planNum]->deserializeObj(serializedPlanInfo,
planInfoLen);
}
// some parameter checks
if (inputParamRowLen < inputParamRowLen_)
return LM_ERR;
// test to do for scalar UDFs
// if (outputRowLen < invocationInfo_->out().getRecordLength())
// return LM_ERR;
if (inputParamRow && inputParamRowLen_ > 0)
// copy parameter row
memcpy(invocationInfo_->par().getRowPtr(), inputParamRow, inputParamRowLen_);
invocationInfo_->callPhase_ = phase;
switch (phase)
{
case tmudr::UDRInvocationInfo::COMPILER_INITIAL_CALL:
if (invocationInfo_->getDebugFlags() &
tmudr::UDRInvocationInfo::PRINT_INVOCATION_INFO_INITIAL)
invocationInfo_->print();
if (invocationInfo_->getDebugFlags() &
tmudr::UDRInvocationInfo::DEBUG_INITIAL_COMPILE_TIME_LOOP)
if (invocationInfo_->getSessionUser() == ComUser::getRootUserName())
interfaceObj_->debugLoop();
else if (da)
*da << DgSqlCode(1260); // warning, only root user can debug
interfaceObj_->describeParamsAndColumns(*invocationInfo_);
break;
case tmudr::UDRInvocationInfo::COMPILER_DATAFLOW_CALL:
interfaceObj_->describeDataflowAndPredicates(*invocationInfo_);
break;
case tmudr::UDRInvocationInfo::COMPILER_CONSTRAINTS_CALL:
interfaceObj_->describeConstraints(*invocationInfo_);
break;
case tmudr::UDRInvocationInfo::COMPILER_STATISTICS_CALL:
interfaceObj_->describeStatistics(*invocationInfo_);
break;
case tmudr::UDRInvocationInfo::COMPILER_DOP_CALL:
interfaceObj_->describeDesiredDegreeOfParallelism(
*invocationInfo_,
*planInfos_[planNum]);
break;
case tmudr::UDRInvocationInfo::COMPILER_PLAN_CALL:
interfaceObj_->describePlanProperties(*invocationInfo_,
*planInfos_[planNum]);
break;
case tmudr::UDRInvocationInfo::COMPILER_COMPLETION_CALL:
interfaceObj_->completeDescription(*invocationInfo_,
*planInfos_[planNum]);
if (invocationInfo_->getDebugFlags() &
tmudr::UDRInvocationInfo::PRINT_INVOCATION_INFO_END_COMPILE)
{
invocationInfo_->print();
printf("\n");
for (CollIndex i=0; i<planInfos_.getUsedLength(); i++)
if (planInfos_.used(i))
{
if (i == planNum)
printf("++++++++++ Chosen plan: ++++++++++\n");
else
printf("-------- Plan not chosen: --------\n");
planInfos_[i]->print();
}
}
break;
case tmudr::UDRInvocationInfo::RUNTIME_WORK_CALL:
{
if (invocationInfo_->getDebugFlags() &
tmudr::UDRInvocationInfo::PRINT_INVOCATION_INFO_AT_RUN_TIME)
{
invocationInfo_->print();
planInfos_[planNum]->print();
}
if ((invocationInfo_->getDebugFlags() &
tmudr::UDRInvocationInfo::DEBUG_INITIAL_RUN_TIME_LOOP_ALL) ||
(invocationInfo_->getDebugFlags() &
tmudr::UDRInvocationInfo::DEBUG_INITIAL_RUN_TIME_LOOP_ONE &&
invocationInfo_->getMyInstanceNum() == 0))
interfaceObj_->debugLoop();
interfaceObj_->processData(*invocationInfo_,
*planInfos_[planNum]);
if (result == LM_OK)
{
if (invocationInfo_->getDebugFlags() & tmudr::UDRInvocationInfo::TRACE_ROWS)
printf("(%d) Emitting EOD\n",
invocationInfo_->getMyInstanceNum());
// call emitRow to indicate EOD, something the
// C interface would do inside the UDF
SQLUDR_Q_STATE qstate = SQLUDR_Q_EOD;
(*interfaceObj_->emitRowPtr_)(
invocationInfo_->out().getRowPtr(),
0,
&qstate);
}
}
break;
default:
*da << DgSqlCode(-11111)
<< DgString0("Invalid call phase in LmRoutineCppObj::invokeRoutineMethod()");
result = LM_ERR;
break;
}
// return length of updated invocation and plan info for
// compile-time phases
if (invocationInfo_ &&
phase < tmudr::UDRInvocationInfo::RUNTIME_WORK_CALL)
{
*invocationInfoLenOut = invocationInfo_->serializedLength();
if (planInfos_.used(planNum))
*planInfoLenOut = planInfos_[planNum]->serializedLength();
}
}
catch (tmudr::UDRException e)
{
// Check the returned SQLSTATE value and raise appropriate
// SQL code. Valid SQLSTATE values begin with "38" except "38000"
const char *sqlState = e.getSQLState();
if ((strncmp(sqlState, "38", 2) == 0) &&
(strncmp(sqlState, "38000", 5) != 0))
{
*da << DgSqlCode(-LME_CUSTOM_ERROR)
<< DgString0(e.getMessage().c_str())
<< DgString1(sqlState);
*da << DgCustomSQLState(sqlState);
}
else
{
*da << DgSqlCode(-LME_UDF_ERROR)
<< DgString0(invocationInfo_->getUDRName().c_str())
<< DgString1(sqlState)
<< DgString2(e.getMessage().c_str());
}
result = LM_ERR;
}
catch (...)
{
*da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
<< DgString0(getNameForDiags())
<< DgString1(invocationInfo_->callPhaseToString(phase))
<< DgString2("LmRoutineCppObj::invokeRoutineMethod()")
<< DgString3("general exception");
result = LM_ERR;
}
invocationInfo_->callPhase_ =
tmudr::UDRInvocationInfo::UNKNOWN_CALL_PHASE;
return result;
}
LmResult LmRoutineCppObj::getRoutineInvocationInfo(
/* IN/OUT */ char *serializedInvocationInfo,
/* IN */ Int32 invocationInfoMaxLen,
/* OUT */ Int32 *invocationInfoLenOut,
/* IN/OUT */ char *serializedPlanInfo,
/* IN */ Int32 planInfoMaxLen,
/* IN */ Int32 planNum,
/* OUT */ Int32 *planInfoLenOut,
/* IN/OUT */ ComDiagsArea *da)
{
LmResult result = LM_OK;
// Retrieve updated invocation and plan info.
// The invokeRoutineMethod provided the required buffer
// space, so there is no excuse for having insufficient
// buffer when calling this, and doing so will raise
// an exception in the try block below.
try
{
if (invocationInfo_ && invocationInfoMaxLen > 0)
{
char *tempiibuf = serializedInvocationInfo;
int tempiilen = invocationInfoMaxLen;
*invocationInfoLenOut = invocationInfo_->serialize(
tempiibuf, tempiilen);
}
else
*invocationInfoLenOut = 0;
if (planInfos_.used(planNum) && planInfoMaxLen > 0)
{
char *temppibuf = serializedPlanInfo;
int temppilen = planInfoMaxLen;
*planInfoLenOut = planInfos_[planNum]->serialize(
temppibuf, temppilen);
}
else
*planInfoLenOut = 0;
}
catch (tmudr::UDRException e)
{
// this UDRException is generated by Trafodion code and
// it is an internal error to fail serializing the structs,
// even though the user might have caused it by corrupting
// these structs
*da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
<< DgString0(getNameForDiags())
<< DgString1(invocationInfo_->callPhaseToString(invocationInfo_->getCallPhase()))
<< DgString2("LmRoutineCppObj::getRoutineInvocationInfo()")
<< DgString3(e.getMessage().c_str());
result = LM_ERR;
}
catch (...)
{
*da << DgSqlCode(-LME_OBJECT_INTERFACE_ERROR)
<< DgString0(getNameForDiags())
<< DgString1(invocationInfo_->callPhaseToString(invocationInfo_->getCallPhase()))
<< DgString2("LmRoutineCppObj::getRoutineInvocationInfo()")
<< DgString3("general exception");
result = LM_ERR;
}
return result;
}
LmResult LmRoutineCppObj::setFunctionPtrs(SQLUDR_GetNextRow getNextRowPtr,
SQLUDR_EmitRow emitRowPtr,
ComDiagsArea *da)
{
interfaceObj_->getNextRowPtr_ = getNextRowPtr;
interfaceObj_->emitRowPtr_ = emitRowPtr;
return LM_OK;
}
void LmRoutineCppObj::setUpWall(char *userBuf, int userBufLen)
{
memcpy(userBuf - WALL_STRING_LEN, WALL_STRING, WALL_STRING_LEN);
memcpy(userBuf + userBufLen, WALL_STRING, WALL_STRING_LEN);
}
LmResult LmRoutineCppObj::validateWall(char *userBuf,
int userBufLen,
ComDiagsArea *da,
const char *bufferName)
{
if (memcmp(userBuf - WALL_STRING_LEN, WALL_STRING, WALL_STRING_LEN) != 0)
{
if (da)
*da << DgSqlCode(LME_BUFFER_OVERWRITE)
<< DgString0(invocationInfo_->getUDRName().data())
<< DgString1(bufferName)
<< DgString2("beginning");
else
throw tmudr::UDRException(
38900,
"UDR %s overwrote space before its %s buffer",
invocationInfo_->getUDRName().data(),
bufferName);
}
if (memcmp(userBuf + userBufLen, WALL_STRING, WALL_STRING_LEN) != 0)
{
if (da)
*da << DgSqlCode(LME_BUFFER_OVERWRITE)
<< DgString0(invocationInfo_->getUDRName().data())
<< DgString1(bufferName)
<< DgString2("end");
else
throw tmudr::UDRException(
38900,
"UDR %s overwrote space after its %s buffer",
invocationInfo_->getUDRName().data(),
bufferName);
}
return LM_OK;
}
LmResult LmRoutineCppObj::validateWalls(ComDiagsArea *da)
{
LmResult result = LM_OK;
/* not using walls for input tables at the moment
if (numInputTables_ > 0)
{
for (int i=0; i<numInputTables_; i++)
if (validateWall(inputRows_[i],
inputRowLen_,
da,
(i == 0? "first input table" :
(i == 1? "second input table" :
"third or subsequent input table"))) != LM_OK)
result = LM_ERR;
}
*/
if (validateWall(outputRow_, outputRowLen_, da, "output row buffer") != LM_OK)
result = LM_ERR;
return result;
}
LmResult LmRoutineCppObj::handleFinalCall(ComDiagsArea *diagsArea)
{
// C++ routines deallocate the interface objects, which will
// call user code, and therefore could raise an exception.
// Call the dealloc method before calling the destructor,
// so that we can handle any errors or exceptions.
return dealloc(diagsArea);
}