int pgQueryThread::Execute() { wxMutexLocker lock(m_queriesLock); PGresult *result = NULL; wxMBConv &conv = *(m_conn->conv); wxString &query = m_queries[m_currIndex]->m_query; int &resultToRetrieve = m_queries[m_currIndex]->m_resToRetrieve; long &rowsInserted = m_queries[m_currIndex]->m_rowsInserted; Oid &insertedOid = m_queries[m_currIndex]->m_insertedOid; // using the alias for the pointer here, in order to save the result back // in the pgBatchQuery object pgSet *&dataSet = m_queries[m_currIndex]->m_resultSet; int &rc = m_queries[m_currIndex]->m_returnCode; pgParamsArray *params = m_queries[m_currIndex]->m_params; bool useCallable = m_queries[m_currIndex]->m_useCallable; pgError &err = m_queries[m_currIndex]->m_err; wxCharBuffer queryBuf = query.mb_str(conv); if (PQstatus(m_conn->conn) != CONNECTION_OK) { rc = pgQueryResultEvent::PGQ_CONN_LOST; err.msg_primary = _("Connection to the database server lost"); return(RaiseEvent(rc)); } if (!queryBuf && !query.IsEmpty()) { rc = pgQueryResultEvent::PGQ_STRING_INVALID; m_conn->SetLastResultError(NULL, _("the query could not be converted to the required encoding.")); err.msg_primary = _("Query string is empty"); return(RaiseEvent(rc)); } // Honour the parameters (if any) if (params && params->GetCount() > 0) { int pCount = params->GetCount(); int ret = 0, idx = 0; Oid *pOids = (Oid *)malloc(pCount * sizeof(Oid)); const char **pParams = (const char **)malloc(pCount * sizeof(const char *)); int *pLens = (int *)malloc(pCount * sizeof(int)); int *pFormats = (int *)malloc(pCount * sizeof(int)); // modes are used only by enterprisedb callable statement #if defined (__WXMSW__) || (EDB_LIBPQ) int *pModes = (int *)malloc(pCount * sizeof(int)); #endif for (; idx < pCount; idx++) { pgParam *param = (*params)[idx]; pOids[idx] = param->m_type; pParams[idx] = (const char *)param->m_val; pLens[idx] = param->m_len; pFormats[idx] = param->GetFormat(); #if defined (__WXMSW__) || (EDB_LIBPQ) pModes[idx] = param->m_mode; #endif } if (useCallable) { #if defined (__WXMSW__) || (EDB_LIBPQ) wxLogInfo(wxString::Format( _("using an enterprisedb callable statement (queryid:%ld, threadid:%ld)"), (long)m_currIndex, (long)GetId())); wxString stmt = wxString::Format(wxT("pgQueryThread-%ld-%ld"), this->GetId(), m_currIndex); PGresult *res = PQiPrepareOut(m_conn->conn, stmt.mb_str(wxConvUTF8), queryBuf, pCount, pOids, pModes); if( PQresultStatus(res) != PGRES_COMMAND_OK) { rc = pgQueryResultEvent::PGQ_ERROR_PREPARE_CALLABLE; err.SetError(res, &conv); PQclear(res); goto return_with_error; } ret = PQiSendQueryPreparedOut(m_conn->conn, stmt.mb_str(wxConvUTF8), pCount, pParams, pLens, pFormats, 1); if (ret != 1) { rc = pgQueryResultEvent::PGQ_ERROR_EXECUTE_CALLABLE; m_conn->SetLastResultError(NULL, _("Failed to run PQsendQuery in pgQueryThread")); err.msg_primary = wxString(PQerrorMessage(m_conn->conn), conv); PQclear(res); res = NULL; goto return_with_error; } PQclear(res); res = NULL; #else rc = -1; wxASSERT_MSG(false, _("the program execution flow must not reach to this point in pgQueryThread")); goto return_with_error; #endif } else { // assumptions: we will need the results in text format only ret = PQsendQueryParams(m_conn->conn, queryBuf, pCount, pOids, pParams, pLens, pFormats, 0); if (ret != 1) { rc = pgQueryResultEvent::PGQ_ERROR_SEND_QUERY; m_conn->SetLastResultError(NULL, _("Failed to run PQsendQueryParams in pgQueryThread")); err.msg_primary = _("Failed to run PQsendQueryParams in pgQueryThread.\n") + wxString(PQerrorMessage(m_conn->conn), conv); goto return_with_error; } } goto continue_without_error; return_with_error: { free(pOids); free(pParams); free(pLens); free(pFormats); #if defined (__WXMSW__) || (EDB_LIBPQ) free(pModes); #endif return (RaiseEvent(rc)); } } else { // use the PQsendQuery api in case, we don't have any parameters to // pass to the server if (!PQsendQuery(m_conn->conn, queryBuf)) { rc = pgQueryResultEvent::PGQ_ERROR_SEND_QUERY; err.msg_primary = _("Failed to run PQsendQueryParams in pgQueryThread.\n") + wxString(PQerrorMessage(m_conn->conn), conv); return(RaiseEvent(rc)); } } continue_without_error: int resultsRetrieved = 0; PGresult *lastResult = 0; while (true) { // This is a 'joinable' thread, it is not advisable to call 'delete' // function on this. // Hence - it does not make sense to use the function 'testdestroy' here. // We introduced the 'CancelExecution' function for the same purpose. // // Also, do not raise event when the query execution is cancelled to // avoid the bugs introduced to handle events by the event handler, // which is missing or being deleted. // // It will be responsibility of the compononent, using the object of // pgQueryThread, to take the required actions to take care of the // issue. if (m_cancelled) { m_conn->CancelExecution(); rc = pgQueryResultEvent::PGQ_EXECUTION_CANCELLED; err.msg_primary = _("Execution Cancelled"); if (lastResult) { PQclear(lastResult); lastResult = NULL; } AppendMessage(_("Query-thread execution cancelled...\nthe query is:")); AppendMessage(query); return rc; } if ((rc = PQconsumeInput(m_conn->conn)) != 1) { if (rc == 0) { err.msg_primary = wxString(PQerrorMessage(m_conn->conn), conv); } if (PQstatus(m_conn->conn) == CONNECTION_BAD) { err.msg_primary = _("Connection to the database server lost"); rc = pgQueryResultEvent::PGQ_CONN_LOST; } else { rc = pgQueryResultEvent::PGQ_ERROR_CONSUME_INPUT; } return(RaiseEvent(rc)); } if (PQisBusy(m_conn->conn)) { Yield(); this->Sleep(10); continue; } // if resultToRetrieve is given, the nth result will be returned, // otherwise the last result set will be returned. // all others are discarded PGresult *res = PQgetResult(m_conn->conn); if (!res) break; if((PQresultStatus(res) == PGRES_NONFATAL_ERROR) || (PQresultStatus(res) == PGRES_FATAL_ERROR) || (PQresultStatus(res) == PGRES_BAD_RESPONSE)) { result = res; err.SetError(res, &conv); // Wait for the execution to be finished // We need to fetch all the results, before sending the error // message do { if (PQconsumeInput(m_conn->conn) != 1) { if (m_cancelled) { rc = pgQueryResultEvent::PGQ_EXECUTION_CANCELLED; // Release the result as the query execution has been cancelled by the // user if (result) PQclear(result); return rc; } goto out_of_consume_input_loop; } if ((res = PQgetResult(m_conn->conn)) == NULL) { goto out_of_consume_input_loop; } // Release the temporary results PQclear(res); res = NULL; if (PQisBusy(m_conn->conn)) { Yield(); this->Sleep(10); } } while (true); break; } #if defined (__WXMSW__) || (EDB_LIBPQ) // there should be 2 results in the callable statement - the first is the // dummy, the second contains our out params. if (useCallable) { PQclear(res); result = PQiGetOutResult(m_conn->conn); } #endif if (PQresultStatus(res) == PGRES_COPY_IN) { rc = PGRES_COPY_IN; PQputCopyEnd(m_conn->conn, "not supported by pgadmin"); } if (PQresultStatus(res) == PGRES_COPY_OUT) { int copyRc; char *buf; int copyRows = 0; int lastCopyRc = 0; rc = PGRES_COPY_OUT; AppendMessage(_("query returned copy data:\n")); while((copyRc = PQgetCopyData(m_conn->conn, &buf, 1)) >= 0) { if (buf != NULL) { if (copyRows < 100) { wxString str(buf, conv); wxCriticalSectionLocker cs(m_criticalSection); m_queries[m_currIndex]->m_message.Append(str); } else if (copyRows == 100) AppendMessage(_("Query returned more than 100 copy rows, discarding the rest...\n")); PQfreemem(buf); } if (copyRc > 0) copyRows++; if (m_cancelled) { m_conn->CancelExecution(); rc = pgQueryResultEvent::PGQ_EXECUTION_CANCELLED; return -1; } if (lastCopyRc == 0 && copyRc == 0) { Yield(); this->Sleep(10); } if (copyRc == 0) { if (!PQconsumeInput(m_conn->conn)) { if (PQstatus(m_conn->conn) == CONNECTION_BAD) { err.msg_primary = _("Connection to the database server lost"); rc = pgQueryResultEvent::PGQ_CONN_LOST; } else { rc = pgQueryResultEvent::PGQ_ERROR_CONSUME_INPUT; err.msg_primary = wxString(PQerrorMessage(m_conn->conn), conv); } return(RaiseEvent(rc)); } } lastCopyRc = copyRc; } res = PQgetResult(m_conn->conn); if (!res) break; } resultsRetrieved++; if (resultsRetrieved == resultToRetrieve) { result = res; insertedOid = PQoidValue(res); if (insertedOid && insertedOid != (Oid) - 1) AppendMessage(wxString::Format(_("query inserted one row with oid %d.\n"), insertedOid)); else AppendMessage(wxString::Format(wxPLURAL("query result with %d row will be returned.\n", "query result with %d rows will be returned.\n", PQntuples(result)), PQntuples(result))); continue; } if (lastResult) { if (PQntuples(lastResult)) AppendMessage(wxString::Format(wxPLURAL("query result with %d row discarded.\n", "query result with %d rows discarded.\n", PQntuples(lastResult)), PQntuples(lastResult))); PQclear(lastResult); } lastResult = res; } out_of_consume_input_loop: if (!result) result = lastResult; err.SetError(result, &conv); AppendMessage(wxT("\n")); rc = PQresultStatus(result); if (rc == PGRES_TUPLES_OK) { dataSet = new pgSet(result, m_conn, conv, m_conn->needColQuoting); dataSet->MoveFirst(); } else if (rc == PGRES_COMMAND_OK) { char *s = PQcmdTuples(result); if (*s) rowsInserted = atol(s); } else if (rc == PGRES_FATAL_ERROR || rc == PGRES_NONFATAL_ERROR || rc == PGRES_BAD_RESPONSE) { if (result) { AppendMessage(wxString(PQresultErrorMessage(result), conv)); PQclear(result); result = NULL; } else { AppendMessage(wxString(PQerrorMessage(m_conn->conn), conv)); } return(RaiseEvent(rc)); } insertedOid = PQoidValue(result); if (insertedOid == (Oid) - 1) insertedOid = 0; return(RaiseEvent(1)); }
void *dbgPgThread::Entry( void ) { wxLogInfo( wxT( "worker thread waiting for some work to do..." )); // This thread should hang at the call to m_condition.Wait() // When m_condition is signaled, we wake up, send a command // to the PostgreSQL server, and wait for a result. while( m_queueCounter.Wait() == wxSEMA_NO_ERROR && !die && !TestDestroy() ) { m_owner.setNoticeHandler( noticeHandler, this ); m_currentCommand = getNextCommand(); wxString command = m_currentCommand->getCommand(); wxLogInfo( wxT( "Executing: %s" ), command.c_str()); // This call to PQexec() will hang until we've received // a complete result set from the server. PGresult *result = 0; #if defined (__WXMSW__) || (EDB_LIBPQ) // If we have a set of params, and we have the required functions... dbgPgParams *params = m_currentCommand->getParams(); bool use_callable = true; // we do not need all of PQi stuff AS90 onwards if (m_owner.EdbMinimumVersion(9, 0)) use_callable = false; #ifdef EDB_LIBPQ if (params && use_callable) #else if (PQiGetOutResult && PQiPrepareOut && PQiSendQueryPreparedOut && params && use_callable) #endif { wxLogInfo(wxT("Using an EnterpriseDB callable statement")); wxString stmt = wxString::Format(wxT("DebugStmt-%d-%d"), this->GetId(), ++run); PGresult *res = PQiPrepareOut(m_owner.getConnection(), stmt.mb_str(wxConvUTF8), command.mb_str(wxConvUTF8), params->nParams, params->paramTypes, params->paramModes); if( PQresultStatus(res) != PGRES_COMMAND_OK) { wxLogError(_( "Could not prepare the callable statement: %s, error: %s" ), stmt.c_str(), wxString(PQresultErrorMessage(res), *conv).c_str()); PQclear(res); return this; } int ret = PQiSendQueryPreparedOut(m_owner.getConnection(), stmt.mb_str(wxConvUTF8), params->nParams, params->paramValues, NULL, // Can be null - all params are text NULL, // Can be null - all params are text 1); if (ret != 1) { wxLogError(_( "Couldn't execute the callable statement: %s" ), stmt.c_str()); PQclear(res); return this; } // We need to call PQgetResult before we can call PQgetOutResult // Note that this is all async code as far as libpq is concerned to // ensure we can always bail out when required, without leaving threads // hanging around. PGresult *dummy; while(true) { if (die || TestDestroy()) { PQrequestCancel(m_owner.getConnection()); return this; } PQconsumeInput(m_owner.getConnection()); if (PQisBusy(m_owner.getConnection())) { Yield(); wxMilliSleep(10); continue; } dummy = PQgetResult(m_owner.getConnection()); // There should be 2 results - the first is the dummy, the second // contains our out params. if (dummy) break; } if((PQresultStatus(dummy) == PGRES_NONFATAL_ERROR) || (PQresultStatus(dummy) == PGRES_FATAL_ERROR)) result = dummy; else { PQclear(dummy); result = PQiGetOutResult(m_owner.getConnection()); } } else { #endif // This is the normal case for a pl/pgsql function, or if we don't // have access to PQgetOutResult. // Note that this is all async code as far as libpq is concerned to // ensure we can always bail out when required, without leaving threads // hanging around. int ret = PQsendQuery(m_owner.getConnection(), command.mb_str(wxConvUTF8)); if (ret != 1) { wxLogError(_( "Couldn't execute the query (%s): %s" ), command.c_str(), wxString(PQerrorMessage(m_owner.getConnection()), *conv).c_str()); return this; } PGresult *part; while(true) { if (die || TestDestroy()) { PQrequestCancel(m_owner.getConnection()); return this; } PQconsumeInput(m_owner.getConnection()); if (PQisBusy(m_owner.getConnection())) { Yield(); wxMilliSleep(10); continue; } // In theory we should only get one result here, but we'll loop // anyway until we get the last one. part = PQgetResult(m_owner.getConnection()); if (!part) break; result = part; } #if defined (__WXMSW__) || (EDB_LIBPQ) } #endif if(!result) { wxLogInfo(wxT( "NULL PGresult - user abort?" )); return this; } wxLogInfo(wxT( "Complete: %s" ), wxString(PQresStatus(PQresultStatus(result)), *conv).c_str()); // Notify the GUI thread that a result set is ready for display if( m_currentCommand->getEventType() == wxEVT_NULL ) { wxCommandEvent resultEvent( wxEVT_COMMAND_MENU_SELECTED, RESULT_ID_DIRECT_TARGET_COMPLETE ); resultEvent.SetClientData( result ); m_currentCommand->getCaller()->AddPendingEvent( resultEvent ); } else { wxCommandEvent resultEvent( wxEVT_COMMAND_MENU_SELECTED, m_currentCommand->getEventType()); resultEvent.SetClientData( result ); m_currentCommand->getCaller()->AddPendingEvent( resultEvent ); } } return this; }