| /**************************************************************************** |
| * 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); |
| } |
| |