| /* -*-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 @@@ |
| **********************************************************************/ |
| /**************************************************************************** |
| * |
| * File: UdrResultSet.cpp |
| * Description: This file contains method definitaions for |
| * UdrResultSet and TmpBuffer classes. |
| * Created: 10/20/2005 |
| * Language: C++ |
| ** |
| ***************************************************************************** |
| */ |
| #include "sqlcli.h" |
| #include "UdrResultSet.h" |
| #include "sql_buffer.h" |
| #include "spinfo.h" |
| #include "UdrDebug.h" |
| #include "ComSizeDefs.h" |
| #include "LmRoutine.h" |
| #include "exp_expr.h" |
| #include "ExpError.h" |
| #include "udrglobals.h" |
| #include "udrutil.h" |
| #include "SQLCLIdev.h" |
| #include "ExpError.h" |
| #include "udrdecs.h" |
| #include "UdrFFDC.h" |
| #include "ComRtUtils.h" |
| |
| extern NABoolean allocateReplyRow(UdrGlobals *UdrGlob, |
| SqlBuffer &replyBuffer, |
| queue_index parentIndex, |
| Int32 replyRowLen, |
| char *&newReplyRow, |
| ControlInfo *&newControlInfo, |
| ex_queue::up_status upStatus); |
| |
| extern NABoolean allocateErrorRow(UdrGlobals *UdrGlob, |
| SqlBuffer &replyBuffer, |
| queue_index parentIndex, |
| NABoolean setDiagsFlag); |
| |
| extern NABoolean allocateEODRow(UdrGlobals *UdrGlob, |
| SqlBuffer &replyBuffer, |
| queue_index parentIndex); |
| |
| // Utility method to deallocate descriptor items created in |
| // UdrResultSet::generateProxySyntax() |
| static void |
| deleteDescItems(ComUInt32 numCols, SQLDESC_ITEM *desc_items, NAMemory *heapPtr) |
| { |
| ComUInt32 index = 0; |
| // Deallocate string values in desc_items and desc_items themselves |
| for (index = 0; index < numCols; index++) |
| { |
| ComUInt32 startingPoint = index * NUMDESC_ITEMS; |
| |
| NADELETEBASIC(desc_items[startingPoint+0].string_val, heapPtr); |
| NADELETEBASIC(desc_items[startingPoint+1].string_val, heapPtr); |
| NADELETEBASIC(desc_items[startingPoint+2].string_val, heapPtr); |
| NADELETEBASIC(desc_items[startingPoint+3].string_val, heapPtr); |
| NADELETEBASIC(desc_items[startingPoint+4].string_val, heapPtr); |
| NADELETEBASIC(desc_items[startingPoint+5].string_val, heapPtr); |
| } |
| |
| heapPtr->deallocateMemory(desc_items); |
| |
| return; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // TmpBuffer methods |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| char *TmpBuffer::getNextRow(ComDiagsArea* &rowDiags, |
| NABoolean singleRowFetchEnabled) |
| { |
| char * retptr = NULL; |
| |
| if (moreRowsToCopy()) |
| { |
| retptr = buffer_ + (index_ * fetchRowSize_); |
| |
| Int32 rowNumber = index_ + 1; |
| while(cliDiags_->getNumber(DgSqlCode::WARNING_)) |
| { |
| // We stop checking the conditions when we hit a condition whose |
| // row number is more than the current row number. Until then, we |
| // copy the conditions from cliDiags_ to rowDiags and delete the |
| // conditions from cliDiags_. |
| // Note that getWarningEntry() expects 1-based index value whereas |
| // deleteWarning() takes 0-based values. |
| const ComCondition *condFromCli = cliDiags_->getWarningEntry(1); |
| Lng32 rowNumberFromCli = condFromCli->getRowNumber(); |
| if (rowNumberFromCli > rowNumber) |
| { |
| break; |
| } |
| else |
| { |
| // In single row fetch case, we have only one row in this |
| // object and all the warnings are related to this row. |
| if (rowNumberFromCli == rowNumber || singleRowFetchEnabled) |
| { |
| if (rowDiags == NULL) |
| rowDiags = ComDiagsArea::allocate(heap_); |
| |
| UDR_ASSERT(rowDiags, "Diags Area for row warning is not present."); |
| ComCondition *cond = rowDiags->makeNewCondition(); |
| *cond = *condFromCli; |
| rowDiags->acceptNewCondition(); |
| } |
| |
| cliDiags_->deleteWarning(0); |
| continue; |
| } |
| } |
| |
| index_++; |
| |
| // Reset pointers if needed |
| if (numRows_ == index_) |
| numRows_ = index_ = 0; |
| } |
| |
| return retptr; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // UdrResultSet Constructor/Destructor definitions |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| // If errors occur, parameter 'd' will be populated with errors. Callers |
| // need to check for diagnostics |
| // This constructor is used on Windows Platform |
| UdrResultSet::UdrResultSet(SPInfo *spInfo, SQLSTMT_ID *stmt, ComDiagsArea &d) |
| : spInfo_(spInfo), |
| state_(RS_INITIATED), |
| lmResultSet_(NULL), |
| proxySyntax_(NULL), |
| rsHandle_(INVALID_RS_HANDLE), |
| rsColumnDesc_(NULL), |
| rsColDescForLM_(NULL), |
| numColumns_(0), |
| exeRowSize_(0), |
| bufferSize_(0), |
| rowsToFetchFromJDBC_(0), |
| stmt_id_(stmt), |
| output_desc_(NULL), |
| quad_fields_(NULL), |
| parentQueueIndex_(0), |
| needToCopyErrorRow_(FALSE), |
| needToCopyEODRow_(FALSE), |
| singleRowFetchEnabled_(FALSE), |
| tmpBuffer_(NULL) |
| { |
| UDR_ASSERT(stmt_id_ != NULL, "Result set statement id not available"); |
| |
| generateProxySyntax(d); |
| |
| UDR_DEBUG1("UdrResultSet %p created.", this); |
| UDR_DEBUG1(" SPInfo %p", spInfo_); |
| UDR_DEBUG1(" LmResultSet %p", lmResultSet_); |
| UDR_DEBUG1(" Stmt name %s", stmt_id_->identifier); |
| UDR_DEBUG1(" Proxy syntax \"%s\"", |
| (proxySyntax_ ? proxySyntax_->data() : "")); |
| } |
| |
| // If errors occur, parameter 'd' will be populated with errors. Callers |
| // need to check for diagnostics |
| // This constructor is used on NSK Platform |
| UdrResultSet::UdrResultSet(SPInfo *spInfo, LmResultSet *lmRS, ComDiagsArea &d) |
| : spInfo_(spInfo), |
| state_(RS_INITIATED), |
| lmResultSet_(lmRS), |
| proxySyntax_(NULL), |
| rsHandle_(INVALID_RS_HANDLE), |
| rsColumnDesc_(NULL), |
| rsColDescForLM_(NULL), |
| numColumns_(0), |
| exeRowSize_(0), |
| bufferSize_(0), |
| rowsToFetchFromJDBC_(0), |
| stmt_id_(NULL), |
| output_desc_(NULL), |
| quad_fields_(NULL), |
| parentQueueIndex_(0), |
| needToCopyErrorRow_(FALSE), |
| needToCopyEODRow_(FALSE), |
| singleRowFetchEnabled_(FALSE), |
| tmpBuffer_(NULL) |
| { |
| UDR_ASSERT(lmResultSet_, "LM Result Set object is not set"); |
| |
| if (lmResultSet_ && lmResultSet_->isCliStmtAvailable()) |
| { |
| stmt_id_ = copyStatementID((SQLSTMT_ID *)lmResultSet_->getStmtID()); |
| UDR_ASSERT(stmt_id_ != NULL, "Result set statement id not available"); |
| } |
| |
| generateProxySyntax(d); |
| |
| UDR_DEBUG1("UdrResultSet %p created.", this); |
| UDR_DEBUG1(" SPInfo %p", spInfo_); |
| UDR_DEBUG1(" LmResultSet %p", lmResultSet_); |
| if (stmt_id_) |
| UDR_DEBUG1(" Stmt name %s", stmt_id_->identifier); |
| UDR_DEBUG1(" Proxy syntax \"%s\"", |
| (proxySyntax_ ? proxySyntax_->data() : "")); |
| } |
| |
| UdrResultSet::~UdrResultSet() |
| { |
| deallocateUDRGeneratedFields(); |
| |
| // Cleanup LmResultSet object |
| if (lmResultSet_) |
| getSPInfo()->getLMHandle()->cleanupLmResultSet(lmResultSet_); |
| |
| lmResultSet_ = NULL; |
| |
| deallocateExeGeneratedFields(); |
| } // UdrResultSet::~UdrResultSet |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // UdrResultSet method definitions |
| // |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| UdrResultSet::deallocateUDRGeneratedFields() |
| { |
| SQLCTX_HANDLE prevContext = 0; |
| |
| if (getContextHandle() != 0) |
| SQL_EXEC_SwitchContext((Lng32) getContextHandle(), &prevContext); |
| |
| |
| NAMemory *heap = collHeap(); |
| |
| if (stmt_id_) |
| { |
| heap->deallocateMemory((void*)stmt_id_->module->module_name); |
| heap->deallocateMemory((void*)stmt_id_->module->charset); |
| heap->deallocateMemory((void*)stmt_id_->module); |
| heap->deallocateMemory((void*)stmt_id_->identifier); |
| heap->deallocateMemory((void*)stmt_id_->charset); |
| heap->deallocateMemory(stmt_id_); |
| } |
| |
| if (output_desc_) |
| { |
| SQL_EXEC_DeallocDesc(output_desc_); |
| heap->deallocateMemory((void*)output_desc_->module); |
| heap->deallocateMemory(output_desc_); |
| } |
| |
| heap->deallocateMemory(quad_fields_); |
| NADELETE(tmpBuffer_, TmpBuffer, collHeap()); |
| NADELETE(proxySyntax_, NAString, collHeap()); |
| heap->deallocateMemory(rsColDescForLM_); |
| |
| rowsToFetchFromJDBC_ = 0; |
| stmt_id_ = NULL; |
| output_desc_ = NULL; |
| proxySyntax_ = NULL; |
| quad_fields_ = NULL; |
| tmpBuffer_ = NULL; |
| rsColDescForLM_ = NULL; |
| |
| parentQueueIndex_ = 0; |
| needToCopyErrorRow_ = FALSE; |
| needToCopyEODRow_ = FALSE; |
| singleRowFetchEnabled_ = FALSE; |
| |
| if (prevContext != 0) |
| SQL_EXEC_SwitchContext_Internal(prevContext, NULL,TRUE); |
| |
| // Let's clear cli diags if there are any |
| SQL_EXEC_ClearDiagnostics(NULL); |
| return; |
| } // UdrResultSet::deallocateUDRGeneratedFields() |
| |
| // Resets all the fields that come from executor in RS_LOAD message |
| void |
| UdrResultSet::deallocateExeGeneratedFields() |
| { |
| rsHandle_ = INVALID_RS_HANDLE; |
| numColumns_ = 0; |
| exeRowSize_ = 0; |
| bufferSize_ = 0; |
| |
| deallocateColumnDesc(); |
| |
| } // UdrResultSet::deallocateExeGeneratedFields |
| |
| const char * |
| UdrResultSet::stateString() |
| { |
| switch (state_) |
| { |
| case RS_INITIATED: return "RS_INITIATED"; |
| case RS_REINITIATED: return "RS_REINITIATED"; |
| case RS_LOADED: return "RS_LOADED"; |
| case RS_UNLOADED: return "RS_UNLOADED"; |
| case RS_FETCH: return "RS_FETCH"; |
| case RS_FETCH_COMPLETE: return "RS_FETCH_COMPLETE"; |
| case RS_CLOSED: return "RS_CLOSED"; |
| case RS_EARLY_CLOSE: return "RS_EARLY_CLOSE"; |
| default: return ComRtGetUnknownString((Int32) state_); |
| } |
| } |
| |
| // Reinitiliazes the UdrResultSet object with the new LmResultSet object. |
| // If lmRS param is NULL, the stmt_id param is used for initialization. |
| // This method is used on both Windows and NSK platforms. On NSK, lmRS param |
| // will have a valid value. On Windows, stmt_id will have a valid value. |
| // |
| // Returns 0 for success |
| // -1 for error |
| Int32 |
| UdrResultSet::reInit(LmResultSet *lmRS, ComDiagsArea &d, SQLSTMT_ID *stmt_id) |
| { |
| state_ = RS_REINITIATED; |
| |
| lmResultSet_ = lmRS; |
| |
| if (lmRS != NULL) |
| { |
| if (lmResultSet_->isCliStmtAvailable()) |
| { |
| // NSK; and RS is using T2 connection |
| stmt_id_ = copyStatementID((SQLSTMT_ID*)lmResultSet_->getStmtID()); |
| UDR_ASSERT(stmt_id_ != NULL, "Result set statement id not available"); |
| } |
| } |
| else |
| { |
| // Windows |
| stmt_id_ = stmt_id; |
| UDR_ASSERT(stmt_id_ != NULL, "Result set statement id not available"); |
| } |
| |
| if (generateProxySyntax(d) != 0) |
| return -1; |
| |
| UDR_DEBUG1("UdrResultSet %p reinitialized.", this); |
| UDR_DEBUG1(" SPInfo %p", spInfo_); |
| UDR_DEBUG1(" LmResultSet %p", lmResultSet_); |
| if (stmt_id_) |
| UDR_DEBUG1(" Stmt name %s", stmt_id_->identifier); |
| UDR_DEBUG1(" Proxy syntax \"%s\"", |
| (proxySyntax_ ? proxySyntax_->data() : "")); |
| |
| return 0; |
| } // UdrResultSet::reInit() |
| |
| // prepareForReinvoke() deallocates all the fields that will be regenerated |
| // by UDR invocation. |
| void |
| UdrResultSet::prepareForReinvoke() |
| { |
| deallocateUDRGeneratedFields(); |
| |
| // Cleanup LmResultSet object |
| if (lmResultSet_) |
| getSPInfo()->getLMHandle()->cleanupLmResultSet(lmResultSet_); |
| |
| lmResultSet_ = NULL; |
| |
| return; |
| } // UdrResultSet::prepareForReinvoke() |
| |
| |
| // Loads column description and other result set row details |
| // with the information from master executor. |
| NABoolean |
| UdrResultSet::load(RSHandle handle, |
| ComUInt32 numRSCols, |
| ComUInt32 rowSize, |
| ComUInt32 bufferSize, |
| UdrParameterInfo *columnDesc, |
| ComDiagsArea &d) |
| { |
| // State should be RS_INITIATED or RS_REINITIATED or RS_UNLOADED |
| if (!isInited() && !isReInited() && !isUnloaded()) |
| { |
| d << DgSqlCode(-UDR_ERR_INVALID_RS_STATE) |
| << DgString0("Load") |
| << DgString1(stateString()); |
| |
| return FALSE; |
| } |
| |
| rsHandle_ = handle; |
| numColumns_ = numRSCols; |
| exeRowSize_ = rowSize; |
| bufferSize_ = bufferSize; |
| |
| populateColumnDesc(columnDesc); |
| |
| state_ = RS_LOADED; |
| |
| UdrGlobals *udrGlob = getSPInfo()->getUdrGlobals(); |
| if (udrGlob->verbose_ && |
| udrGlob->traceLevel_ >= TRACE_DETAILS && |
| udrGlob->showRSLoad_) |
| { |
| ServerDebug(" UdrResultSet %p loaded ", this); |
| ServerDebug(" SPInfo %p", spInfo_); |
| ServerDebug(" LmResultSet %p", lmResultSet_); |
| if (stmt_id_) |
| ServerDebug(" Stmt name %s", stmt_id_->identifier); |
| ServerDebug(" Proxy syntax \"%s\"", |
| (proxySyntax_ ? proxySyntax_->data() : "")); |
| |
| UdrParameterInfo *pi; |
| for (ComUInt32 ind = 0; ind < numColumns_; ind++) |
| { |
| ServerDebug(" "); |
| ServerDebug(" Result Set column# %d: ", ind); |
| pi = &rsColumnDesc_[ind]; |
| pi->display(UdrTraceFile, 4, pi); |
| } |
| } |
| |
| return TRUE; |
| } // UdrResultSet::load() |
| |
| // Makes a copy of SQLSTMT_ID struct |
| SQLSTMT_ID *UdrResultSet::copyStatementID(SQLSTMT_ID *srcStmtID, |
| NABoolean resetStmtInfo) |
| { |
| if (srcStmtID == NULL) |
| return NULL; |
| |
| // SQLSTMT_ID has three pointers fields: module, identifier and charset. |
| // identifier is not null terminated. It's length is stored in |
| // identifier_len field. charset is null terminated string. |
| SQLSTMT_ID *stmt_id = new (collHeap()) SQLSTMT_ID; |
| |
| // Copy all primitive fields |
| stmt_id->version = srcStmtID->version; |
| stmt_id->name_mode = srcStmtID->name_mode; |
| stmt_id->identifier_len = srcStmtID->identifier_len; |
| |
| if (resetStmtInfo && stmt_id->name_mode != stmt_handle) |
| { |
| // We may be copying a SQLSTMT_ID created by the JDBC driver. That |
| // structure may be associated with a StatementInfo object inside |
| // the CLI that contains pointers to the descriptors JDBC is using |
| // for this statement. We don't want to use those same descriptors |
| // here in our MXUDR code. We don't even know if they are still |
| // valid. We need to break the association between this new |
| // SQLSTMT_ID and the StatementInfo. This is done by setting the |
| // handle field to 0 for all name modes other than |
| // stmt_handle. For the stmt_handle mode, StatementInfo |
| // associations are not maintained. |
| stmt_id->handle = 0; |
| } |
| else |
| { |
| stmt_id->handle = srcStmtID->handle; |
| } |
| |
| stmt_id->tag = srcStmtID->tag; |
| |
| // Make a copy of SQLMODULE_ID |
| // SQLMODULE_ID has two pointer fields: module_name (whose length is in |
| // module_name_len) and charset (which is null terminated) |
| SQLMODULE_ID *module_id = new (collHeap()) SQLMODULE_ID; |
| const SQLMODULE_ID *srcModuleID = srcStmtID->module; |
| module_id->version = srcModuleID->version; |
| module_id->module_name_len = srcModuleID->module_name_len; |
| char *module_name = new (collHeap()) char[srcModuleID->module_name_len]; |
| str_cpy_all(module_name, srcModuleID->module_name, |
| srcModuleID->module_name_len); |
| module_id->module_name = (const char *) module_name; |
| |
| ComUInt32 module_charset_len = str_len(srcModuleID->charset); |
| char *module_charset = new (collHeap()) char[module_charset_len + 1]; |
| str_cpy_all(module_charset, srcModuleID->charset, (Lng32) module_charset_len); |
| module_charset[module_charset_len] = '\0'; |
| module_id->charset = (const char *) module_charset; |
| |
| stmt_id->module = module_id; |
| |
| // Copy identifier field |
| char *identifier = new (collHeap()) char[srcStmtID->identifier_len]; |
| str_cpy_all(identifier, srcStmtID->identifier, srcStmtID->identifier_len); |
| stmt_id->identifier = (const char *) identifier; |
| |
| // Copy charset field |
| ComUInt32 stmt_charset_len = str_len(srcStmtID->charset); |
| char *stmt_charset = new (collHeap()) char[stmt_charset_len + 1]; |
| str_cpy_all(stmt_charset, srcStmtID->charset, (Lng32) stmt_charset_len); |
| stmt_charset[stmt_charset_len] = '\0'; |
| stmt_id->charset = (const char *) stmt_charset; |
| |
| return stmt_id; |
| } // udrResultSet::copyStatementID |
| |
| // Sets RS's context and returns the current context handle in oldCtx |
| // Returns 0 for success and other values for failure |
| Int32 |
| UdrResultSet::setContext(SQLCTX_HANDLE &oldCtx, ComDiagsArea &d) |
| { |
| // Set the return value, oldCtx, to 0 |
| oldCtx = 0; |
| |
| // No need for switching context when there is no lmResultSet_ object |
| // or when there is no context handle for this UdrResultSet. |
| // The first thing can happen when there is no JDBC involved in UDR Server |
| // such as on Windows platform. In this case, we will have only one context. |
| // Second case is when Type 4 driver is used. |
| if (lmResultSet_ == NULL || getContextHandle() == 0) |
| return 0; |
| |
| SQLCTX_HANDLE tmpCtx; |
| Int32 result = SQL_EXEC_SwitchContext((Lng32) getContextHandle(), |
| &tmpCtx); |
| |
| if (result < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_SwitchContext") |
| << DgInt0(result); |
| |
| return result; |
| } |
| |
| oldCtx = tmpCtx; |
| return 0; |
| } // UdrResultSet::setContext |
| |
| Int32 |
| UdrResultSet::resetContext(SQLCTX_HANDLE ctxHandle, ComDiagsArea &d) |
| { |
| if (lmResultSet_ == NULL || getContextHandle() == 0) |
| return 0; |
| |
| SQLCTX_HANDLE tmpCtxHandle; |
| Int32 result = SQL_EXEC_SwitchContext_Internal(ctxHandle, &tmpCtxHandle,TRUE); |
| |
| if (result < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_SwitchContext") |
| << DgInt0(result); |
| |
| return result; |
| } |
| |
| UDR_ASSERT(tmpCtxHandle == (SQLCTX_HANDLE) getContextHandle(), |
| "Wrong context Handle."); |
| |
| return result; |
| } // UdrResultSet::resetContext |
| |
| // populates proxySyntax_ field |
| Int32 |
| UdrResultSet::generateProxySyntax(ComDiagsArea &d) |
| { |
| Lng32 retcode = 0; |
| ComUInt32 index = 0; |
| |
| if (lmResultSet_ && ! lmResultSet_->isCliStmtAvailable()) |
| { |
| // RS is using T4 connection. |
| proxySyntax_ = new (collHeap()) NAString(lmResultSet_->getProxySyntax()); |
| return retcode; |
| } |
| |
| |
| if (state_ != RS_INITIATED && state_ != RS_REINITIATED) |
| { |
| // 'Initiate' is not a request that can be asked by UDR server |
| // clients. It's an internal request for UDR server. |
| d << DgSqlCode(-UDR_ERR_INVALID_RS_STATE) |
| << DgString0("Initiate") |
| << DgString1(stateString()); |
| |
| return -1; |
| } |
| |
| SQLCTX_HANDLE prevContext = 0; |
| if (setContext(prevContext, d) != 0) |
| return -1; |
| |
| if (stmt_id_ == NULL) |
| { |
| if (lmResultSet_ == NULL) |
| d << DgSqlCode(-UDR_ERR_INTERNAL_ERROR) |
| << DgString0("LmResultSet object is not set"); |
| else |
| stmt_id_ = copyStatementID((SQLSTMT_ID*)lmResultSet_->getStmtID()); |
| } |
| |
| UDR_ASSERT(stmt_id_ != NULL, "NULL statement id for result set"); |
| |
| // Allocate an output descriptor where column information |
| // will be stored |
| output_desc_ = new (collHeap()) SQLDESC_ID; |
| SQLMODULE_ID *outmodule = new (collHeap()) SQLMODULE_ID; |
| output_desc_->name_mode = desc_handle; |
| output_desc_->identifier = 0; |
| output_desc_->handle = 0; |
| output_desc_->module = outmodule; |
| outmodule->module_name =0; |
| |
| retcode = SQL_EXEC_AllocDesc(output_desc_, 500); |
| if (retcode < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_AllocDesc") |
| << DgInt0(retcode); |
| |
| return -1; |
| } |
| |
| // Describe Statement. |
| retcode = SQL_EXEC_DescribeStmt(stmt_id_, NULL, output_desc_); |
| if (retcode < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_DescribeStmt") |
| << DgInt0(retcode); |
| |
| return -1; |
| } |
| |
| // Check how many columns there are in the output_desc |
| ComUInt32 numColumns = 0; |
| retcode = SQL_EXEC_GetDescEntryCount(output_desc_, (Lng32*) &numColumns); |
| if (retcode < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_GetDescEntryCount") |
| << DgInt0(retcode); |
| |
| return -1; |
| } |
| |
| // The CLI statement does not have any output columns. This situation |
| // may be rare but quite possible on windows because of our hack to |
| // produce RS on windows. |
| if (numColumns <= 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INVALID_RS_COLUMN_COUNT) |
| << DgInt0(numColumns); |
| |
| return -1; |
| } |
| |
| // We want NUMDESC_ITEMS number of different information |
| // for each column. desc_items will have room for |
| // (numColumns * NUMDESC_ITEMS) items. The information is arranged |
| // as (C1.I1), (C1.I2), ... (C1.Im), |
| // (C2.I1), (C2.I2), ... (C2.Im) |
| // ... ... |
| // (Cn.I1), (Cn.I2), ... (Cn.Im) and so on |
| // for numColumns = n and NUMDESC_ITEMS = m. |
| SQLDESC_ITEM *desc_items = |
| new (collHeap()) SQLDESC_ITEM[numColumns * NUMDESC_ITEMS]; |
| |
| for (index = 0; index < numColumns; index++) |
| { |
| ComUInt32 startingPoint = index * NUMDESC_ITEMS; |
| |
| // We are allocating ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN size of |
| // sqldesc_catalog_name, sqldesc_schema_name, sqldesc_table_name |
| // sqldesc_name, sqldesc_text_format and sqldesc_heading. |
| // TBD: Not sure if it is okay for sqldesc_heading |
| char *sqldesc_catalog_name = |
| new (collHeap()) char[ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN]; |
| desc_items[startingPoint+0].item_id = SQLDESC_CATALOG_NAME; |
| desc_items[startingPoint+0].entry = (Lng32) index + 1; |
| desc_items[startingPoint+0].num_val_or_len = |
| ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN; |
| desc_items[startingPoint+0].string_val = sqldesc_catalog_name; |
| |
| char *sqldesc_schema_name = |
| new (collHeap()) char[ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN]; |
| desc_items[startingPoint+1].item_id = SQLDESC_SCHEMA_NAME; |
| desc_items[startingPoint+1].entry = (Lng32)index + 1; |
| desc_items[startingPoint+1].num_val_or_len = |
| ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN; |
| desc_items[startingPoint+1].string_val = sqldesc_schema_name; |
| |
| char *sqldesc_table_name = |
| new (collHeap()) char[ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN]; |
| desc_items[startingPoint+2].item_id = SQLDESC_TABLE_NAME; |
| desc_items[startingPoint+2].entry = (Lng32) index + 1; |
| desc_items[startingPoint+2].num_val_or_len = |
| ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN; |
| desc_items[startingPoint+2].string_val = sqldesc_table_name; |
| |
| char *sqldesc_column_name = |
| new (collHeap()) char[ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN]; |
| desc_items[startingPoint+3].item_id = SQLDESC_NAME; |
| desc_items[startingPoint+3].entry = (Lng32) index + 1; |
| desc_items[startingPoint+3].num_val_or_len = |
| ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN; |
| desc_items[startingPoint+3].string_val = sqldesc_column_name; |
| |
| char *sqldesc_text_format = |
| new (collHeap()) char[ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN]; |
| desc_items[startingPoint+4].item_id = SQLDESC_TEXT_FORMAT; |
| desc_items[startingPoint+4].entry = (Lng32) index + 1; |
| desc_items[startingPoint+4].num_val_or_len = |
| ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN; |
| desc_items[startingPoint+4].string_val = sqldesc_text_format; |
| |
| char *sqldesc_heading = |
| new (collHeap()) char[ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN]; |
| desc_items[startingPoint+5].item_id = SQLDESC_HEADING; |
| desc_items[startingPoint+5].entry = (Lng32) index + 1; |
| desc_items[startingPoint+5].num_val_or_len = |
| ComMAX_ANSI_IDENTIFIER_EXTERNAL_LEN; |
| desc_items[startingPoint+5].string_val = sqldesc_heading; |
| |
| desc_items[startingPoint+6].item_id = SQLDESC_NULLABLE; |
| desc_items[startingPoint+6].entry = (Lng32) index + 1; |
| } |
| |
| retcode = SQL_EXEC_GetDescItems2(output_desc_, |
| (Lng32) numColumns * NUMDESC_ITEMS, |
| desc_items); |
| if (retcode < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_GetDescItems2") |
| << DgInt0(retcode); |
| |
| deleteDescItems(numColumns, desc_items, collHeap()); |
| return -1; |
| } |
| |
| // Now put together Proxy Syntax |
| proxySyntax_ = new (collHeap()) NAString(); |
| NAString &proxy = *proxySyntax_; |
| proxy += "SELECT * FROM TABLE ( SP_RESULT_SET ( "; |
| |
| for (index = 0; index < numColumns; index++) |
| { |
| ComUInt32 startingPoint = index * NUMDESC_ITEMS; |
| |
| SQLDESC_ITEM &catalog_name = desc_items[startingPoint+0]; |
| catalog_name.string_val[catalog_name.num_val_or_len] = '\0'; |
| |
| SQLDESC_ITEM &schema_name = desc_items[startingPoint+1]; |
| schema_name.string_val[schema_name.num_val_or_len] = '\0'; |
| |
| SQLDESC_ITEM &table_name = desc_items[startingPoint+2]; |
| table_name.string_val[table_name.num_val_or_len] = '\0'; |
| |
| SQLDESC_ITEM &column_name = desc_items[startingPoint+3]; |
| column_name.string_val[column_name.num_val_or_len] = '\0'; |
| |
| SQLDESC_ITEM &text_format = desc_items[startingPoint+4]; |
| text_format.string_val[text_format.num_val_or_len] = '\0'; |
| |
| SQLDESC_ITEM &heading = desc_items[startingPoint+5]; |
| heading.string_val[heading.num_val_or_len] = '\0'; |
| |
| SQLDESC_ITEM &null_value = desc_items[startingPoint+6]; |
| |
| // To take care of any delimited identifiers, |
| // convert the internal names of catalog, schema, table and |
| // columns to external format by calling ToAnsiIdentifier |
| NAString quote("\""), dot("."), space(" "); |
| if (catalog_name.num_val_or_len != 0) |
| proxy += ToAnsiIdentifier(catalog_name.string_val) + dot; |
| |
| if (schema_name.num_val_or_len != 0) |
| proxy += ToAnsiIdentifier(schema_name.string_val) + dot; |
| |
| if (table_name.num_val_or_len != 0) |
| proxy += ToAnsiIdentifier(table_name.string_val) + dot; |
| |
| // Just in case, if column name is null, set '(EXPR)' as |
| // column name |
| if (column_name.num_val_or_len != 0) |
| proxy += ToAnsiIdentifier(column_name.string_val) + space; |
| else |
| proxy += quote + "(EXPR)" + quote + space; |
| |
| proxy += text_format.string_val + space; |
| |
| NAString quotedHeading; |
| if (heading.num_val_or_len > 0) |
| ToQuotedString(quotedHeading, heading.string_val); |
| proxy += ((heading.num_val_or_len > 0) ? ("HEADING " + quotedHeading) |
| : ""); |
| proxy += space; |
| proxy += (null_value.num_val_or_len ? "" : "NOT NULL"); |
| proxy += ((index == numColumns - 1) ? " ) )" : ", "); |
| } |
| |
| // Restore the previous context |
| if (prevContext != 0) |
| { |
| if (resetContext(prevContext, d) != 0) |
| { |
| deleteDescItems(numColumns, desc_items, collHeap()); |
| return -1; |
| } |
| } |
| |
| deleteDescItems(numColumns, desc_items, collHeap()); |
| |
| return 0; |
| } // UdrResultSet::generateProxySyntax |
| |
| // Allocates and populates rsColumnDesc_ & rsColDescForLM_ fields |
| void |
| UdrResultSet::populateColumnDesc(UdrParameterInfo *columnDesc) |
| { |
| // Store UdrParameterInfo objects |
| rsColumnDesc_ = (UdrParameterInfo*) |
| collHeap()->allocateMemory(numColumns_ * sizeof(UdrParameterInfo)); |
| memset(rsColumnDesc_, 0, numColumns_ * sizeof(UdrParameterInfo)); |
| |
| rsColDescForLM_ = (LmParameter *) |
| collHeap()->allocateMemory(numColumns_ * sizeof(LmParameter)); |
| memset(rsColDescForLM_, 0, numColumns_ * sizeof(LmParameter)); |
| |
| for (ComUInt32 index = 0; index < numColumns_; index++) |
| { |
| rsColumnDesc_[index] = columnDesc[index]; |
| |
| UdrParameterInfo *udrColumn = &rsColumnDesc_[index]; |
| LmParameter *p = &rsColDescForLM_[index]; |
| |
| // Determine the encoding charset. We will use =_SQL_MX_ISO_MAPPING |
| // define value if the param's charset is ISO88591. |
| CharInfo::CharSet charset = |
| (CharInfo::CharSet) udrColumn->getEncodingCharSet(); |
| if (charset == CharInfo::ISO88591) |
| charset = spInfo_->getUdrGlobals()->getIsoMapping(); |
| |
| p->init((ComFSDataType) udrColumn->getFSType(), |
| udrColumn->getPrec(), |
| udrColumn->getScale(), |
| charset, |
| (CharInfo::Collation) udrColumn->getCollation(), |
| COM_OUTPUT_COLUMN, |
| FALSE, // objMap |
| RS_NONE, |
| 0, 0, 0, 0, 0, 0, // input lengths and offsets |
| udrColumn->getDataOffset(), |
| udrColumn->getDataLength(), |
| udrColumn->getNullIndicatorOffset(), |
| udrColumn->getNullIndicatorLength(), |
| udrColumn->getVCLenIndOffset(), |
| udrColumn->getVCIndicatorLength(), |
| NULL); // parameter name |
| } |
| |
| } // UdrResultSet::populateColumnDesc |
| |
| // Deallocates rsColumnDesc_ field |
| void |
| UdrResultSet::deallocateColumnDesc() |
| { |
| collHeap()->deallocateMemory(rsColumnDesc_); |
| rsColumnDesc_ = NULL; |
| } // udrResultSet::deallocateColumnDesc |
| |
| // Fetch rows from JDBC(if needed) & CLI and populate rows in |
| // the replyBuffer. |
| // |
| // Returns the number of rows that were copied into replyBuffer excluding |
| // ERROR and EOD rows. |
| // |
| // Note that errors from SQL_EXEC_FETCH are considered to be user errors, |
| // so an ERROR/EOD rows will be added to replyBuffer and the number of |
| // rows copied so far will be returned. |
| // |
| // |
| Lng32 |
| UdrResultSet::fetchRows(UdrGlobals *udrGlob, |
| SqlBuffer *requestBuffer, |
| SqlBuffer *replyBuffer, |
| ComDiagsArea &mainDiags, |
| NAList<ComDiagsArea*> *rowDiagsList) |
| { |
| if (state_ != RS_LOADED && |
| state_ != RS_FETCH && |
| state_ != RS_REINITIATED && |
| state_ != RS_EARLY_CLOSE) |
| { |
| mainDiags << DgSqlCode(-UDR_ERR_INVALID_RS_STATE) |
| << DgString0("Fetch or Continue") |
| << DgString1(stateString()); |
| |
| return -1; |
| } |
| |
| SQLCTX_HANDLE prevContext = 0; |
| if (setContext(prevContext, mainDiags) != 0) |
| return -1; |
| |
| NABoolean eofEncountered = FALSE; |
| // We need to set up_state.parentIndex correctly. The request |
| // contains an empty row and will contain down_state.parentIndex. |
| // We need to copy that into our rows. |
| // We will have valid requestBuffer only for FETCH requests. CONTINUE |
| // requests will not have requestBuffer. So save parentIndex for later |
| // use with CONTINUE messages. |
| if (requestBuffer) |
| { |
| down_state downState; |
| tupp requestRow; |
| Lng32 retcode = requestBuffer->moveOutSendOrReplyData |
| (TRUE, // [IN] sending? (vs. replying) |
| &downState, // [OUT] queue state |
| requestRow, // [OUT] new data tupp_descriptor |
| NULL, // [OUT] new ControlInfo area |
| NULL, // [OUT] new diags tupp_descriptor |
| NULL // [OUT] new stats area |
| ); |
| |
| parentQueueIndex_ = downState.parentIndex; |
| } |
| |
| if (state_ == RS_EARLY_CLOSE) |
| { |
| // An RS CLOSE request arrived at some point. We need to return |
| // EOD to the executor and call the close() method. |
| NABoolean ok = copyEODRowIntoSqlBuffer(replyBuffer, parentQueueIndex_); |
| |
| // Something went wrong internally if there's no room for an EOD |
| // entry in this reply buffer |
| UDR_ASSERT(ok, "Failed to add EOD to reply buffer"); |
| |
| // Transition to the CLOSED state and return. The return value is |
| // the number of rows written into the reply buffer. |
| close(&mainDiags); |
| return 0; |
| } |
| |
| state_ = RS_FETCH; |
| |
| // Allocate databuffer with bufferSize_ bytes |
| if (tmpBuffer_ == NULL) |
| tmpBuffer_ = new (collHeap()) TmpBuffer(bufferSize_, exeRowSize_, |
| collHeap()); |
| |
| // The local variable numBufferedRows is incremented when ever a row |
| // is copied in the sql buffer (replyBuffer) means copied directly |
| // into sql Buffer by fetchFromJDBC() or fetchFromCLI(). |
| ComUInt32 numBufferedRows = 0; |
| |
| // Check if we have any rows that were fetched earlier and |
| // not yet sent to caller. |
| |
| if (tmpBuffer_->moreRowsToCopy()) |
| { |
| copyRowsIntoSqlBuffer(replyBuffer, |
| parentQueueIndex_, |
| numBufferedRows, |
| rowDiagsList); |
| } |
| |
| // By this time make sure that tmpBuffer_ is empty. |
| // If we got out from copyRowsIntoSqlBuffer() function and |
| // tmpBuffer_ is not empty, then it means SQL buffer is full. |
| // In that case, don't fetch any more rows from JDBC or CLI. |
| if (!(tmpBuffer_->moreRowsToCopy())) |
| { |
| // Now check if we have to fetch any special rows. |
| // For SPJs, special rows mean those rows that were cached |
| // in JDBC buffer. |
| if (lmResultSet_->moreSpecialRows()) |
| { |
| fetchRowsFromJDBC(udrGlob, |
| replyBuffer, |
| numBufferedRows, // in/out value |
| eofEncountered, |
| mainDiags, |
| rowDiagsList); |
| } |
| // Fetch rows from CLI only when JDBC row access was successful |
| // and eof is not encountered and there is space in the sql buffer(given |
| // by no rows in tmpBuffer_). |
| // If CLI statement is closed which is possible |
| // in case of unique fetch row, fetchRowsFromJDBC already has added EOD Row |
| // in sql Buffer and set eofEncountered to TRUE. |
| if (mainDiags.getNumber(DgSqlCode::ERROR_) == 0 && !eofEncountered && |
| !(tmpBuffer_->moreRowsToCopy())) |
| { |
| fetchRowsFromCLI(udrGlob, |
| replyBuffer, |
| numBufferedRows, // in/out value |
| mainDiags, |
| rowDiagsList); |
| } |
| } |
| // Restore the previous context |
| if (prevContext != 0) |
| { |
| if (resetContext(prevContext, mainDiags) != 0) |
| return -1; |
| } |
| |
| return numBufferedRows; |
| } // udrResultSet::fetchRows |
| |
| // Fetches rows that are cached in JDBC buffers. |
| // Note 8/4/06: |
| // Right now the function fetchSpecialRows() in LmResultSetJava.cpp |
| // return one row at a time from a ResultSet. The fetchRowsFromJDBC() code |
| // is based on this fact. So if in the future it returns more than one row, |
| // then this function code will be required to change. |
| |
| void |
| UdrResultSet::fetchRowsFromJDBC(UdrGlobals *udrGlob, |
| SqlBuffer *replyBuffer, |
| ComUInt32 &numBufferedRows, |
| NABoolean &eofEncountered, |
| ComDiagsArea &mainDiags, |
| NAList<ComDiagsArea*> *rowDiagsList) |
| { |
| eofEncountered = FALSE; |
| |
| |
| |
| NABoolean done = FALSE; |
| while (lmResultSet_->moreSpecialRows()) |
| { |
| // At this time tempBuffer_ should be empty |
| ComDiagsArea *diagsArea = tmpBuffer_->getDiags(); |
| |
| // Right now, Function fetchSpecialRows() processes one row at a time |
| // and returns 1, if it succeeds. If in future it processes more than |
| // one row and returns more than 1, then this function will require |
| // changes too. |
| Lng32 numRows = |
| lmResultSet_->fetchSpecialRows(tmpBuffer_->getBufferPtr(), |
| rsColDescForLM_, numColumns_, |
| mainDiags, diagsArea); |
| if (numRows == -1) |
| { |
| needToCopyErrorRow_ = TRUE; |
| needToCopyEODRow_ = TRUE; |
| |
| if (copyErrorRowIntoSqlBuffer(replyBuffer, parentQueueIndex_, mainDiags)) |
| needToCopyErrorRow_ = FALSE; |
| else |
| done = TRUE; |
| |
| // Write EOD row only after writing ERROR row, if we have to write one. |
| // Change state_ only when EOD row is written. |
| if (done != TRUE && copyEODRowIntoSqlBuffer(replyBuffer, parentQueueIndex_)) |
| { |
| needToCopyEODRow_ = FALSE; |
| state_ = RS_FETCH_COMPLETE; |
| eofEncountered = TRUE; |
| } |
| done = TRUE; |
| } |
| else |
| UDR_ASSERT((numRows == 1) || (numRows == 0), "Language Manager returned more than one row, or found an exception or found other errors." ); |
| |
| if (done != TRUE && !lmResultSet_->isCliStmtAvailable() && numRows == 0) |
| { |
| // There are no more rows to read from T4 driver. |
| needToCopyEODRow_ = TRUE; |
| if (copyEODRowIntoSqlBuffer(replyBuffer, parentQueueIndex_)) |
| { |
| needToCopyEODRow_ = FALSE; |
| state_ = RS_FETCH_COMPLETE; |
| eofEncountered = TRUE; |
| } |
| |
| done = TRUE; |
| } |
| |
| if (done == TRUE) { |
| return; |
| } |
| |
| // By this time tempBuffer_ should not be empty. |
| tmpBuffer_->setNumRows(numRows); |
| |
| // Copy rows from TmpBuffer into replyBuffer |
| if (tmpBuffer_->moreRowsToCopy()) |
| { |
| ComUInt32 numRowsCopied = 0; |
| copyRowsIntoSqlBuffer(replyBuffer, |
| parentQueueIndex_, |
| numRowsCopied, |
| rowDiagsList); |
| if (numRowsCopied == 0) |
| return; // there is no space in buffer |
| else |
| numBufferedRows += numRowsCopied; |
| } |
| } |
| |
| |
| // If CLI statement is closed, copy end of data Row into sqlBuffer |
| // and set eofEncounter TRUE, so that we will not be fetching from CLI. |
| // e.g. If there is an unique row fetch (e.g select with where on |
| // primary key) jdbc gets 1 row, and at that time CLI statement gets closed. |
| |
| if (lmResultSet_->isCliStmtAvailable() && lmResultSet_->isCLIStmtClosed()) |
| { |
| if (copyEODRowIntoSqlBuffer(replyBuffer, parentQueueIndex_)) |
| { |
| needToCopyEODRow_ = FALSE; |
| state_ = RS_FETCH_COMPLETE; |
| eofEncountered = TRUE; |
| } |
| } |
| |
| |
| } // udrResultSet::fetchRowsFromJDBC |
| |
| |
| // Sets up the quad fields for multiple row fetching from CLI. |
| // Quad fields contain four fields. |
| // 1. Data ptr, 2. data size |
| // 3. Null indicator ptr and 4. Null indicator size |
| // We need to setup a quadfield for each output column. CLI uses data size |
| // to advance the pointer to copy next row value. |
| // |
| // For example: C1R1 is copied at DataPtr |
| // C1R2 is copied at DataPtr+data size |
| // C1R3 is copied at DataPtr+(2 * data size) and so on |
| // Similar things happen for Null indicator. |
| // |
| // setupQuadFields() sets up quadfields in such way that CLI copies all |
| // columns of a row next to each other and also all the rows are copied |
| // next to each other. |
| // |
| // Note: |
| // 1. tmpBuffer_ is set up to hold buffersize/rowsize number of rows. |
| // These number of rows may not fit into given SqlBuffer because of |
| // SqlBuffer's overhead. |
| // 2. It is possible for CLI to return fewer than requested number of rows. |
| // So in fetchRowsFromCLI(), we make multiple attempts to fill tmpBuffer_ |
| // (and in turn SqlBuffer). |
| // |
| // returns FALSE for errors |
| // TRUE for success |
| NABoolean |
| UdrResultSet::setupQuadFields(ComDiagsArea &d) |
| { |
| // We can reuse the quad_fields_ that were setup earlier. |
| if (quad_fields_ != NULL) |
| return TRUE; |
| |
| // The Quad_Fields point to memory locations in tmpBuffer_ |
| ComUInt32 nBytes = numColumns_ * sizeof(SQLCLI_QUAD_FIELDS); |
| if (nBytes > 0) |
| { |
| quad_fields_ = (SQLCLI_QUAD_FIELDS *) collHeap()->allocateMemory(nBytes); |
| memset(quad_fields_, 0, nBytes); |
| } |
| |
| for (ComUInt32 index = 0; index < numColumns_; index++) |
| { |
| // Next we want to compute the offset of the data region for this |
| // column. There are two cases to consider: |
| // |
| // a. VARCHAR columns. The CLI returns VARCHAR length indicators |
| // with the data. We setup quad fields so that |
| // - the beginning of the data region is the offset of the |
| // VARCHAR length indicator |
| // - the "quad field layout" does not include the size of the |
| // length prefix. This may be non-intuitive but it's what the |
| // CLI expects. |
| // |
| // b. Non-VARCHAR columns. Just use the data offset that was |
| // provided in the RS LOAD message. |
| |
| const UdrParameterInfo ¶mInfo = rsColumnDesc_[index]; |
| ComUInt32 vcIndLen = paramInfo.getVCIndicatorLength(); |
| ComUInt32 dataOffset = paramInfo.getDataOffset(); |
| ComUInt32 dataLayout = tmpBuffer_->getFetchRowSize(); |
| ComUInt32 nullIndLayout = dataLayout; |
| const char *buf = tmpBuffer_->getBufferPtr(); |
| |
| if (vcIndLen > 0) |
| { |
| ComUInt32 vcLenOffset = paramInfo.getVCLenIndOffset(); |
| UDR_ASSERT((vcLenOffset + vcIndLen) == dataOffset, |
| "VARCHAR indicator must immediately precede data"); |
| UDR_ASSERT(vcIndLen % 2 == 0, "VARCHAR indicator len must be even"); |
| dataOffset = vcLenOffset; |
| dataLayout -= vcIndLen; |
| } |
| |
| quad_fields_[index].var_ptr = (void *) (buf + dataOffset); |
| quad_fields_[index].var_layout = (Lng32) dataLayout; |
| |
| if (paramInfo.isNullable()) |
| { |
| ComUInt32 nullIndOffset = paramInfo.getNullIndicatorOffset(); |
| quad_fields_[index].ind_ptr = (void *) (buf + nullIndOffset); |
| quad_fields_[index].ind_layout = (Lng32) nullIndLayout; |
| } |
| } |
| |
| Lng32 rowsPerFetch = (Lng32) tmpBuffer_->getNumRowsPerFetch(); |
| #ifdef UDR_DEBUG |
| char * e = getenv("UDR_RS_FETCH_SIZE"); |
| if (e && e[0]) |
| { |
| Lng32 fetchSize = atol(e); |
| if (fetchSize > 0 && fetchSize < rowsPerFetch) |
| rowsPerFetch = fetchSize; |
| } |
| #endif |
| |
| singleRowFetchEnabled_ = (rowsPerFetch == 1) ? TRUE : FALSE; |
| |
| UDR_DEBUG2("Fetch size for Result Set %p is set to %d.", |
| this, rowsPerFetch); |
| |
| Lng32 rowset_status[1]; |
| Int32 retcode = SQL_EXEC_SETROWSETDESCPOINTERS(output_desc_, |
| rowsPerFetch, |
| rowset_status, |
| 1, |
| (Lng32) numColumns_, |
| quad_fields_); |
| if (retcode < 0) |
| { |
| d << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_SETROWSETDESCPOINTERS") |
| << DgInt0(retcode); |
| |
| return FALSE; |
| } |
| |
| return TRUE; |
| } // udrResultSet::setupQuadFields |
| |
| |
| void |
| UdrResultSet::fetchRowsFromCLI(UdrGlobals *udrGlob, |
| SqlBuffer *replyBuffer, |
| ComUInt32 &numBufferedRows, |
| ComDiagsArea &mainDiags, |
| NAList<ComDiagsArea*> *rowDiagsList) |
| { |
| Lng32 retcode = 0; |
| const char *moduleName = "fetchRowsFromCLI"; |
| |
| // First, setup quad fields to point to TmpBuffer so that CLI copies |
| // row values into TmpBuffer |
| if (setupQuadFields(mainDiags) == FALSE) |
| return; |
| |
| // If JDBC used nowait for this stmt, let's disassociate |
| // the stmt from the QFO file. UDR Server uses waited |
| // mode to fetch rows from RS stmt. |
| if (stmt_id_->tag != 0) |
| { |
| retcode = SQL_EXEC_DisassocFileNumber(stmt_id_); |
| if (retcode < 0) |
| { |
| mainDiags << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_DisassocFileNumber") |
| << DgInt0(retcode); |
| |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| } |
| |
| stmt_id_->tag = 0; |
| } |
| |
| // Check if we have any rows that were fetched earlier and |
| // not yet sent to caller. |
| if (tmpBuffer_->moreRowsToCopy()) |
| { |
| copyRowsIntoSqlBuffer(replyBuffer, |
| parentQueueIndex_, |
| numBufferedRows, |
| rowDiagsList); |
| } |
| |
| // Fetch more rows and copy them to SqlBuffer. |
| // Looks like there was a bug in CLI code where it returns fewer |
| // than requested rows even though there are so many rows. So we call |
| // FETCH in loop until the reply buffer is full, error occurred or |
| // EOD reached. |
| // |
| // The following loop is executed with the following conditions |
| // - EOD is not reached (either normally or abnormally) |
| // - tmpBuffer_ is empty |
| while (! needToCopyEODRow_ && |
| ! tmpBuffer_->moreRowsToCopy()) |
| { |
| retcode = SQL_EXEC_ClearDiagnostics(stmt_id_); |
| if (retcode < 0) |
| { |
| mainDiags << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_ClearDiagnostics") |
| << DgInt0(retcode); |
| |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| continue; |
| } |
| |
| // Start fetching rows into tmpBuffer_ (pointed to by output_desc_) |
| retcode = SQL_EXEC_Fetch(stmt_id_, output_desc_, 0, 0); |
| |
| if (retcode < 0) |
| { |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| } |
| |
| if (retcode == 100) |
| { |
| needToCopyErrorRow_ = FALSE; |
| needToCopyEODRow_ = TRUE; |
| } |
| |
| if (retcode != 0 && retcode != 100) |
| { |
| // There were errors or warnings, let's get CLI diags into tmpBuffer_. |
| NAMemory *h = collHeap(); |
| ULng32 bufSize = 2048; |
| char *bufForPackedDiags = (char *) h->allocateMemory(bufSize); |
| ULng32 bufSizeNeeded = 0; |
| Lng32 messageObjType = 0; |
| Lng32 messageObjVersion = 0; |
| Lng32 fetchRetcode = retcode; |
| |
| retcode = SQL_EXEC_GetPackedDiagnostics_Internal( |
| /*OUT*/ bufForPackedDiags, |
| /*IN*/ bufSize, |
| /*OUT*/ &bufSizeNeeded, |
| /*OUT*/ &messageObjType, |
| /*OUT*/ &messageObjVersion); |
| |
| if (retcode >= 0 && bufSizeNeeded > bufSize) |
| { |
| h->deallocateMemory(bufForPackedDiags); |
| bufSize = bufSizeNeeded; |
| bufForPackedDiags = (char *) h->allocateMemory(bufSize); |
| retcode = SQL_EXEC_GetPackedDiagnostics_Internal( |
| /*OUT*/ bufForPackedDiags, |
| /*IN*/ bufSize, |
| /*OUT*/ &bufSizeNeeded, |
| /*OUT*/ &messageObjType, |
| /*OUT*/ &messageObjVersion); |
| } |
| |
| if (retcode >= 0) |
| { |
| ComDiagsArea &d = *(tmpBuffer_->getDiags()); |
| d.unpackObj(messageObjType, |
| messageObjVersion, |
| TRUE, |
| bufSizeNeeded, |
| bufForPackedDiags); |
| |
| h->deallocateMemory(bufForPackedDiags); |
| bufForPackedDiags = NULL; |
| |
| if (fetchRetcode < 0 && d.getNumber(DgSqlCode::ERROR_) == 0) |
| { |
| // There are cases where Fetch returns error but no error |
| // conditions exists in CLI. These situations are addressed |
| // here by raising internal error. |
| mainDiags << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_Fetch") |
| << DgInt0(fetchRetcode); |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| continue; |
| } |
| } |
| else |
| { |
| h->deallocateMemory(bufForPackedDiags); |
| bufForPackedDiags = NULL; |
| |
| mainDiags << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_MergeDiagnostics_Internal") |
| << DgInt0(retcode); |
| |
| SQL_EXEC_ClearDiagnostics(stmt_id_); |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| continue; |
| } |
| } |
| |
| retcode = SQL_EXEC_ClearDiagnostics(stmt_id_); |
| if (retcode < 0) |
| { |
| mainDiags << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_ClearDiagnostics") |
| << DgInt0(retcode); |
| |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| continue; |
| } |
| |
| // Find the number of rows fetched. |
| Lng32 numRowsFetched = 0; |
| retcode = SQL_EXEC_GetDescItem(output_desc_, 1, |
| SQLDESC_ROWSET_NUM_PROCESSED, |
| &numRowsFetched, 0, 0, 0, 0); |
| if (retcode < 0) |
| { |
| mainDiags << DgSqlCode(-UDR_ERR_INTERNAL_CLI_ERROR) |
| << DgString0("SQL_EXEC_GetDescItem") |
| << DgInt0(retcode); |
| |
| SQL_EXEC_ClearDiagnostics(stmt_id_); |
| |
| needToCopyErrorRow_ = needToCopyEODRow_ = TRUE; |
| continue; |
| } |
| |
| tmpBuffer_->setNumRows((ComUInt32) numRowsFetched); |
| |
| if (numRowsFetched > 0 && |
| udrGlob->verbose_ && |
| udrGlob->traceLevel_ >= TRACE_DATA_AREAS && |
| udrGlob->showInvoke_) |
| { |
| ComUInt32 numRows = tmpBuffer_->getNumRows(); |
| ComUInt32 rowLen = tmpBuffer_->getExeRowSize(); |
| const char *tmpBufData = tmpBuffer_->getBufferPtr(); |
| ComUInt32 tmpBufLen = tmpBuffer_->getSize(); |
| |
| ServerDebug(""); |
| ServerDebug("[UdrServ (%s)] CLI fetch buffer", moduleName); |
| ServerDebug("[UdrServ (%s)] N rows %ld, exe row size %u, buf size %u", |
| moduleName, numRows, rowLen, tmpBufLen); |
| |
| // Display either the full buffer, or N+1 rows, whichever region |
| // is smaller |
| ComUInt32 displayBytes = (numRows + 1) * rowLen; |
| if (displayBytes > tmpBufLen) |
| displayBytes = tmpBufLen; |
| |
| dumpBuffer((unsigned char *) tmpBufData, displayBytes); |
| } |
| |
| // Now copy as many rows as we can fit in replyBuffer from tmpBuffer_ |
| ComUInt32 numRowsCopied = 0; |
| copyRowsIntoSqlBuffer(replyBuffer, |
| parentQueueIndex_, |
| numRowsCopied, |
| rowDiagsList); |
| numBufferedRows += numRowsCopied; |
| |
| } // while loop |
| |
| // Even if we have called copyCLIRowsIntoSqlBuffer() above, we may |
| // still have some rows in tmpBuffer_ that need to be copied to replyBuffer. |
| // This is possible because there may not be enough space in |
| // replyBuffer for all the rows read. So write ERROR/EOD row only if no |
| // more rows in tmpBuffer_. |
| if (! tmpBuffer_->moreRowsToCopy()) |
| { |
| // Note that it is possible that there is no space for ERROR/EOD row. |
| // That is not a problem and we write these rows next time. |
| if (needToCopyErrorRow_) |
| { |
| if (copyErrorRowIntoSqlBuffer(replyBuffer, parentQueueIndex_, mainDiags)) |
| needToCopyErrorRow_ = FALSE; |
| } |
| |
| // Write EOD row only after writing ERROR row, if we have to write one. |
| // Change state_ only when EOD row is written. |
| if (needToCopyEODRow_ && !needToCopyErrorRow_) |
| { |
| if (copyEODRowIntoSqlBuffer(replyBuffer, parentQueueIndex_)) |
| { |
| needToCopyEODRow_ = FALSE; |
| state_ = RS_FETCH_COMPLETE; |
| } |
| } |
| } |
| |
| // return the number of rows (not including Error & EOD rows) in SqlBuffer. |
| return; |
| |
| } // udrResultSet::fetchRowsFromCLI |
| |
| |
| // copyRowsIntoSqlBuffer() knows how to copy a row from tmpBuffer_ |
| // into SqlBuffer. It allocates a row in SqlBuffer and copies a row |
| // from tmpBuffer_. This method uses the passed in queueIndex value |
| // for all the rows. |
| // If there are warnings for a row, a ComDiagsArea object containing |
| // all the warnings of that row will be inserted into rowDiagsList. |
| void |
| UdrResultSet::copyRowsIntoSqlBuffer(SqlBuffer *replyBuffer, |
| queue_index queueIndex, |
| ComUInt32 &numRowsCopied, |
| NAList<ComDiagsArea*> *rowDiagsList) |
| { |
| // First copy the rows themselves along with any associated |
| // warnings. |
| while (tmpBuffer_->moreRowsToCopy()) |
| { |
| char *rowPtr = NULL; |
| ControlInfo *newControlInfo = NULL; |
| |
| // Allocate a row in SqlBuffer. |
| NABoolean result = allocateReplyRow(getSPInfo()->getUdrGlobals(), |
| *replyBuffer, |
| queueIndex, |
| (Lng32) exeRowSize_, |
| rowPtr, |
| newControlInfo, |
| ex_queue::Q_OK_MMORE); |
| |
| if (result) |
| { |
| ComDiagsArea *rowDiags = NULL; |
| char *srcRowPtr = tmpBuffer_->getNextRow(rowDiags, |
| singleRowFetchEnabled_); |
| fixDataRow(srcRowPtr, rowDiags); |
| |
| memcpy(rowPtr, srcRowPtr, exeRowSize_); |
| numRowsCopied++; |
| |
| // Above getNextRow() call might have generated rowDiags pointer. |
| // Make sure those diags will be sent back to caller |
| if (rowDiags && rowDiags->getNumber(DgSqlCode::WARNING_)) |
| { |
| newControlInfo->setIsExtDiagsAreaPresent(TRUE); |
| rowDiagsList->insert(rowDiags); |
| } |
| } |
| else |
| { |
| // No more space left in the SqlBuffer. That is okay here. |
| // We return from here and let the caller come back |
| // asking for more. |
| return; |
| } |
| } |
| } // UdrResultSet::copyRowsIntoSqlBuffer |
| |
| NABoolean |
| UdrResultSet::copyErrorRowIntoSqlBuffer(SqlBuffer *replyBuffer, |
| queue_index queueIndex, |
| ComDiagsArea &mainDiags) |
| { |
| UdrGlobals *glob = getSPInfo()->getUdrGlobals(); |
| NABoolean errorRowAllocOK = |
| allocateErrorRow(glob, *replyBuffer, queueIndex, TRUE); |
| |
| // Add error condition to main diags |
| if (errorRowAllocOK) |
| mainDiags.mergeAfter(* tmpBuffer_->getDiags()); |
| |
| return errorRowAllocOK; |
| } // UdrResultSet::copyErrorRowIntoSqlBuffer |
| |
| NABoolean |
| UdrResultSet::copyEODRowIntoSqlBuffer(SqlBuffer *replyBuffer, |
| queue_index queueIndex) |
| { |
| UdrGlobals *glob = getSPInfo()->getUdrGlobals(); |
| NABoolean eodRowAllocOK = |
| allocateEODRow(glob, *replyBuffer, parentQueueIndex_); |
| |
| return eodRowAllocOK; |
| } // UdrResultSet::copyEODRowIntoSqlBuffer |
| |
| // fixDataRow() makes any corrections needed for the CLI row |
| // so that the rows can be copied to SqlBuffer easily. |
| char * |
| UdrResultSet::fixDataRow(char *rowPtr, ComDiagsArea * rowDiags) |
| { |
| // Correction for 8402 (String overflow) warning: |
| // For string overflow cases, CLI sets the final string size |
| // in the NULL indicator bytes. Look for |
| // "if (cliDiags && cliDiags->containsWarning(0, EXE_STRING_OVERFLOW))" |
| // in InputOutputExpr::outputValues() for details. |
| // We will correct the row so that the null indicator bytes will |
| // contain either '0's(not null) or '\377's(null). |
| |
| if (rowDiags && rowDiags->containsWarning(0, EXE_STRING_OVERFLOW)) |
| { |
| char *nullIndPtr; |
| for (ComUInt32 colIndex=0; colIndex < numColumns_; colIndex++) |
| { |
| UdrParameterInfo colInfo = rsColumnDesc_[colIndex]; |
| |
| ComUInt16 nullIndLen = colInfo.getNullIndicatorLength(); |
| if (colInfo.isNullable()) |
| { |
| nullIndPtr = rowPtr + colInfo.getNullIndicatorOffset(); |
| |
| for (Int32 i=0; i < nullIndLen; i++) |
| if (! (nullIndPtr[i] & NEG_BIT_MASK)) |
| nullIndPtr[i] = '\0'; |
| } |
| } |
| } |
| |
| return rowPtr; |
| } // UdrResultSet::fixDataRow |
| |
| // UdrResultSet::close() is called during RS_CLOSE message processing. |
| // The UDR Server and LM resources for this UdrResultSet will be freed |
| // and state changes to RS_CLOSED. |
| void |
| UdrResultSet::close(ComDiagsArea *diags) |
| { |
| // release UDR resources |
| deallocateUDRGeneratedFields(); |
| |
| // Cleanup LmResultSet object |
| if (lmResultSet_) |
| getSPInfo()->getLMHandle()->cleanupLmResultSet(lmResultSet_, diags); |
| lmResultSet_ = NULL; |
| |
| setState(RS_CLOSED); |
| } // UdrResultSet::close |
| |
| void UdrResultSet::processRSClose(ComDiagsArea *diags) |
| { |
| if (state_ == RS_FETCH) |
| { |
| // All we do is transition to EARLY_CLOSE and return. Later, a |
| // CONTINUE request will arrive and we will return EOD to the |
| // executor and call the close() method. |
| setState(RS_EARLY_CLOSE); |
| } |
| else |
| { |
| // If we are not currently fetching then call the close() method |
| close(diags); |
| } |
| } |
| |
| |
| // Unloading a UdrResultSet means that the Executor is breaking the link |
| // between RS Proxy TCB and UdrResultSet object. |
| // After unloading a UdrResultSet, Executor has to send RS_LOAD message |
| // at this index so that this UdrResultSet would be useful. |
| void |
| UdrResultSet::unload() |
| { |
| deallocateExeGeneratedFields(); |
| |
| setState(RS_UNLOADED); |
| } // UdrResultSet::unload |