diff --git a/pgadmin/ctl/ctlSQLResult.cpp b/pgadmin/ctl/ctlSQLResult.cpp index 73e3d67..34df194 100644 --- a/pgadmin/ctl/ctlSQLResult.cpp +++ b/pgadmin/ctl/ctlSQLResult.cpp @@ -141,7 +141,9 @@ int ctlSQLResult::Abort() { ((sqlResultTable *)GetTable())->SetThread(0); - thread->Delete(); + thread->CancelExecution(); + thread->Wait(); + delete thread; } thread = 0; diff --git a/pgadmin/db/pgConn.cpp b/pgadmin/db/pgConn.cpp index 6aebc21..b0d2a9a 100644 --- a/pgadmin/db/pgConn.cpp +++ b/pgadmin/db/pgConn.cpp @@ -52,7 +52,7 @@ static void pgNoticeProcessor(void *arg, const char *message) pgConn::pgConn(const wxString &server, const wxString &service, const wxString &hostaddr, const wxString &database, const wxString &username, const wxString &password, int port, const wxString &rolename, int sslmode, OID oid, const wxString &applicationname, const wxString &sslcert, const wxString &sslkey, const wxString &sslrootcert, const wxString &sslcrl, - const bool sslcompression) + const bool sslcompression) : m_cancelConn(NULL) { wxString msg; @@ -324,7 +324,10 @@ bool pgConn::DoConnect() void pgConn::Close() { if (conn) + { + CancelExecution(); PQfinish(conn); + } conn = 0; connStatus = PGCONN_BAD; } @@ -350,11 +353,26 @@ bool pgConn::Reconnect() } -pgConn *pgConn::Duplicate() +pgConn *pgConn::Duplicate(const wxString &_appName) { - return new pgConn(wxString(save_server), wxString(save_service), wxString(save_hostaddr), wxString(save_database), wxString(save_username), wxString(save_password), - save_port, save_rolename, save_sslmode, save_oid, - save_applicationname, save_sslcert, save_sslkey, save_sslrootcert, save_sslcrl, save_sslcompression); + pgConn *res = new pgConn(wxString(save_server), wxString(save_service), + wxString(save_hostaddr), wxString(save_database), wxString(save_username), + wxString(save_password), save_port, save_rolename, save_sslmode, save_oid, + _appName.IsEmpty() ? save_applicationname : _appName, save_sslcert, save_sslkey, + save_sslrootcert, save_sslcrl, save_sslcompression); + + // Save the version and features information from the existing connection + res->majorVersion = majorVersion; + res->minorVersion = minorVersion; + res->patchVersion = patchVersion; + res->isEdb = isEdb; + res->isGreenplum = isGreenplum; + res->reservedNamespaces = reservedNamespaces; + + for (size_t index = FEATURE_INITIALIZED; index < FEATURE_LAST; index++) + res->features[index] = features[index]; + + return res; } @@ -714,7 +732,11 @@ bool pgConn::ExecuteVoid(const wxString &sql, bool reportError) PGresult *qryRes; wxLogSql(wxT("Void query (%s:%d): %s"), this->GetHost().c_str(), this->GetPort(), sql.c_str()); + + SetConnCancel(); qryRes = PQexec(conn, sql.mb_str(*conv)); + ResetConnCancel(); + lastResultStatus = PQresultStatus(qryRes); SetLastResultError(qryRes); @@ -734,7 +756,7 @@ bool pgConn::ExecuteVoid(const wxString &sql, bool reportError) -wxString pgConn::ExecuteScalar(const wxString &sql) +wxString pgConn::ExecuteScalar(const wxString &sql, bool reportError) { wxString result; @@ -743,14 +765,18 @@ wxString pgConn::ExecuteScalar(const wxString &sql) // Execute the query and get the status. PGresult *qryRes; wxLogSql(wxT("Scalar query (%s:%d): %s"), this->GetHost().c_str(), this->GetPort(), sql.c_str()); + + SetConnCancel(); qryRes = PQexec(conn, sql.mb_str(*conv)); + ResetConnCancel(); + lastResultStatus = PQresultStatus(qryRes); SetLastResultError(qryRes); // Check for errors if (lastResultStatus != PGRES_TUPLES_OK && lastResultStatus != PGRES_COMMAND_OK) { - LogError(); + LogError(!reportError); PQclear(qryRes); return wxEmptyString; } @@ -775,14 +801,17 @@ wxString pgConn::ExecuteScalar(const wxString &sql) return result; } -pgSet *pgConn::ExecuteSet(const wxString &sql) +pgSet *pgConn::ExecuteSet(const wxString &sql, bool reportError) { // Execute the query and get the status. if (GetStatus() == PGCONN_OK) { PGresult *qryRes; wxLogSql(wxT("Set query (%s:%d): %s"), this->GetHost().c_str(), this->GetPort(), sql.c_str()); + + SetConnCancel(); qryRes = PQexec(conn, sql.mb_str(*conv)); + ResetConnCancel(); lastResultStatus = PQresultStatus(qryRes); SetLastResultError(qryRes); @@ -792,14 +821,17 @@ pgSet *pgConn::ExecuteSet(const wxString &sql) pgSet *set = new pgSet(qryRes, this, *conv, needColQuoting); if (!set) { - wxLogError(__("Couldn't create a pgSet object!")); + if (reportError) + wxLogError(_("Couldn't create a pgSet object!")); + else + wxLogQuietError(_("Couldn't create a pgSet object!")); PQclear(qryRes); } return set; } else { - LogError(); + LogError(!reportError); PQclear(qryRes); } } @@ -924,7 +956,15 @@ void pgConn::LogError(const bool quiet) bool pgConn::IsAlive() { if (GetStatus() != PGCONN_OK) + { + if (conn) + { + PQfinish(conn); + conn = 0; + connStatus = PGCONN_BROKEN; + } return false; + } PGresult *qryRes = PQexec(conn, "SELECT 1;"); lastResultStatus = PQresultStatus(qryRes); @@ -968,6 +1008,59 @@ void pgConn::Reset() } +void pgConn::SetConnCancel(void) +{ + wxMutexLocker lock(m_cancelConnMutex); + PGcancel *oldCancelConn = m_cancelConn; + + m_cancelConn = NULL; + + if (oldCancelConn != NULL) + PQfreeCancel(oldCancelConn); + + if (!conn) + return; + + m_cancelConn = PQgetCancel(conn); + +} + + +void pgConn::ResetConnCancel(void) +{ + wxMutexLocker lock(m_cancelConnMutex); + PGcancel *oldCancelConn = m_cancelConn; + + m_cancelConn = NULL; + + if (oldCancelConn != NULL) + PQfreeCancel(oldCancelConn); +} + + +void pgConn::CancelExecution(void) +{ + char errbuf[256]; + wxMutexLocker lock(m_cancelConnMutex); + + if (m_cancelConn) + { + PGcancel *cancelConn = m_cancelConn; + m_cancelConn = NULL; + + if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) + { + SetLastResultError(NULL, wxT("Cancel request sent")); + } + else + { + SetLastResultError(NULL, wxString::Format(wxT("Could not send cancel request:\n%s"), errbuf)); + } + PQfreeCancel(cancelConn); + } +} + + wxString pgConn::GetVersionString() { return ExecuteScalar(wxT("SELECT version();")); @@ -1101,14 +1194,14 @@ bool pgConn::TableHasColumn(wxString schemaname, wxString tblname, const wxStrin schemaname.Replace(wxT("'"), wxT("''")); wxString sql - = wxT("SELECT a.attname AS colname FROM pg_catalog.pg_attribute a ") \ - wxT("WHERE a.attrelid = (SELECT c.oid FROM pg_catalog.pg_class c ") \ - wxT(" LEFT JOIN pg_catalog.pg_namespace n ON ") \ - wxT(" n.oid = c.relnamespace ") \ - wxT(" WHERE c.relname ~ '^(") + tblname + wxT(")$' AND ") \ - wxT(" n.nspname ~ '^(") + schemaname + wxT(")$') AND ") \ - wxT(" a.attnum > 0 AND NOT a.attisdropped ") \ - wxT("ORDER BY a.attnum"); + = wxT("SELECT a.attname AS colname FROM pg_catalog.pg_attribute a ") \ + wxT("WHERE a.attrelid = (SELECT c.oid FROM pg_catalog.pg_class c ") \ + wxT(" LEFT JOIN pg_catalog.pg_namespace n ON ") \ + wxT(" n.oid = c.relnamespace ") \ + wxT(" WHERE c.relname ~ '^(") + tblname + wxT(")$' AND ") \ + wxT(" n.nspname ~ '^(") + schemaname + wxT(")$') AND ") \ + wxT(" a.attnum > 0 AND NOT a.attisdropped ") \ + wxT("ORDER BY a.attnum"); pgSet *set = ExecuteSet(sql); if (set) @@ -1129,3 +1222,88 @@ bool pgConn::TableHasColumn(wxString schemaname, wxString tblname, const wxStrin return false; } +void pgError::SetError(PGresult *_res, wxMBConv *_conv) +{ + if (!_conv) + { + _conv = &wxConvLibc; + } + if (_res) + { + msg_primary = wxString(PQresultErrorField(_res, PG_DIAG_MESSAGE_PRIMARY), *_conv); + severity = wxString(PQresultErrorField(_res, PG_DIAG_SEVERITY), *_conv); + sql_state = wxString(PQresultErrorField(_res, PG_DIAG_SQLSTATE), *_conv); + msg_detail = wxString(PQresultErrorField(_res, PG_DIAG_MESSAGE_DETAIL), *_conv); + msg_hint = wxString(PQresultErrorField(_res, PG_DIAG_MESSAGE_HINT), *_conv); + statement_pos = wxString(PQresultErrorField(_res, PG_DIAG_STATEMENT_POSITION), *_conv); + internal_pos = wxString(PQresultErrorField(_res, PG_DIAG_INTERNAL_POSITION), *_conv); + internal_query = wxString(PQresultErrorField(_res, PG_DIAG_INTERNAL_QUERY), *_conv); + context = wxString(PQresultErrorField(_res, PG_DIAG_CONTEXT), *_conv); + source_file = wxString(PQresultErrorField(_res, PG_DIAG_SOURCE_FILE), *_conv); + source_line = wxString(PQresultErrorField(_res, PG_DIAG_SOURCE_LINE), *_conv); + source_function = wxString(PQresultErrorField(_res, PG_DIAG_SOURCE_FUNCTION), *_conv); + } + else + { + msg_primary = wxEmptyString; + severity = wxEmptyString; + sql_state = wxEmptyString; + msg_detail = wxEmptyString; + msg_hint = wxEmptyString; + statement_pos = wxEmptyString; + internal_pos = wxEmptyString; + internal_query = wxEmptyString; + context = wxEmptyString; + source_file = wxEmptyString; + source_line = wxEmptyString; + source_function = wxEmptyString; + } + + wxString errMsg; + + if (severity != wxEmptyString && msg_primary != wxEmptyString) + errMsg = severity + wxT(": ") + msg_primary; + else if (msg_primary != wxEmptyString) + errMsg = msg_primary; + + if (!sql_state.IsEmpty()) + { + if (!errMsg.EndsWith(wxT("\n"))) + errMsg += wxT("\n"); + errMsg += _("SQL state: "); + errMsg += sql_state; + } + + if (!msg_detail.IsEmpty()) + { + if (!errMsg.EndsWith(wxT("\n"))) + errMsg += wxT("\n"); + errMsg += _("Detail: "); + errMsg += msg_detail; + } + + if (!msg_hint.IsEmpty()) + { + if (!errMsg.EndsWith(wxT("\n"))) + errMsg += wxT("\n"); + errMsg += _("Hint: "); + errMsg += msg_hint; + } + + if (!statement_pos.IsEmpty()) + { + if (!errMsg.EndsWith(wxT("\n"))) + errMsg += wxT("\n"); + errMsg += _("Character: "); + errMsg += statement_pos; + } + + if (!context.IsEmpty()) + { + if (!errMsg.EndsWith(wxT("\n"))) + errMsg += wxT("\n"); + errMsg += _("Context: "); + errMsg += context; + } + formatted_msg = errMsg; +} diff --git a/pgadmin/db/pgQueryThread.cpp b/pgadmin/db/pgQueryThread.cpp index 77b361c..98c4017 100644 --- a/pgadmin/db/pgQueryThread.cpp +++ b/pgadmin/db/pgQueryThread.cpp @@ -21,176 +21,512 @@ #include "db/pgSet.h" #include "db/pgConn.h" #include "db/pgQueryThread.h" +#include "db/pgQueryResultEvent.h" +#include "utils/pgDefs.h" #include "utils/sysLogger.h" -static void pgNoticeProcessor(void *arg, const char *message) +const wxEventType PGQueryResultEvent = wxNewEventType(); + +// default notice processor for the pgQueryThread +// we do assume that the argument passed will be always the +// object of pgQueryThread +void pgNoticeProcessor(void *_arg, const char *_message) { - wxString str(message, wxConvUTF8); + wxString str(_message, wxConvUTF8); wxLogNotice(wxT("%s"), str.Trim().c_str()); - ((pgQueryThread *)arg)->appendMessage(str); + if (_arg) + { + ((pgQueryThread *)_arg)->AppendMessage(str); + } } -pgQueryThread::pgQueryThread(pgConn *_conn, const wxString &qry, int _resultToRetrieve, wxWindow *_caller, long _eventId, void *_data) - : wxThread(wxTHREAD_JOINABLE) + +// support for multiple queries support +pgQueryThread::pgQueryThread(pgConn *_conn, wxEvtHandler *_caller, + PQnoticeProcessor _processor, void *_noticeHandler) : + wxThread(wxTHREAD_JOINABLE), m_currIndex(-1), m_conn(_conn), + m_cancelled(false), m_multiQueries(true), m_useCallable(false), + m_caller(_caller), m_processor(pgNoticeProcessor), m_noticeHandler(NULL) { - query = qry; - conn = _conn; - dataSet = 0; - result = 0; - resultToRetrieve = _resultToRetrieve; - rc = -1; - insertedOid = (OID) - 1; - caller = _caller; - eventId = _eventId; - data = _data; - - wxLogSql(wxT("Thread query (%s:%d): %s"), conn->GetHost().c_str(), conn->GetPort(), qry.c_str()); - - conn->RegisterNoticeProcessor(pgNoticeProcessor, this); - if (conn->conn) - PQsetnonblocking(conn->conn, 1); + // check if we can really use the enterprisedb callable statement and + // required +#ifdef __WXMSW__ + if (PQiGetOutResult && PQiPrepareOut && PQiSendQueryPreparedOut) + { + // we do not need all of pqi stuff as90 onwards + if (m_conn && m_conn->conn && m_conn->GetIsEdb() + && !m_conn->EdbMinimumVersion(9, 0)) + { + m_useCallable = true; + } + } +#else // __WXMSW__ +#ifdef EDB_LIBPQ + // use callable statement only with enterprisedb advanced servers + // we do not need all of pqi stuff as90 onwards + if (m_conn && m_conn->conn && m_conn->GetIsEdb() + && !m_conn->EdbMinimumVersion(9, 0)) + { + m_useCallable = true; + } +#endif // EDB_LIBPQ +#endif // __WXMSW__ + + if (m_conn && m_conn->conn) + { + PQsetnonblocking(m_conn->conn, 1); + } + + if (_processor != NULL) + { + m_processor = _processor; + if (_noticeHandler) + m_noticeHandler = _noticeHandler; + else + m_noticeHandler = (void *)this; + } + else + { + m_noticeHandler = (void *)this; + } } +pgQueryThread::pgQueryThread(pgConn *_conn, const wxString &_qry, + int _resultToRetrieve, wxWindow *_caller, long _eventId, void *_data) + : wxThread(wxTHREAD_JOINABLE), m_currIndex(-1), m_conn(_conn), + m_cancelled(false), m_multiQueries(false), m_useCallable(false), + m_caller(NULL), m_processor(pgNoticeProcessor), m_noticeHandler(NULL) +{ + if (m_conn && m_conn->conn) + { + PQsetnonblocking(m_conn->conn, 1); + } + m_queries.Add( + new pgBatchQuery(_qry, (pgParamsArray *)NULL, _eventId, _data, false, + _resultToRetrieve)); + + wxLogInfo(wxT("queueing : %s"), _qry.c_str()); + m_noticeHandler = (void *)this; + + if (_caller) + { + m_caller = _caller->GetEventHandler(); + } +} + + +void pgQueryThread::AddQuery(const wxString &_qry, pgParamsArray *_params, + long _eventId, void *_data, bool _useCallable, int _resultToRetrieve) +{ + m_queries.Add( + new pgBatchQuery(_qry, _params, _eventId, _data, + // use callable statement only if supported + m_useCallable && _useCallable, _resultToRetrieve)); + + wxLogInfo(wxT("queueing (%ld): %s"), GetId(), _qry.c_str()); +} + + pgQueryThread::~pgQueryThread() { - conn->RegisterNoticeProcessor(0, 0); - if (dataSet) - delete dataSet; + m_conn->RegisterNoticeProcessor(0, 0); + WX_CLEAR_ARRAY(m_queries); } -wxString pgQueryThread::GetMessagesAndClear() +wxString pgQueryThread::GetMessagesAndClear(int idx) { wxString msg; + if (idx == -1) + idx = m_currIndex; + + if (idx >= 0 && idx <= m_currIndex) { - wxCriticalSectionLocker cs(criticalSection); - msg = messages; - messages.Empty(); + wxCriticalSectionLocker lock(m_criticalSection); + msg = m_queries[idx]->m_message; + m_queries[idx]->m_message.empty(); } return msg; } -void pgQueryThread::appendMessage(const wxString &str) +void pgQueryThread::AppendMessage(const wxString &str) { - if (messages.IsEmpty()) + if (m_queries[m_currIndex]->m_message.IsEmpty()) { + wxCriticalSectionLocker lock(m_criticalSection); if (str != wxT("\n")) - appendMessageRaw(str); + m_queries[m_currIndex]->m_message.Append(str); } else - appendMessageRaw(wxT("\n") + str); -} - -void pgQueryThread::appendMessageRaw(const wxString &str) -{ - wxCriticalSectionLocker cs(criticalSection); - messages.Append(str); + { + wxCriticalSectionLocker lock(m_criticalSection); + m_queries[m_currIndex]->m_message.Append(wxT("\n")).Append(str); + } } -int pgQueryThread::execute() +int pgQueryThread::Execute() { - rowsInserted = -1L; + 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"); - if (!conn->conn) - return(raiseEvent(0)); + return(RaiseEvent(rc)); + } - wxCharBuffer queryBuf = query.mb_str(*conn->conv); if (!queryBuf && !query.IsEmpty()) { - conn->SetLastResultError(NULL, _("The query could not be converted to the required encoding.")); - return(raiseEvent(0)); + 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)); } - if (!PQsendQuery(conn->conn, queryBuf)) + // Honour the parameters (if any) + if (params && params->GetCount() > 0) { - conn->SetLastResultError(NULL); - conn->IsAlive(); - return(raiseEvent(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) { - if (TestDestroy()) + // 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) { - if (rc != -3) + m_conn->CancelExecution(); + rc = pgQueryResultEvent::PGQ_EXECUTION_CANCELLED; + + err.msg_primary = _("Execution Cancelled"); + + if (lastResult) { - if (!PQrequestCancel(conn->conn)) // could not abort; abort failed. - return(raiseEvent(-1)); + PQclear(lastResult); + lastResult = NULL; + } + AppendMessage(_("Query-thread execution cancelled...\nthe query is:")); + AppendMessage(query); + + return rc; + } - rc = -3; + 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 (!PQconsumeInput(conn->conn)) - return(raiseEvent(0)); - if (PQisBusy(conn->conn)) + + if (PQisBusy(m_conn->conn)) { Yield(); this->Sleep(10); + continue; } - // If resultToRetrieve is given, the nth result will be returned, + // 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(conn->conn); + 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) { - PQputCopyEnd(conn->conn, "not supported by pgAdmin"); + rc = PGRES_COPY_IN; + PQputCopyEnd(m_conn->conn, "not supported by pgadmin"); } + if (PQresultStatus(res) == PGRES_COPY_OUT) { - int copyrc; + int copyRc; char *buf; - int copyrows = 0; - int lastcopyrc = 0; + int copyRows = 0; + int lastCopyRc = 0; - appendMessage(_("Query returned COPY data:\n")); + rc = PGRES_COPY_OUT; - while((copyrc = PQgetCopyData(conn->conn, &buf, 1)) >= 0) + AppendMessage(_("query returned copy data:\n")); + + while((copyRc = PQgetCopyData(m_conn->conn, &buf, 1)) >= 0) { if (buf != NULL) { - if (copyrows < 100) + if (copyRows < 100) { - wxString str(buf, wxConvUTF8); - appendMessageRaw(str); + 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")); + else if (copyRows == 100) + AppendMessage(_("Query returned more than 100 copy rows, discarding the rest...\n")); PQfreemem(buf); } - if (copyrc > 0) - copyrows++; - if (TestDestroy() && rc != -3) + if (copyRc > 0) + copyRows++; + + if (m_cancelled) { - if (!PQrequestCancel(conn->conn)) // could not abort; abort failed. - return(raiseEvent(-1)); - rc = -3; + m_conn->CancelExecution(); + rc = pgQueryResultEvent::PGQ_EXECUTION_CANCELLED; + + return -1; } - if (lastcopyrc == 0 && copyrc == 0) + if (lastCopyRc == 0 && copyRc == 0) { Yield(); this->Sleep(10); } - if (copyrc == 0) + if (copyRc == 0) { - if (!PQconsumeInput(conn->conn)) - return(raiseEvent(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; + lastCopyRc = copyRc; } - res = PQgetResult(conn->conn); + + res = PQgetResult(m_conn->conn); + if (!res) break; } @@ -200,37 +536,36 @@ int pgQueryThread::execute() { result = res; insertedOid = PQoidValue(res); - if (insertedOid && insertedOid != (OID) - 1) - appendMessage(wxString::Format(_("Query inserted one row with OID %d.\n"), insertedOid)); + 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", + 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", + 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; - conn->SetLastResultError(result); + err.SetError(result, &conv); - appendMessage(wxT("\n")); - rc = PQresultStatus(result); - insertedOid = PQoidValue(result); - if (insertedOid == (OID) - 1) - insertedOid = 0; + AppendMessage(wxT("\n")); + rc = PQresultStatus(result); if (rc == PGRES_TUPLES_OK) { - dataSet = new pgSet(result, conn, *conn->conv, conn->needColQuoting); + dataSet = new pgSet(result, m_conn, conv, m_conn->needColQuoting); dataSet->MoveFirst(); } else if (rc == PGRES_COMMAND_OK) @@ -239,37 +574,238 @@ int pgQueryThread::execute() if (*s) rowsInserted = atol(s); } - else if (rc == PGRES_FATAL_ERROR) + else if (rc == PGRES_FATAL_ERROR || + rc == PGRES_NONFATAL_ERROR || + rc == PGRES_BAD_RESPONSE) { - appendMessage(conn->GetLastError() + wxT("\n")); + if (result) + { + AppendMessage(wxString(PQresultErrorMessage(result), conv)); + PQclear(result); + result = NULL; + } + else + { + AppendMessage(wxString(PQerrorMessage(m_conn->conn), conv)); + } + + return(RaiseEvent(rc)); } - return(raiseEvent(1)); -} + insertedOid = PQoidValue(result); + if (insertedOid == (Oid) - 1) + insertedOid = 0; + + return(RaiseEvent(1)); +} -int pgQueryThread::raiseEvent(int retval) +int pgQueryThread::RaiseEvent(int _retval) { - if (caller) - { #if !defined(PGSCLI) - wxCommandEvent resultEvent(wxEVT_COMMAND_MENU_SELECTED, eventId); - resultEvent.SetClientData(data); -#if wxCHECK_VERSION(2, 9, 0) - caller->GetEventHandler()->AddPendingEvent(resultEvent); -#else - caller->AddPendingEvent(resultEvent); -#endif + if (m_caller) + { + pgQueryResultEvent resultEvent(GetId(), m_queries[m_currIndex], m_queries[m_currIndex]->m_eventID); -#endif + // client data + resultEvent.SetClientData(m_queries[m_currIndex]->m_data); + resultEvent.SetInt(_retval); + + m_caller->AddPendingEvent(resultEvent); } - return retval; +#endif + return _retval; } void *pgQueryThread::Entry() { - rc = -2; - execute(); + do + { + if (m_currIndex < (((int)m_queries.GetCount()) - 1)) + { + // Create the PGcancel object to enable cancelling the running + // query + m_conn->SetConnCancel(); + m_currIndex++; + + m_queries[m_currIndex]->m_returnCode = -2; + m_queries[m_currIndex]->m_rowsInserted = -1l; + + wxLogSql(wxT("Thread executing query (%d:%s:%d): %s"), + m_currIndex + 1, m_conn->GetHost().c_str(), m_conn->GetPort(), + m_queries[m_currIndex]->m_query.c_str()); + + // register the notice processor for the current query + m_conn->RegisterNoticeProcessor(m_processor, m_noticeHandler); + + // execute the current query now + Execute(); + + // remove the notice processor now + m_conn->RegisterNoticeProcessor(0, 0); + + // reset the PGcancel object + m_conn->ResetConnCancel(); + } + + if (!m_multiQueries || m_cancelled) + break; + + wxThread::Sleep(20); + } + while (true); return(NULL); } + +int pgQueryThread::DeleteReleasedQueries() +{ + int res = 0, + idx = 0; + + if (m_queriesLock.TryLock() == wxMUTEX_BUSY) + return res; + + for (; idx <= m_currIndex; idx++) + { + if (m_queries[idx]->m_resultSet != NULL) + { + pgSet *set = m_queries[idx]->m_resultSet; + m_queries[idx]->m_resultSet = NULL; + delete set; + set = NULL; + + res++; + } + } + m_queriesLock.Unlock(); + + return res; +} + + +const wxString &pgBatchQuery::GetErrorMessage() +{ + return m_err.msg_primary; +} + +pgBatchQuery::~pgBatchQuery() +{ + if (m_resultSet) + { + delete m_resultSet; + m_resultSet = NULL; + } + + if (m_params) + { + WX_CLEAR_ARRAY((*m_params)); + delete m_params; + m_params = NULL; + } +} + +bool pgBatchQuery::Release() +{ + bool res = false; + + if (m_resultSet != NULL) + { + res = true; + + pgSet *set = m_resultSet; + m_resultSet = NULL; + + if (set) + delete set; + set = NULL; + } + + if (m_params) + { + res = true; + + WX_CLEAR_ARRAY((*m_params)); + delete m_params; + m_params = NULL; + } + + return res; +} + +pgQueryResultEvent::pgQueryResultEvent( + unsigned long _thrdId, pgBatchQuery *_qry, int _id) : + wxCommandEvent(PGQueryResultEvent, _id), m_thrdId(_thrdId), + m_query(_qry) { } + +pgQueryResultEvent::pgQueryResultEvent(const pgQueryResultEvent &_ev) + : wxCommandEvent(_ev), m_thrdId(_ev.m_thrdId), m_query(_ev.m_query) { } + + +pgParam::pgParam(Oid _type, void *_val, int _len, short _mode) + : m_type(_type), m_val(_val), m_len(_len), m_mode(_mode) +{ + switch(m_type) + { + case PGOID_TYPE_CHAR: + case PGOID_TYPE_NAME: + case PGOID_TYPE_TEXT: + case PGOID_TYPE_VARCHAR: + case PGOID_TYPE_CSTRING: + m_format = 0; + default: + m_format = 1; + } +} + +// wxString data +pgParam::pgParam(Oid _oid, wxString *_val, wxMBConv *_conv, short _mode) + : m_mode(_mode) +{ + if (m_mode == PG_PARAM_OUT || !_val) + { + m_len = 0; + } + else + { + m_len = _val->Len(); + } + if (_val) + { + char *str = (char *)malloc(m_len + 1); + if (!_val->IsEmpty() && _mode != PG_PARAM_OUT) + { + strncpy(str, + (const char *)_val->mb_str( + *(_conv != NULL ? _conv : &wxConvLocal)), m_len); + str[m_len] = '\0'; + } + else + { + str[0] = '\0'; + } + m_val = (void *)(str); + } + else + { + m_val = NULL; + } + m_type = _oid; + + // text format + m_format = 0; +} + + +pgParam::~pgParam() +{ + if (m_val) + free(m_val); + m_val = NULL; +} + + +int pgParam::GetFormat() +{ + return m_format; +} diff --git a/pgadmin/db/pgSet.cpp b/pgadmin/db/pgSet.cpp index adfb1e9..3ee195a 100644 --- a/pgadmin/db/pgSet.cpp +++ b/pgadmin/db/pgSet.cpp @@ -20,7 +20,6 @@ // App headers #include "db/pgSet.h" #include "db/pgConn.h" -#include "db/pgQueryThread.h" #include "utils/sysLogger.h" #include "utils/pgDefs.h" diff --git a/pgadmin/debugger/ctlCodeWindow.cpp b/pgadmin/debugger/ctlCodeWindow.cpp deleted file mode 100644 index a93c177..0000000 --- a/pgadmin/debugger/ctlCodeWindow.cpp +++ /dev/null @@ -1,1563 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// ctlCodeWindow.cpp - debugger -// -////////////////////////////////////////////////////////////////////////// - -#include "pgAdmin3.h" - -// wxWindows headers -#include -#include -#include -#include - -// App headers -#include "debugger/ctlCodeWindow.h" -#include "debugger/ctlVarWindow.h" -#include "debugger/frmDebugger.h" -#include "debugger/dlgDirectDbg.h" -#include "debugger/dbgConst.h" -#include "debugger/dbgPgConn.h" -#include "debugger/dbgResultset.h" -#include "debugger/dbgBreakPoint.h" -#include "ctl/ctlMenuToolbar.h" - -//////////////////////////////////////////////////////////////////////////////// -// NOTES: -// -// 1) In this class, we update the user interface in a lazy fashion. Instead of -// updating the variable list, call stack, and breakpoint markers every time -// the target pauses, we wait for an idle period and then query the proxy for -// the variable list, call stack, and breakpoint list. That way, the debugger -// doesn't bog down when you hit the 'step over' or 'step into' key repeatedly. -// -// Lazy updating is a multi-step operation. When the target pauses (which -// means that a call to pldbg_continue(), pldbg_step_over(), or pldbg_step_into() -// has just returned), we update the source code window and then set a flag -// (m_updateVars) that tells us to update the variable window during the next -// idle period. As soon as OnIdle() is called, we update the variable window, -// turn off the m_updateVars flag, and turn on the next flag (m_updateStack) -// to force the stack window to update itself during the next idle period. -// After the stack window updates itself, it turns on m_updateBreakpoints() to -// force the breakpoint markers to refresh. -// -// 2) This class will issue a number of different queries to the proxy process. -// Each query executes (one at a time) in a separate thread so that the user -// interface remains responsive. When we queue up a query, we give an event -// ID to the queue - when the query completes, the dbgPgThread objects sends us -// an event that includes the event ID that we provided. -// -// All of the result-set related event names start with 'RESULT_ID_' and each -// event is handled by an event handler function whose name starts with 'Result'. -// For example, when we queue a query to retrieve the source code for a function, -// we tell dbgPgThread to send us a RESULT_ID_GET_SOURCE event when the query -// completes and we handle that event in a function named ResultSource. - -IMPLEMENT_CLASS(ctlCodeWindow, pgFrame) - -BEGIN_EVENT_TABLE(ctlCodeWindow , pgFrame) - EVT_MENU(MENU_ID_TOGGLE_BREAK, ctlCodeWindow::OnCommand) - EVT_MENU(MENU_ID_CLEAR_ALL_BREAK, ctlCodeWindow::OnCommand) - - EVT_MENU(MENU_ID_CONTINUE, ctlCodeWindow::OnCommand) - EVT_MENU(MENU_ID_STEP_OVER, ctlCodeWindow::OnCommand) - EVT_MENU(MENU_ID_STEP_INTO, ctlCodeWindow::OnCommand) - EVT_MENU(MENU_ID_STOP, ctlCodeWindow::OnCommand) - - EVT_IDLE(ctlCodeWindow::OnIdle) - - EVT_BUTTON(MENU_ID_NOTICE_RECEIVED, ctlCodeWindow::OnNoticeReceived) - - EVT_LISTBOX(wxID_ANY, ctlCodeWindow::OnSelectFrame) - EVT_GRID_CELL_CHANGE( ctlCodeWindow::OnVarChange) - EVT_STC_MARGINCLICK(wxID_ANY, ctlCodeWindow::OnMarginClick) - EVT_STC_UPDATEUI(wxID_ANY, ctlCodeWindow::OnPositionStc) - - EVT_MENU(RESULT_ID_ATTACH_TO_PORT, ctlCodeWindow::ResultPortAttach) - EVT_MENU(RESULT_ID_BREAKPOINT, ctlCodeWindow::ResultBreakpoint) - EVT_MENU(RESULT_ID_GET_VARS, ctlCodeWindow::ResultVarList) - EVT_MENU(RESULT_ID_GET_STACK, ctlCodeWindow::ResultStack) - EVT_MENU(RESULT_ID_GET_BREAKPOINTS, ctlCodeWindow::ResultBreakpoints) - EVT_MENU(RESULT_ID_GET_SOURCE, ctlCodeWindow::ResultSource) - EVT_MENU(RESULT_ID_NEW_BREAKPOINT, ctlCodeWindow::ResultNewBreakpoint) - EVT_MENU(RESULT_ID_NEW_BREAKPOINT_WAIT, ctlCodeWindow::ResultNewBreakpointWait) - EVT_MENU(RESULT_ID_DEL_BREAKPOINT, ctlCodeWindow::ResultDeletedBreakpoint) - EVT_MENU(RESULT_ID_DEPOSIT_VALUE, ctlCodeWindow::ResultDepositValue) - EVT_MENU(RESULT_ID_ABORT_TARGET, ctlCodeWindow::ResultAbortTarget) - EVT_MENU(RESULT_ID_ADD_BREAKPOINT, ctlCodeWindow::ResultAddBreakpoint) - EVT_MENU(RESULT_ID_LAST_BREAKPOINT, ctlCodeWindow::ResultLastBreakpoint) - EVT_MENU(RESULT_ID_LISTENER_CREATED, ctlCodeWindow::ResultListenerCreated) - EVT_MENU(RESULT_ID_TARGET_READY, ctlCodeWindow::ResultTargetReady) - - EVT_TIMER(wxID_ANY, ctlCodeWindow::OnTimer) -END_EVENT_TABLE() - -//////////////////////////////////////////////////////////////////////////////// -// Static data members -//////////////////////////////////////////////////////////////////////////////// -wxString ctlCodeWindow::m_commandAttach(wxT("SELECT * FROM pldbg_attach_to_port(%s)")); -wxString ctlCodeWindow::m_commandWaitForBreakpoint( wxT( "SELECT * FROM pldbg_wait_for_breakpoint(%s)")); -wxString ctlCodeWindow::m_commandGetVars(wxT("SELECT name, varClass, value, pg_catalog.format_type( dtype, NULL ) as dtype, isconst FROM pldbg_get_variables(%s)")); -wxString ctlCodeWindow::m_commandGetStack(wxT("SELECT targetName, args, linenumber FROM pldbg_get_stack(%s) ORDER BY level")); -wxString ctlCodeWindow::m_commandGetBreakpoints(wxT("SELECT * FROM pldbg_get_breakpoints(%s)")); -wxString ctlCodeWindow::m_commandGetSourceV1(wxT("SELECT %s AS pkg, %s AS func, pldbg_get_source(%s,%s,%s) AS source, targetName, args FROM pldbg_get_stack(%s) ORDER BY level LIMIT 1")); -wxString ctlCodeWindow::m_commandGetSourceV2(wxT("SELECT %s AS func, pldbg_get_source(%s,%s) AS source, targetName, args FROM pldbg_get_stack(%s) ORDER BY level LIMIT 1")); -wxString ctlCodeWindow::m_commandStepOver(wxT("SELECT * FROM pldbg_step_over(%s)")); -wxString ctlCodeWindow::m_commandStepInto(wxT("SELECT * FROM pldbg_step_into(%s)")); -wxString ctlCodeWindow::m_commandContinue(wxT("SELECT * FROM pldbg_continue(%s)")); -wxString ctlCodeWindow::m_commandSetBreakpointV1(wxT("SELECT * FROM pldbg_set_breakpoint(%s,%s,%s,%d)")); -wxString ctlCodeWindow::m_commandSetBreakpointV2(wxT("SELECT * FROM pldbg_set_breakpoint(%s,%s,%d)")); -wxString ctlCodeWindow::m_commandClearBreakpointV1(wxT("SELECT * FROM pldbg_drop_breakpoint(%s,%s,%s,%d)")); -wxString ctlCodeWindow::m_commandClearBreakpointV2(wxT("SELECT * FROM pldbg_drop_breakpoint(%s,%s,%d)")); -wxString ctlCodeWindow::m_commandSelectFrame(wxT("SELECT * FROM pldbg_select_frame(%s,%d)")); -wxString ctlCodeWindow::m_commandDepositValue(wxT("SELECT * FROM pldbg_deposit_value(%s,%s,%d,%s)")); -wxString ctlCodeWindow::m_commandAbortTarget(wxT("SELECT * FROM pldbg_abort_target(%s)")); -wxString ctlCodeWindow::m_commandAddBreakpointEDB(wxT("SELECT * FROM pldbg_set_global_breakpoint(%s, %s, %s, %s, %s)")); -wxString ctlCodeWindow::m_commandAddBreakpointPG(wxT("SELECT * FROM pldbg_set_global_breakpoint(%s, %s, %s, %s)")); -wxString ctlCodeWindow::m_commandGetTargetInfo(wxT("SELECT *, %s as pid FROM pldbg_get_target_info('%s', '%c')")); -wxString ctlCodeWindow::m_commandCreateListener(wxT("SELECT * from pldbg_create_listener()")); -wxString ctlCodeWindow::m_commandWaitForTarget(wxT("SELECT * FROM pldbg_wait_for_target(%s)")); - -//////////////////////////////////////////////////////////////////////////////// -// ctlCodeWindow constructor -// -// This class implements the debugger window. The constructor expects a string -// that contains a TCP port number - the constructor connects to the debugger -// server waiting at that port. -// -// A ctlCodeWindow object creates (and manages) a toolbar and handles toolbar -// and keystroke messages. The input messages are treated as debugger commands. -// -// The m_view member is a ctlSQLBox that displays the code for the PL -// function that you're debugging. The m_currentLineNumber member tracks the current -// line (that is, the line about to execute). We use hilight the current line. -// If m_currentLineNumber is -1, there is no current line. - -ctlCodeWindow::ctlCodeWindow( frmDebugger *parent, wxWindowID id, const dbgConnProp &connProps ) - : pgFrame(NULL, wxEmptyString), - m_debugPort(connProps.m_debugPort), - m_parent(parent), - m_currentLineNumber(-1), - m_updateVars(false), - m_updateStack(false), - m_updateBreakpoints(false), - m_progressBar(NULL), - m_timer(this) -{ - m_targetComplete = false; - m_targetAborted = false; - - clearSourceCache(); - - wxWindowBase::SetFont(settings->GetSystemFont()); - - m_stackWindow = new ctlStackWindow(parent , WINDOW_ID_STACK, wxDefaultPosition, wxDefaultSize, 0); - m_tabWindow = new ctlTabWindow(parent, WINDOW_ID_TABS, wxDefaultPosition, wxDefaultSize, wxAUI_NB_BOTTOM | wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_WINDOWLIST_BUTTON); - m_view = new ctlSQLBox(parent, -1); - - // Set up the markers that we use do indicate the current line and a breakpoint - m_view->MarkerDefine( MARKER_CURRENT, wxSTC_MARK_ARROW , *wxGREEN, *wxGREEN ); - m_view->MarkerDefine( MARKER_CURRENT_BG, wxSTC_MARK_BACKGROUND, *wxGREEN, *wxGREEN ); - m_view->MarkerDefine( MARKER_BREAKPOINT, wxSTC_MARK_CIRCLEPLUS, *wxRED, *wxRED ); - - m_view->SetMarginWidth(1, 16); - m_view->SetMarginType(1, wxSTC_MARGIN_SYMBOL); - - // Make sure that the text control tells us when the user clicks in the left margin - m_view->SetMarginSensitive( 0, true ); - m_view->SetMarginSensitive( 1, true ); - m_view->SetMarginSensitive( 2, true ); - - // Make sure the user can't edit the source code for this function... - m_view->SetReadOnly( true ); - - // We create a ctlCodeWindow when a dbgPgThread intercepts a PLDBGBREAK NOTICE - // generated by the PostgreSQL server. The NOTICE contains a TCP port number - // and we connect to that port here. - m_parent->manager.AddPane(m_view, wxAuiPaneInfo().Name(wxT("sourcePane")).Caption(_("Source pane")).Center().CaptionVisible(false).CloseButton(false).MinSize(wxSize(200, 100)).BestSize(wxSize(350, 200))); - m_parent->manager.AddPane(m_stackWindow, wxAuiPaneInfo().Name(wxT("stackPane")).Caption(_("Stack pane")).Right().MinSize(wxSize(100, 100)).BestSize(wxSize(250, 200))); - m_parent->manager.AddPane(m_tabWindow, wxAuiPaneInfo().Name(wxT("outputPane")).Caption(_("Output pane")).Bottom().MinSize(wxSize(200, 100)).BestSize(wxSize(550, 300))); - - // Now (re)load the layout - wxString perspective; - settings->Read(wxT("Debugger/frmDebugger/Perspective-") + wxString(FRMDEBUGGER_PERSPECTIVE_VER), &perspective, FRMDEBUGGER_DEFAULT_PERSPECTIVE); - m_parent->manager.LoadPerspective(perspective, true); - - // And reset the captions - m_parent->manager.GetPane(wxT("sourcePane")).Caption(_("Source pane")); - m_parent->manager.GetPane(wxT("stackPane")).Caption(_("Stack pane")); - m_parent->manager.GetPane(wxT("outputPane")).Caption(_("Output pane")); - - // Sync the View menu options - m_parent->m_viewMenu->Check(MENU_ID_VIEW_STACKPANE, m_parent->manager.GetPane(wxT("stackPane")).IsShown()); - m_parent->m_viewMenu->Check(MENU_ID_VIEW_OUTPUTPANE, m_parent->manager.GetPane(wxT("outputPane")).IsShown()); - - // Enable the options for these controls - m_parent->m_viewMenu->Enable(MENU_ID_VIEW_OUTPUTPANE, true); - m_parent->m_viewMenu->Enable(MENU_ID_VIEW_STACKPANE, true); - - m_parent->manager.Update(); - - // The wsDbgConn constructor connects to the given host+port and - // and sends events to us whenever a string arrives from the - // debugger server. - - m_parent->getStatusBar()->SetStatusText( _( "Connecting to debugger" ), 1 ); - m_dbgConn = new dbgPgConn(m_parent, connProps, (this != 0 ? true : false) ); - m_parent->getStatusBar()->SetStatusText( _( "Connecting to debugger..." ), 1 ); - - // Our proxy API may throw (perfectly legitimate) errors at us (for example, - // if the target process ends while we are waiting for a breakpoint) - apparently - // those error messages scare the user when they show up in the log, so we'll - // just suppress logging for this session - - PQclear( m_dbgConn->waitForCommand( wxT( "SET log_min_messages TO fatal" ))); - - m_sessionType = SESSION_TYPE_UNKNOWN; - - // force - enableTools(); -} - -void ctlCodeWindow::OnClose(wxCloseEvent &event) -{ - if (event.CanVeto() && !m_targetAborted && !m_targetComplete) - { - if (wxMessageBox(_("Are you sure you wish to abort the debugging session?\nThis will abort the function currently being debugged."), _("Close debugger"), wxICON_QUESTION | wxYES_NO) != wxYES) - { - event.Veto(); - return; - } - } - // If we're global debugging, we may be waiting for a - // breakpoint if we're waiting for the function to be - // called a second, third, fourth etc. time - if (!m_parent->m_standaloneDirectDbg) - { - m_targetAborted = true; - if (m_dbgConn) - { - m_dbgConn->Cancel(); - stopDebugging(); - } - } - else - { - // If we haven't completed or aborted, stop debugging. - if (!m_targetComplete && !m_targetAborted) - stopDebugging(); - } - - // Wait for the abort to complete. - while (!m_targetComplete && !m_targetAborted) - wxTheApp->Yield(true); - - closeConnection(); - m_timer.Stop(); - -#ifdef __WXGTK__ - if (m_parent->m_standaloneDirectDbg && m_targetAborted && !m_targetComplete) - m_parent->m_standaloneDirectDbg->Close(); -#endif - - event.Skip(); -} - -//////////////////////////////////////////////////////////////////////////////// -// OnMarginClick() -// -// This event handler is called when the user clicks in the margin to the left -// of a line of source code. We use the margin to display breakpoint indicators -// so it makes sense that if you click on an breakpoint indicator, we will clear -// that breakpoint. If you click on a spot that does not contain a breakpoint -// indicator (but it's still in the margin), we create a new breakpoint at that -// line. - -void ctlCodeWindow::OnMarginClick( wxStyledTextEvent &event ) -{ - int lineNumber; - - // Check that the user clicked on the line number or breakpoint margin. - // We don't want to set a breakpoint when the user folds/unfolds code. - if (!(event.GetMargin() == 0 || event.GetMargin() == 1)) - return; - - lineNumber = m_view->LineFromPosition(event.GetPosition()); - if (!lineNumber) - return; - - // If we already have a breakpoint at the clickpoint, disable it, otherwise - // create a new breakpoint. - - if(m_view->MarkerGet(lineNumber) &MARKERINDEX_TO_MARKERMASK(MARKER_BREAKPOINT)) - clearBreakpoint(lineNumber, true); - else - setBreakpoint(lineNumber); -} - -//////////////////////////////////////////////////////////////////////////////// -// OnPositionStc() -// -// Update the current line number etc in the status bar. - -void ctlCodeWindow::OnPositionStc( wxStyledTextEvent &event ) -{ - wxString pos; - pos.Printf(_("Ln %d Col %d Ch %d"), m_view->LineFromPosition(m_view->GetCurrentPos()) + 1, m_view->GetColumn(m_view->GetCurrentPos()) + 1, m_view->GetCurrentPos() + 1); - m_parent->getStatusBar()->SetStatusText(pos, 2); -} - - -//////////////////////////////////////////////////////////////////////////////// -// doDebug() -// -// This function gets things started by asking the proxy process to attach -// to the given port (m_debugPort). - -void ctlCodeWindow::startLocalDebugging() -{ - m_sessionType = SESSION_TYPE_DIRECT; - - m_dbgConn->startCommand( wxString::Format( m_commandAttach, m_debugPort.c_str()), GetEventHandler(), RESULT_ID_ATTACH_TO_PORT ); -} - -void ctlCodeWindow::resumeLocalDebugging() -{ - // The user has asked to resume local debugging after the previous target finished. - // We can find ourself in two different states here: - // - // 1) The previous target may have run to completion, in which case we are already - // waiting for the target to hit a breakpoint - // - // 2) The user aborted the previous target, in which case we are NOT waiting for - // a breakpoint and we'd better tell the proxy to wait - // - // We use the m_targetAborted flag to figure out which of the two states were are - // in. If m_targetAborted is false, our proxy is already waiting for the target - // to hit a breakpoint so we don't have to do anything here. If m_targetAborted - // is true, the proxy is waiting for another command from us - send it a waitForBreakpoint - // request - - // Clear the source cache in case someone updated the function in another session - clearSourceCache(); - m_displayedFuncOid = wxT("-1"); - m_displayedPackageOid = wxT("-1"); - - if( m_targetAborted ) - { - m_targetAborted = false; - m_dbgConn->startCommand( wxString::Format( m_commandWaitForBreakpoint, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// OnActivate() -// -// This event handler is called when this window is activated *or* deactivated. -// When we become active, we want to display the debugger toolbar. -// When we become inactive, we want to hide the debugger toolbar. - -void ctlCodeWindow::disableTools() -{ - setTools(false); -} - -void ctlCodeWindow::enableTools() -{ - setTools(true); -} - -void ctlCodeWindow::setTools(bool enable) -{ - // If we don't have a connection to the debugger proxy, disable all - // of the debugger controls - bool activateDebug = true; - - if( m_dbgConn == NULL ) - activateDebug = false; - - if( enable == false ) - activateDebug = false; - - ctlMenuToolbar *t = m_parent->m_toolBar; - wxMenu *m = m_parent->m_debugMenu; - - // We may find that our toolbar has disappeared during application shutdown - - // It seems a little strange that OnActivate() is called during shutdown, but - // that does seem to happen on Win32 hosts. - - if (t) - { - t->EnableTool( MENU_ID_STEP_INTO, activateDebug ); - t->EnableTool( MENU_ID_STEP_OVER, activateDebug ); - t->EnableTool( MENU_ID_CONTINUE, activateDebug ); - t->EnableTool( MENU_ID_TOGGLE_BREAK, activateDebug ); - t->EnableTool( MENU_ID_CLEAR_ALL_BREAK, activateDebug ); - t->EnableTool( MENU_ID_STOP, activateDebug ); - } - - if (m) - { - m->Enable( MENU_ID_STEP_INTO, activateDebug ); - m->Enable( MENU_ID_STEP_OVER, activateDebug ); - m->Enable( MENU_ID_CONTINUE, activateDebug ); - m->Enable( MENU_ID_TOGGLE_BREAK, activateDebug ); - m->Enable( MENU_ID_CLEAR_ALL_BREAK, activateDebug ); - m->Enable( MENU_ID_STOP, activateDebug ); - } - - /* Activate hook */ - m_parent->Show( true ); -} - -//////////////////////////////////////////////////////////////////////////////// -// OnIdle() -// -// This event handler is called during 'idle time' (that is, when other events -// are not waiting in the queue). We use the idle period to update most parts -// of the user interface in a lazy fashion. That way, the debugger doesn't -// bog down when you hit the 'step over' or 'step into' key repeatedly - we -// don't bother updating the variable list, call stack, or breakpoint markers -// until the user pauses for a moment. - -void ctlCodeWindow::OnIdle( wxIdleEvent &event ) -{ - if (m_targetAborted) - return; - - if( m_updateVars ) - { - m_updateVars = false; - m_dbgConn->startCommand( wxString::Format( m_commandGetVars, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_GET_VARS ); - return; - } - - if( m_updateStack ) - { - m_updateStack = false; - m_dbgConn->startCommand( wxString::Format( m_commandGetStack, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_GET_STACK ); - return; - } - - if( m_updateBreakpoints ) - { - m_updateBreakpoints = false; - m_dbgConn->startCommand( wxString::Format( m_commandGetBreakpoints, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_GET_BREAKPOINTS ); - return; - } - -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultPortAttach() -// -// This event handler is called when the result set of an earlier query arrives -// from the proxy. In this case, the result set is generated by a call to -// pldbg_attach_to_port(). If the query succeeded, our proxy is connected to -// the debugger server running inside of the target process. At that point, -// we have to wait for the target process to wait for a breakpoint so we -// queue up another query (a call to pldbg_wait_for_breakpoint()). - -void ctlCodeWindow::ResultPortAttach( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( result.getCommandStatus() == PGRES_TUPLES_OK ) - { - m_parent->getStatusBar()->SetStatusText( _( "Connected to debugger" ), 1 ); - - m_sessionHandle = result.getString( 0 ); - - m_dbgConn->startCommand( wxString::Format( m_commandWaitForBreakpoint, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - } - else - { - wxLogError( result.getErrorMessage(), _( "Connection Error" )); - closeConnection(); - } -} - - -//////////////////////////////////////////////////////////////////////////////// -// ResultBreakpoint() -// -// This event handler is called when the target process pauses. The target -// may have reached a breakpoint or it may have just completed a step/over or -// step/into. -// -// In any case, we schedule an update of the user interface for the next idle -// period by calling updateUI(). - -void ctlCodeWindow::ResultBreakpoint( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - if( result.getCommandStatus() == PGRES_TUPLES_OK ) - { - // Change our focus - if (result.columnExists(wxT("pkg"))) - m_focusPackageOid = result.getString(wxT( "pkg")); - else - m_focusPackageOid = wxT("0"); - - m_focusFuncOid = result.getString(wxT("func")); - - // The result set contains one tuple: - // packageOID, functionOID, linenumber - m_parent->getStatusBar()->SetStatusText(wxString::Format(_( "Paused at line %d"), atoi(result.getString(wxT("linenumber")).ToAscii()) - 1), 1); - updateUI(result); - - /* break point markup line number */ - unhilightCurrentLine(); - - int current_line = atoi(result.getString( wxT("linenumber")).ToAscii()) - 1; - if ( current_line < 0) - current_line = 1; - m_currentLineNumber = current_line; - - m_view->SetAnchor(m_view->PositionFromLine(!current_line ? 0 : current_line - 1)); - m_view->SetCurrentPos(m_view->PositionFromLine(!current_line ? 0 : current_line - 1)); - m_view->MarkerAdd(current_line - 1, MARKER_CURRENT); - m_view->MarkerAdd(current_line - 1, MARKER_CURRENT_BG); - m_view->EnsureCaretVisible(); - enableTools(); - - } - else if(result.getCommandStatus() == PGRES_FATAL_ERROR) - { - if (!m_targetAborted) - { - // We were waiting for a breakpoint (because we just sent a step into, step over, or continue request) and - // the proxy sent us an error instead. Presumably, the target process exited before reaching a breakpoint. - // - // If we have any global breakpoints, we must have a listener in the proxy... wait for the next target process. - - if( m_breakpoints.GetCount()) - { - m_dbgConn->startCommand( wxString::Format(m_commandWaitForTarget, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_TARGET_READY); - - m_parent->getStatusBar()->SetStatusText(_("Waiting for a target"), 1 ); - m_parent->getStatusBar()->SetStatusText(wxT(""), 2 ); - - launchWaitingDialog(); - } - } - } - } -} - - -void ctlCodeWindow::launchWaitingDialog() -{ - m_parent->getStatusBar()->SetStatusText(wxString::Format( _( "Waiting for another session to invoke %s" ), m_targetName.c_str()), 1 ); - - // NOTE: the waiting-dialog takes forever to appear running a remote X session so you can disable it by defining the following env. variable - if( getenv( "SUPPRESS_WAIT_DIALOG" )) - m_progressBar = NULL; - else - { - m_progressBar = new wxProgressDialog(_( "Waiting for target" ), wxString::Format( _( "Waiting for breakpoint in %s" ), m_targetName.c_str()), 100, m_parent, wxPD_SMOOTH | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME ); - - m_timer.Start( 100 ); // 10 clock ticks per second - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultVarList() -// -// This event handler is called when the proxy finishes sending us a list of -// variables (in response to an earlier call to pldbg_get_variables()). -// -// We extract the variable names, types, and values from the result set and -// add them to the variable (and parameter) windows. - -void ctlCodeWindow::ResultVarList( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - if( result.getCommandStatus() == PGRES_TUPLES_OK ) - { - // The result set contains one tuple per variable - for( int row = 0; row < result.getRowCount(); ++row ) - { - wxString varName = result.getString( wxT( "name" ), row ); - char varClass = result.getString( wxT( "varclass" ), row )[0]; - - if( varClass == 'A' ) - { - getParamWindow( true )->addVar( varName, result.getString( wxT( "value" ), row ), result.getString( wxT( "dtype" ), row ), result.getBool( wxT( "isconst" ), row )); - } - else if( varClass == 'P' ) - { - getPkgVarWindow( true )->addVar( varName, result.getString( wxT( "value" ), row ), result.getString( wxT( "dtype" ), row ), result.getBool( wxT( "isconst" ), row )); - } - else - { - getVarWindow( true )->addVar( varName, result.getString( wxT( "value" ), row ), result.getString( wxT( "dtype" ), row ), result.getBool( wxT( "isconst" ), row )); - } - } - } - - // Update the next part of the user interface - m_updateStack = true; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultStack() -// -// This event handler is called when the proxy finishes sending us a stack -// trace (in response to an earlier call to pldbg_get_stack()). -// -// We extract each frame from the result set and add it to the stack window. -// For each frame, the proxy sends us the function name, line number, and -// a string that holds the name and value of each argument. - -void ctlCodeWindow::ResultStack( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - if( result.getCommandStatus() == PGRES_TUPLES_OK ) - { - // The result set contains one tuple per frame: - // package, function, linenumber, args - - wxArrayString stack; - - for(int row = 0; row < result.getRowCount(); ++row) - stack.Add(wxString::Format(wxT( "%s(%s)@%s" ), result.getString(wxT("targetName"), row ).c_str(), result.getString(wxT("args"), row).c_str(), result.getString(wxT("linenumber"), row).c_str())); - - getStackWindow()->clear(); - getStackWindow()->setStack( stack ); - - } - - m_updateBreakpoints = true; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultBreakpoints() -// -// This event handler is called when the proxy finishes sending us a list of -// breakpoints (in response to an earlier SHOW BREAKPOINTS command). -// -// We clear out the old breakpoint markers and then display a new marker -// for each breakpoint defined in the current function. - -void ctlCodeWindow::ResultBreakpoints(wxCommandEvent &event) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost(result)) - closeConnection(); - else - { - if(result.getCommandStatus() == PGRES_TUPLES_OK) - { - clearBreakpointMarkers(); - - // The result set contains one tuple per breakpoint: - // packageOID, functionOID, linenumber - - for(int row = 0; row < result.getRowCount(); ++row) - { - m_view->MarkerAdd(result.getLong(wxT("linenumber"), row) - 1, MARKER_BREAKPOINT); - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultSource() -// -// This event handler is called when the proxy finishes sending us the source -// code for a function (in response to an earlier call to pldbg_get_source()). -// -// We keep a client-side cache of source code listings to avoid an extra -// round trip for each step. In this function, we add the source code to -// the cache and then display the source code in the source window. - -void ctlCodeWindow::ResultSource(wxCommandEvent &event) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if(connectionLost(result)) - closeConnection(); - else - { - wxString pkg = wxT("0"); - if (result.columnExists(wxT("pkg"))) - pkg = result.getString(wxT("pkg")); - - cacheSource(pkg, result.getString(wxT("func")), result.getString(wxT("source")), wxString::Format(wxT("%s(%s)"), result.getString(wxT("targetName")).c_str(), result.getString(wxT("args")).c_str())); - displaySource(pkg, result.getString(wxT("func"))); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultDeletedBreakpoint() -// -// This event handler is called when the proxy finishes executing a DROP -// BREAKPOINT command on our behalf. -// -// If the DROP BREAKPOINT command succeeded, we display a mesasge in the -// status bar. - -void ctlCodeWindow::ResultDeletedBreakpoint( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - if( result.getBool( 0 )) - { - m_updateBreakpoints = true; - m_parent->getStatusBar()->SetStatusText( _( "Breakpoint dropped" ), 1 ); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultNewBreakpoint() -// -// This event handler is called when the proxy finishes executing a CREATE -// BREAKPOINT command on our behalf. -// -// We schedule a refresh of our breakpoint markers for the next idle period. - -void ctlCodeWindow::ResultNewBreakpoint( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else if( gotFatalError( result )) - popupError(result); - else - { - } -} - -void ctlCodeWindow::ResultNewBreakpointWait( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else if( gotFatalError( result )) - popupError(result); - else - { - setTools(false); - m_dbgConn->startCommand( wxString::Format( m_commandWaitForTarget, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_TARGET_READY ); - - launchWaitingDialog(); - } -} -//////////////////////////////////////////////////////////////////////////////// -// ResultDepositValue() -// -// This event handler is called when the proxy completes a 'deposit' operation -// (in response to an earlier call to pldbg_deposit_value()). -// -// We schedule a refresh of our variable window(s) for the next idle period. - -void ctlCodeWindow::ResultDepositValue( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - if( result.getBool( 0 )) - { - m_parent->getStatusBar()->SetStatusText( _( "Value changed" ), 1 ); - m_updateVars = true; - } - else - { - wxLogError(wxT( "Could not deposit the new value." )); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultAbortTarget() -// -// This event handler is called when the proxy completes an 'abort target' -// operation (in response to an earlier call to pldbg_abort_target()). -// - -void ctlCodeWindow::ResultAbortTarget( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - if( result.getBool( 0 )) - { - // Make a note of the fact that we've aborted the target, that way we - // can know (should we start debugging again) that we are *not* waiting - // for a breakpoint. - m_targetAborted = true; - - m_parent->getStatusBar()->SetStatusText( _( "Execution Canceled" ), 1 ); - - // Remove the current-line indicator - unhilightCurrentLine(); - - // And force the toolbar to refresh itself (to disable the debugger-related tools) - setTools(false); - } - else - { - // FIXME: display error message here... - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// connectionLost() -// -// This function checks the given result set to determine whether the connection -// to the debugger proxy is still viable. For the moment, we assume that any -// fatal error is... fatal. We should probably PQstatus() if we get a fatal -// error. - -bool ctlCodeWindow::connectionLost( dbgResultset &resultSet ) -{ - if (!m_dbgConn) - return true; - - if( m_dbgConn->isConnected()) - return false; - else - return true; -} - -bool ctlCodeWindow::gotFatalError( dbgResultset &resultSet ) -{ - if( resultSet.getCommandStatus() == PGRES_FATAL_ERROR ) - return( true ); - else - return( false ); -} - -void ctlCodeWindow::popupError(dbgResultset &resultSet) -{ - wxLogError(wxT("%s"), resultSet.getErrorMessage().c_str()); - m_timer.Stop(); - m_parent->getStatusBar()->SetStatusText(_( "Done." ), 1 ); - setTools(false); -} - -//////////////////////////////////////////////////////////////////////////////// -// closeConnection() -// -// This member function closes the connection to the debugger and changes the -// user interface to let the user know what just happened. In particular, we -// remove the breakpoint markers (they may be obsolete) and disable the -// debugger-related tools on the toolbar. - -void ctlCodeWindow::closeConnection() -{ - // Close the debugger (proxy) connection - if (m_dbgConn) - m_dbgConn->Close(); - m_dbgConn = NULL; - - // Let the user know what happened - m_parent->getStatusBar()->SetStatusText( _( "Debugger connection terminated (session complete)" ), 1 ); - - // Remove the current-line indicator - unhilightCurrentLine(); - - // And force the toolbar to refresh itself (to disable the debugger-related tools) - setTools(false); -} - -//////////////////////////////////////////////////////////////////////////////// -// updateSourceCode() -// -// This function is invoked whenever the target process pauses (either because -// it reached a breakpoint or because it just completed a step over/into). In -// this function, we update the source code window. -// -// The caller gives us a result set (that had better contain a breakpoint tuple) -// that gives us the OID of the function that we're paused in. We search our -// cache for the source code for that function. If we don't already have the -// source code for the function, we send a request for the code to the proxy and -// (asynchronously) wait for the result set (ResultSource() is called when the -// result set arrives). If we have the required source code (in the cache), we -// display the code in the source window (if not already displayed). - -void ctlCodeWindow::updateSourceCode(dbgResultset &breakpoint) -{ - wxString packageOID, funcOID, lineNumber; - - if (breakpoint.columnExists(wxT("pkg"))) - packageOID = breakpoint.getString( wxT("pkg")); - else - packageOID = wxT("0"); - - funcOID = breakpoint.getString(wxT("func")); - lineNumber = breakpoint.getString(wxT("linenumber")); - - m_currentLineNumber = atoi((const char *)lineNumber.c_str()); - - if(!findSourceInCache(packageOID, funcOID)) - { - getSource(packageOID, funcOID); - } - else - { - displaySource(packageOID, funcOID); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// updateUI() -// -// This function is called each time the target pauses (either because it reached -// a breakpoint or because it just completed a step over/into). We update some -// parts of the user interface in a lazy fashion so that we remain responsive if -// the user repeatedly hits the step over/into key. updateUI() turns off the lazy -// update flags and the updates the source code window - when the source code -// refresh completes, we schedule a variable refresh for the next idle period. -// When the variable refresh completes, it schedules a stack refresh... - -void ctlCodeWindow::updateUI(dbgResultset &breakpoint) -{ - // Arrange for the lazy parts of our UI to be updated - // during the next IDLE time - m_updateVars = false; - m_updateStack = false; - m_updateBreakpoints = false; - - updateSourceCode(breakpoint); -} - -//////////////////////////////////////////////////////////////////////////////// -// clearBreakpointMarkers() -// -// This is a helper function that clears all of the breakpoint markers currently -// displayed in the source code window (m_view). - -void ctlCodeWindow::clearBreakpointMarkers() -{ - int lineNo = 0; - - while(( lineNo = m_view->MarkerNext( lineNo, MARKERINDEX_TO_MARKERMASK( MARKER_BREAKPOINT ))) != -1 ) - m_view->MarkerDelete( lineNo++, MARKER_BREAKPOINT ); -} - -//////////////////////////////////////////////////////////////////////////////// -// findSourceInCache() -// -// A ctlCodeWindow can display the source code for many different functions (if -// you step from one function into another function, the same ctlCodeWindow will -// display the source code for each function). -// -// To avoid constantly re-sending the source code for each function over the -// network we keep a cache (m_sourceCodeMap) of the source code for each -// function that we've seen. The cache is indexed by function OID. We keep -// track of the transaction and command ID for each function too so we know -// when our cached copy becomes stale. -// -// This function searches the cache for an entry that matches the given -// function ID. Note that we simply return true or false to indicate -// whether the function exists in the cache - to retreive the actual source -// code for a function, call getSource() - -bool ctlCodeWindow::findSourceInCache(const wxString &packageOID, const wxString &funcOID) -{ - sourceHash::iterator match = m_sourceCodeMap.find(funcOID); - - if (match == m_sourceCodeMap.end()) - return(false); - else - { - // FIXME: compare the xid and cid here, throw out out the cached copy (and return false) if they don't match - return(true); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// clearSourceCache() -// -// (Re-)initializes the source cache. - -void ctlCodeWindow::clearSourceCache() -{ - m_sourceCodeMap.clear(); - - // Put a dummy entry for invalid function OID to the cache. This is - // displayed at least for inline code blocks, as we currently have - // no way to fetch the source for those. - m_sourceCodeMap[wxT("0")] = wsCodeCache(wxT("0"), wxT("0"), wxT(""), wxT("")); -} - -//////////////////////////////////////////////////////////////////////////////// -// cacheSource() -// -// This function adds the source code for a given function to the cache. See -// findSourceInCache() for more details. - -void ctlCodeWindow::cacheSource(const wxString &packageOID, const wxString &funcOID, const wxString &sourceCode, const wxString &signature) -{ - // Throw out any stale version - m_sourceCodeMap.erase(funcOID); - - // And add the new version to the cache. - m_sourceCodeMap[funcOID] = wsCodeCache(packageOID, funcOID, sourceCode, signature); - -} - -//////////////////////////////////////////////////////////////////////////////// -// getSource() -// -// This function retrieves the source code for a given function from the -// PostgreSQL server. We don't actually wait for completionm, we just -// schedule the request. - -void ctlCodeWindow::getSource(const wxString &packageOID, const wxString &funcOID) -{ - if (m_dbgConn->DebuggerApiVersion() <= DEBUGGER_V2_API) - m_dbgConn->startCommand(wxString::Format(m_commandGetSourceV1, packageOID.c_str(), funcOID.c_str(), m_sessionHandle.c_str(), packageOID.c_str(), funcOID.c_str(), m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_GET_SOURCE); - else - m_dbgConn->startCommand(wxString::Format(m_commandGetSourceV2, funcOID.c_str(), m_sessionHandle.c_str(), funcOID.c_str(), m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_GET_SOURCE); -} - -//////////////////////////////////////////////////////////////////////////////// -// displaySource() -// -// This function loads the source code for the given funcID into the view. -// If the requested function is already loaded, we just return without doing -// any extra work. - -void ctlCodeWindow::displaySource(const wxString &packageOID, const wxString &funcOID) -{ - - // We're about to display the source code for the target, give the keyboard - // focus to the view (so that function keys will work). This seems like a - // reasonable point in time to grab the focus since we're just doing something - // rather visual. - m_view->SetFocus(); - - // If we've already loaded the source code for this function, just update the current-line marker - if( m_displayedFuncOid != funcOID || m_displayedPackageOid != packageOID ) - { - // We're looking at a different target now, delete all of the local, package, and parameter variables - // so we can add the variables defined by the new target. - if( getVarWindow( false )) - getVarWindow( false )->delVar(); - - if( getParamWindow( false )) - getParamWindow( false )->delVar(); - - if( getPkgVarWindow( false )) - getPkgVarWindow( false )->delVar(); - - m_displayedFuncOid = funcOID; - m_displayedPackageOid = packageOID; - - wsCodeCache &codeCache = m_sourceCodeMap[funcOID]; - - // Now erase any old code and write out the new listing - m_view->SetReadOnly( false ); - - // Get the source. Ignore any CRs. - wxString src = codeCache.getSource(); - src.Replace(wxT("\r"), wxT("")); - - // Before PostgreSQL 9.1, the server ignored an initial newline - // when calculating line numbers. The line numbers of what we display - // has to match the server's numbering, or all the highlighting and - // breakpoints are off, so if we're connected to a server older than - // 9.1, strip the initial newline from the display. - if (src.StartsWith(wxT("\n"))) - { - if (!m_dbgConn->BackendMinimumVersion(9, 1)) - src = src.AfterFirst('\n'); - } - - m_view->SetText(src); - - m_view->Colourise(0, src.Length()); - m_view->SetReadOnly(true); - } - - // Clear the current-line indicator - int lineNo = m_view->MarkerNext(0, MARKERINDEX_TO_MARKERMASK( MARKER_CURRENT)); - int current_line = m_currentLineNumber; - - if (lineNo != -1) - { - m_view->MarkerDelete(lineNo, MARKER_CURRENT); - m_view->MarkerDelete(lineNo, MARKER_CURRENT_BG); - } - - // Adjustment of the next position - if (current_line >= 1) - current_line--; - - // Add the current-line indicator to the current line of code - m_view->MarkerAdd(current_line, MARKER_CURRENT); - m_view->MarkerAdd(current_line, MARKER_CURRENT_BG); - - // Scroll the source code listing (if required) to make sure - // that this line of code is visible - // - // (note: we set the anchor and the caret to the same position to avoid - // creating a selection region) - m_view->SetAnchor(m_view->PositionFromLine(current_line)); - m_view->SetCurrentPos(m_view->PositionFromLine(current_line)); - m_view->EnsureCaretVisible(); - - // Update the next lazy part of the user interface (the variable list) - m_updateVars = true; -} - -//////////////////////////////////////////////////////////////////////////////// -// getLineNo() -// -// Figure out which line (in the source code listing) contains the cursor -// (actually, the insertion point) - -int ctlCodeWindow::getLineNo( ) -{ - return(m_view->LineFromPosition( m_view->GetCurrentPos( ))); -} - -//////////////////////////////////////////////////////////////////////////////// -// OnCommand() -// -// This event handler is called when the user clicks a button in the debugger -// toolbar. - -void ctlCodeWindow::OnCommand( wxCommandEvent &event ) -{ - - switch( event.GetId()) - { - case MENU_ID_TOGGLE_BREAK: - { - // The user wants to set or clear a breakpoint at the line that - // contains the insertion point (the caret) - if (isBreakpoint(getLineNo())) - clearBreakpoint(getLineNo(), true); - else - setBreakpoint(getLineNo()); - break; - } - - case MENU_ID_CLEAR_ALL_BREAK: - { - // The user wants to clear all the breakpoint - clearAllBreakpoints(); - break; - } - - case MENU_ID_CONTINUE: - { - // The user wants to continue execution (as opposed to - // single-stepping through the code). Unhilite all - // variables and tell the debugger server to continue. - m_dbgConn->startCommand( wxString::Format( m_commandContinue, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - m_parent->getStatusBar()->SetStatusText( _( "Waiting for target (continue)..." ), 1 ); - unhilightCurrentLine(); - disableTools(); - break; - } - - case MENU_ID_STEP_OVER: - { - // The user wants to step-over a function invocation (or - // just single-step). Unhilite all variables and tell the - // debugger server to step-over - m_dbgConn->startCommand( wxString::Format( m_commandStepOver, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - m_parent->getStatusBar()->SetStatusText( _( "Waiting for target (step over)..." ), 1 ); - unhilightCurrentLine(); - disableTools(); - break; - } - - case MENU_ID_STEP_INTO: - { - // The user wants to step-into a function invocation (or - // just single-step). Unhilite all variables and tell the - // debugger server to step-into - m_dbgConn->startCommand( wxString::Format( m_commandStepInto, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - m_parent->getStatusBar()->SetStatusText( _( "Waiting for target (step into)..." ), 1 ); - unhilightCurrentLine(); - disableTools(); - break; - } - - case MENU_ID_STOP: - { - stopDebugging(); - if (!m_parent->m_standaloneDirectDbg) - closeConnection(); - unhilightCurrentLine(); - break; - } - - default: - break; - } -} - -void ctlCodeWindow::OnNoticeReceived( wxCommandEvent &event ) -{ - getMessageWindow()->AppendText( event.GetString()); - m_tabWindow->selectTab( ID_MSG_PAGE ); -} - -void ctlCodeWindow::OnResultSet( PGresult *result ) -{ - getResultWindow()->fillGrid( result ); -} - -//////////////////////////////////////////////////////////////////////////////// -// setBreakpoint() -// -// This function creates a breakpoint at the given line number (actually, it -// sends a request to the debugger server to create the breakpoint - the server -// will send back a reply if everything worked). - -void ctlCodeWindow::setBreakpoint(int lineNumber) -{ - if (m_dbgConn->DebuggerApiVersion() <= DEBUGGER_V2_API) - m_dbgConn->startCommand(wxString::Format(m_commandSetBreakpointV1, m_sessionHandle.c_str(), m_displayedPackageOid.c_str(), m_displayedFuncOid.c_str(), lineNumber + 1), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT); - else - m_dbgConn->startCommand(wxString::Format(m_commandSetBreakpointV2, m_sessionHandle.c_str(), m_displayedFuncOid.c_str(), lineNumber + 1), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT); - - m_updateBreakpoints = true; -} - -//////////////////////////////////////////////////////////////////////////////// -// clearBreakpoint() -// -// This function clears a breakpoint at the given line number (actually, it -// sends a request to the debugger server to clear the breakpoint) - -void ctlCodeWindow::clearBreakpoint( int lineNumber, bool requestUpdate ) -{ - if (m_dbgConn->DebuggerApiVersion() <= DEBUGGER_V2_API) - m_dbgConn->startCommand(wxString::Format(m_commandClearBreakpointV1, m_sessionHandle.c_str(), m_displayedPackageOid.c_str(), m_displayedFuncOid.c_str(), lineNumber + 1), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT); - else - m_dbgConn->startCommand(wxString::Format(m_commandClearBreakpointV2, m_sessionHandle.c_str(), m_displayedFuncOid.c_str(), lineNumber + 1), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT); - - if (requestUpdate) - m_updateBreakpoints = true; -} - -void ctlCodeWindow::clearAllBreakpoints() -{ - int lineNo = 0; - - if (m_view) - { - while(( lineNo = m_view->MarkerNext( lineNo, MARKERINDEX_TO_MARKERMASK( MARKER_BREAKPOINT ))) != -1 ) - clearBreakpoint( lineNo++, false ); - - m_updateBreakpoints = true; - } -} - -void ctlCodeWindow::stopDebugging() -{ - m_dbgConn->startCommand( wxString::Format( m_commandAbortTarget, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_ABORT_TARGET ); -} - -//////////////////////////////////////////////////////////////////////////////// -// OnSelectFrame() -// -// This event handler is called when the user clicks on a frame in the stack- -// trace window. We ask the debugger server to switch to that frame, update -// the breakpoint markers, and send a list of variables that are in-scope in -// the selected frame. -// -// Note: when the debugger server sees a '^' command, it automatically sends -// is a "current-statement-location" message and we'll update the source -// code listing when we receive that message. -// - -void ctlCodeWindow::OnSelectFrame( wxCommandEvent &event ) -{ - if( event.GetSelection() != -1 ) - { - if (!m_targetComplete && !m_targetAborted) - m_dbgConn->startCommand( wxString::Format( m_commandSelectFrame, m_sessionHandle.c_str(), event.GetSelection()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - } -} - -void ctlCodeWindow::OnVarChange( wxGridEvent &event ) -{ - ctlVarWindow *window; - - if( event.GetId() == ID_PARAMGRID ) - window = getParamWindow( false ); - else if( event.GetId() == ID_VARGRID ) - window = getVarWindow( false ); - else - window = getPkgVarWindow( false ); - - wxString varName = window->getVarName( event.GetRow()); - wxString varValue = window->getVarValue( event.GetRow()); - - if( event.GetId() == ID_PKGVARGRID ) - varName.Prepend( wxT( "@" )); - - m_dbgConn->startCommand( wxString::Format( m_commandDepositValue, m_sessionHandle.c_str(), m_dbgConn->qtDbString(varName).c_str(), -1, m_dbgConn->qtDbString(varValue).c_str()), GetEventHandler(), RESULT_ID_DEPOSIT_VALUE ); - - -} - -void ctlCodeWindow::startGlobalDebugging( ) -{ - m_sessionType = SESSION_TYPE_INCONTEXT; - - m_dbgConn->startCommand(m_commandCreateListener, GetEventHandler(), RESULT_ID_LISTENER_CREATED); -} - -//////////////////////////////////////////////////////////////////////////////// -// ResultListenerCreated() -// -// This event handler is called when the result set of an earlier query arrives -// from the proxy. In this case, the result set is generated by a call to -// pldbg_create_listener(). If the query succeeded, our proxy has created a -// global listener that's listening for a debugger target. At this point, the -// listener is *not* waiting for a target, it's just created the socket. -// Now we'll issue another query (to the proxy) that will force it to wait -// for a debugger target. - -void ctlCodeWindow::ResultListenerCreated( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else - { - // We now have a global listener and a session handle. - // Grab the session handle (we'll need it for just about - // everything else). - - m_sessionHandle = result.getString( 0 ); - - // Now create any global breakpoints that the user requested. - // We start by asking the server to resolve the breakpoint name - // into an OID (or a pair of OID's if the target is defined in a - // package). As each (targetInfo) result arrives, we add a - // breakpoint at the resulting OID. - - unsigned int index = 1; - - for( dbgBreakPointList::Node *node = m_breakpoints.GetFirst(); node; node = node->GetNext(), ++index ) - { - dbgBreakPoint *breakpoint = node->GetData(); - - if( index < m_breakpoints.GetCount()) - addBreakpoint( breakpoint, RESULT_ID_ADD_BREAKPOINT ); - else - addBreakpoint( breakpoint, RESULT_ID_LAST_BREAKPOINT ); - } - } -} - -void ctlCodeWindow::ResultTargetReady( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if(m_progressBar) - { - m_progressBar->Close(); - delete m_progressBar; - m_progressBar = NULL; - } - - if( connectionLost( result )) - closeConnection(); - else - { - setTools(true); - m_dbgConn->startCommand( wxString::Format( m_commandWaitForBreakpoint, m_sessionHandle.c_str()), GetEventHandler(), RESULT_ID_BREAKPOINT ); - } -} - -void ctlCodeWindow::addBreakpoint( dbgBreakPoint *breakpoint, wxEventType nextStep ) -{ - // The user want's to add a (global) breakpoint on a function, procedure, oid, or trigger - // - // First, ask the proxy to resolve the target name into an OID (or two OID's if the - // target happens to reside in a package). When the target info arrives, we'll get a - // RESULT_ID_ADD_BREAKPOINT message. - - char targetType = 0; - - switch( breakpoint->getTargetType()) - { - case dbgBreakPoint::FUNCTION: - targetType = 'f'; - break; - case dbgBreakPoint::PROCEDURE: - targetType = 'p'; - break; - case dbgBreakPoint::OID: - targetType = 'o'; - break; - case dbgBreakPoint::TRIGGER: - targetType = 't'; - break; - } - - m_dbgConn->startCommand( wxString::Format( m_commandGetTargetInfo, breakpoint->getTargetProcess().c_str(), breakpoint->getTarget().c_str(), targetType ), GetEventHandler(), nextStep ); -} - -void ctlCodeWindow::ResultAddBreakpoint( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else if( gotFatalError( result )) - popupError(result); - else - { - /* - * In EnterpriseDB versions <= 9.1 the - * pldbg_set_global_breakpoint function took five arguments, - * the 2nd argument being the package's OID, if any. Starting - * with 9.2, the package OID argument is gone, and the function - * takes four arguments like the community version has always - * done. - */ - if (m_dbgConn->GetIsEdb() && !m_dbgConn->BackendMinimumVersion(9, 2)) - m_dbgConn->startCommand(wxString::Format(m_commandAddBreakpointEDB, m_sessionHandle.c_str(), result.getString(wxT("pkg")).c_str(), result.getString(wxT("target")).c_str(), wxT("NULL"), result.getString(wxT( "pid")).c_str()), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT); - else - m_dbgConn->startCommand(wxString::Format(m_commandAddBreakpointPG, m_sessionHandle.c_str(), result.getString(wxT("target")).c_str(), wxT("NULL"), result.getString(wxT( "pid")).c_str()), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT); - - if( m_targetName.IsEmpty() == false ) - m_targetName.Append( wxT( ", " )); - - m_targetName.Append( result.getString( wxT( "fqname" ))); - } -} - -void ctlCodeWindow::ResultLastBreakpoint( wxCommandEvent &event ) -{ - dbgResultset result((PGresult *)event.GetClientData()); - - if( connectionLost( result )) - closeConnection(); - else if( gotFatalError( result )) - popupError(result); - else - { - /* - * In EnterpriseDB versions <= 9.1 the - * pldbg_set_global_breakpoint function took five arguments, - * the 2nd argument being the package's OID, if any. Starting - * with 9.2, the package OID argument is gone, and the function - * takes four arguments like the community version has always - * done. - */ - if (m_dbgConn->GetIsEdb() && !m_dbgConn->BackendMinimumVersion(9, 2)) - m_dbgConn->startCommand(wxString::Format(m_commandAddBreakpointEDB, m_sessionHandle.c_str(), result.getString(wxT("pkg")).c_str(), result.getString(wxT("target")).c_str(), wxT("NULL"), result.getString(wxT("pid")).c_str()), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT_WAIT); - else - m_dbgConn->startCommand(wxString::Format(m_commandAddBreakpointPG, m_sessionHandle.c_str(), result.getString(wxT("target")).c_str(), wxT("NULL"), result.getString(wxT("pid")).c_str()), GetEventHandler(), RESULT_ID_NEW_BREAKPOINT_WAIT); - - if( m_targetName.IsEmpty() == false ) - m_targetName.Append( wxT( ", " )); - - m_targetName.Append( result.getString( wxT( "fqname" ))); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// getBreakpointList() -// -// This function returns a non-const reference to our breakpoint list. The -// caller typically populates this list before calling startDebugging() - we -// set a breakpoint for each member of the list - -dbgBreakPointList &ctlCodeWindow::getBreakpointList() -{ - return( m_breakpoints ); -} - -void ctlCodeWindow::unhilightCurrentLine() -{ - - int lineNo = m_view->MarkerNext( 0, MARKERINDEX_TO_MARKERMASK( MARKER_CURRENT )); - - if( lineNo != -1 ) - { - m_view->MarkerDelete( lineNo, MARKER_CURRENT ); - m_view->MarkerDelete( lineNo, MARKER_CURRENT_BG ); - } - -} - -void ctlCodeWindow::OnTimer( wxTimerEvent &event ) -{ - if( m_progressBar ) - { - if( m_progressBar->Pulse() == false ) - { - closeConnection(); - m_targetAborted = true; - m_parent->Close(); - } - } - else - { - // If we're indirect debugging from the query tool, the query thread - // might be preventing Idle events being fired, so we do it - // manually here. - if (!m_parent->m_standaloneDirectDbg) - { - wxIdleEvent evt; - OnIdle(evt); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// wsCodeCache constructor -// -// Each entry in our code cache (ctlCodeWindow::m_sourceCodeMap) is an object -// of class wsCodeCache. - -wsCodeCache::wsCodeCache(const wxString &packageOID, const wxString &funcOID, const wxString &source, const wxString &signature) - : m_packageOID(packageOID), m_funcOID(funcOID), m_sourceCode(source), m_signature(signature) -{ -} - - diff --git a/pgadmin/debugger/ctlMessageWindow.cpp b/pgadmin/debugger/ctlMessageWindow.cpp index 3069645..c7316d2 100644 --- a/pgadmin/debugger/ctlMessageWindow.cpp +++ b/pgadmin/debugger/ctlMessageWindow.cpp @@ -17,7 +17,7 @@ // App headers #include "debugger/ctlMessageWindow.h" -IMPLEMENT_CLASS( ctlMessageWindow, wxTextCtrl ) +IMPLEMENT_CLASS(ctlMessageWindow, wxTextCtrl) //////////////////////////////////////////////////////////////////////////////// // ctlMessageWindow constructor @@ -25,20 +25,20 @@ IMPLEMENT_CLASS( ctlMessageWindow, wxTextCtrl ) // Initialize the grid control and clear it out.... // -ctlMessageWindow::ctlMessageWindow( wxWindow *parent, wxWindowID id ) - : wxTextCtrl( parent, wxID_ANY, wxT(""), wxPoint(0, 0), wxSize(0, 0), - wxTE_MULTILINE | wxTE_READONLY) +ctlMessageWindow::ctlMessageWindow(wxWindow *parent, wxWindowID id) + : wxTextCtrl(parent, wxID_ANY, wxT(""), wxPoint(0, 0), wxSize(0, 0), + wxTE_MULTILINE | wxTE_READONLY) { SetFont(settings->GetSQLFont()); } //////////////////////////////////////////////////////////////////////////////// -// addMessage() +// AddMessage() // // Adds the message in the 'DBMS Messages' window. // -void ctlMessageWindow::addMessage( wxString message ) +void ctlMessageWindow::AddMessage(wxString message) { AppendText(message + wxT("\n")); } @@ -49,13 +49,13 @@ void ctlMessageWindow::addMessage( wxString message ) // Removes the given message from the 'DBMS Messages' window. // -void ctlMessageWindow::delMessage( const char *name ) +void ctlMessageWindow::DelMessage(const char *name) { SetValue(wxT("")); } -wxString ctlMessageWindow::getMessage( int row ) +wxString ctlMessageWindow::GetMessage(int row) { - return( GetValue()); + return(GetValue()); } diff --git a/pgadmin/debugger/ctlResultGrid.cpp b/pgadmin/debugger/ctlResultGrid.cpp index 3a47b1e..65d98e8 100644 --- a/pgadmin/debugger/ctlResultGrid.cpp +++ b/pgadmin/debugger/ctlResultGrid.cpp @@ -39,15 +39,24 @@ ctlResultGrid::ctlResultGrid( wxWindow *parent, wxWindowID id ) // Given a result set handle, this function copies the values in that result // set into the grid. -void ctlResultGrid::fillGrid( PGresult *result ) +void ctlResultGrid::FillResult(pgSet *set) { - int rowCount = PQntuples( result ); - int colCount = PQnfields( result ); + // Clear out the old results (if any) and resize + // grid to match the result set + if( GetNumberRows()) + DeleteRows( 0, GetNumberRows()); + if( GetNumberCols()) + DeleteCols( 0, GetNumberCols()); + + if (!set) + return; + + int rowCount = set->NumRows(); + int colCount = set->NumCols(); // If this PGresult represents a non-query command // (like an INSERT), there won't be any columns in // the result set - just return - if( colCount == 0 ) return; @@ -55,42 +64,34 @@ void ctlResultGrid::fillGrid( PGresult *result ) BeginBatch(); - // Clear out the old results (if any) and resize - // grid to match the result set - - if( GetNumberRows()) - DeleteRows( 0, GetNumberRows()); - if( GetNumberCols()) - DeleteCols( 0, GetNumberCols()); + AppendRows(rowCount); + AppendCols(colCount); - AppendRows( rowCount ); - AppendCols( colCount ); - - EnableEditing( false ); + EnableEditing(false); // Copy the column names from the result set into the column headers - - for( int col = 0; col < colCount; ++col ) - SetColLabelValue( col, wxString( PQfname( result, col ), wxConvUTF8 )); + int row = 0, + col; + for(col = 0; col < colCount; ++col) + SetColLabelValue(col, set->ColName(col)); // Now copy each value from the result set into the grid - - for( int row = 0; row < rowCount; ++row ) + while(!set->Eof()) { - for( int col = 0; col < colCount; ++col ) + for(col = 0; col < colCount; ++col) { - if( PQgetisnull( result, row, col )) - SetCellValue( row, col, wxT( "" )); + if(set->IsNull(col)) + SetCellValue(row, col, wxT("")); else - SetCellValue( row, col, wxString( PQgetvalue( result, row, col ), wxConvUTF8 )); + SetCellValue(row, col, set->GetVal(col)); } + row++; + set->MoveNext(); } // Resize each column to fit its content - - AutoSizeColumns( false ); + AutoSizeColumns(false); // Enable repaints - EndBatch(); } diff --git a/pgadmin/debugger/ctlStackWindow.cpp b/pgadmin/debugger/ctlStackWindow.cpp index 1156425..a39631b 100644 --- a/pgadmin/debugger/ctlStackWindow.cpp +++ b/pgadmin/debugger/ctlStackWindow.cpp @@ -33,24 +33,21 @@ ctlStackWindow::ctlStackWindow(wxWindow *parent, wxWindowID id, const wxPoint &p } //////////////////////////////////////////////////////////////////////////////// -// clear() +// ClearStack() // // Remove all stack frames from the display // - -void ctlStackWindow::clear() +void ctlStackWindow::ClearStack() { Set(0, NULL); } //////////////////////////////////////////////////////////////////////////////// -// setStack() +// SetStack() // // Add an array of stack frames to the display // - - -void ctlStackWindow::setStack(const wxArrayString &stack ) +void ctlStackWindow::SetStack(const wxArrayString &stack ) { for(size_t i = 0; i < stack.GetCount(); ++i) { diff --git a/pgadmin/debugger/ctlTabWindow.cpp b/pgadmin/debugger/ctlTabWindow.cpp index 17c4f6d..e2ec306 100644 --- a/pgadmin/debugger/ctlTabWindow.cpp +++ b/pgadmin/debugger/ctlTabWindow.cpp @@ -21,7 +21,7 @@ #include "debugger/dbgConst.h" -IMPLEMENT_CLASS( ctlTabWindow, wxWindow ) +IMPLEMENT_CLASS(ctlTabWindow, wxWindow) //////////////////////////////////////////////////////////////////////////////// // ctlTabWindow constructor @@ -29,153 +29,147 @@ IMPLEMENT_CLASS( ctlTabWindow, wxWindow ) // This constructor creates a new notebook (a tab control) and clears out the // rest of the data members. // - -ctlTabWindow::ctlTabWindow( wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name ) - : ctlAuiNotebook( parent, id, pos, size, style ), - m_resultWindow( 0 ), - m_varWindow( 0 ), - m_pkgVarWindow( 0 ), - m_stackWindow( 0 ), - m_paramWindow( 0 ), - m_messageWindow( 0 ) +ctlTabWindow::ctlTabWindow(wxWindow *parent, wxWindowID id, const wxPoint &pos, + const wxSize &size, long style, const wxString &name) + : ctlAuiNotebook(parent, id, pos, size, style), + m_resultWindow(NULL), + m_varWindow(NULL), + m_pkgVarWindow(NULL), + m_stackWindow(NULL), + m_paramWindow(NULL), + m_messageWindow(NULL) { wxWindowBase::SetFont(settings->GetSystemFont()); m_tabMap = new wsTabHash(); } -void ctlTabWindow::selectTab( wxWindowID id ) +void ctlTabWindow::SelectTab(wxWindowID id) { - wsTabHash::iterator result = m_tabMap->find( id ); + wsTabHash::iterator result = m_tabMap->find(id); - if( result != m_tabMap->end()) + if (result != m_tabMap->end()) { - SetSelection( result->second ); + SetSelection(result->second); } } //////////////////////////////////////////////////////////////////////////////// -// getResultWindow() +// GetResultWindow() // // This function returns a pointer to our child result window (m_resultWindow) // and creates that window when we first need it. // - -ctlResultGrid *ctlTabWindow::getResultWindow( void ) +ctlResultGrid *ctlTabWindow::GetResultWindow() { - if( m_resultWindow == 0 ) + if (m_resultWindow == 0) { // We don't have a result window yet - go ahead and create one - m_resultWindow = new ctlResultGrid( this, -1 ); - AddPage( m_resultWindow, _( "Results" ), true ); + m_resultWindow = new ctlResultGrid(this, -1); + AddPage(m_resultWindow, _("Results"), true); } - return( m_resultWindow ); + return m_resultWindow; } //////////////////////////////////////////////////////////////////////////////// -// getVarWindow() +// GetVarWindow() // // This function returns a pointer to our child 'local-variables' window // (m_varWindow) and creates that window when we first need it. // - -ctlVarWindow *ctlTabWindow::getVarWindow( bool create ) +ctlVarWindow *ctlTabWindow::GetVarWindow(bool _create) { - if(( m_varWindow == NULL ) && create ) + if ((m_varWindow == NULL) && _create) { // We don't have a variable window yet - go ahead and create one (*m_tabMap)[ID_VARGRID] = GetPageCount(); - m_varWindow = new ctlVarWindow( this, ID_VARGRID ); - AddPage( m_varWindow, _( "Local Variables" ), true ); + m_varWindow = new ctlVarWindow(this, ID_VARGRID); + AddPage(m_varWindow, _("Local Variables"), true); } - return( m_varWindow ); + return m_varWindow; } //////////////////////////////////////////////////////////////////////////////// -// getPkgVarWindow() +// GetPkgVarWindow() // // This function returns a pointer to our child 'package-variables' window // (m_varWindow) and creates that window when we first need it. // - -ctlVarWindow *ctlTabWindow::getPkgVarWindow( bool create ) +ctlVarWindow *ctlTabWindow::GetPkgVarWindow(bool create) { - if(( m_pkgVarWindow == NULL ) && create ) + if ((m_pkgVarWindow == NULL) && create) { // We don't have a variable window yet - go ahead and create one (*m_tabMap)[ID_PKGVARGRID] = GetPageCount(); - m_pkgVarWindow = new ctlVarWindow( this, ID_PKGVARGRID ); - AddPage( m_pkgVarWindow, _( "Package Variables" ), true ); + m_pkgVarWindow = new ctlVarWindow(this, ID_PKGVARGRID); + AddPage(m_pkgVarWindow, _("Package Variables"), true); } - return( m_pkgVarWindow ); + return m_pkgVarWindow; } //////////////////////////////////////////////////////////////////////////////// -// getParamWindow() +// GetParamWindow() // // This function returns a pointer to our child 'parameters' window // (m_paramWindow) and creates that window when we first need it. // - -ctlVarWindow *ctlTabWindow::getParamWindow( bool create ) +ctlVarWindow *ctlTabWindow::GetParamWindow(bool create) { - if(( m_paramWindow == NULL ) && create ) + if ((m_paramWindow == NULL) && create) { // We don't have a variable window yet - go ahead and create one (*m_tabMap)[ID_PARAMGRID] = GetPageCount(); - m_paramWindow = new ctlVarWindow( this, ID_PARAMGRID ); - AddPage( m_paramWindow, _( "Parameters" ), true ); + m_paramWindow = new ctlVarWindow(this, ID_PARAMGRID); + AddPage(m_paramWindow, _("Parameters"), true); } - return( m_paramWindow ); + return m_paramWindow; } //////////////////////////////////////////////////////////////////////////////// -// getMessageWindow() +// GetMessageWindow() // // This function returns a pointer to our child 'messages' window // (m_messageWindow) and creates that window when we first need it. // - -ctlMessageWindow *ctlTabWindow::getMessageWindow( void ) +ctlMessageWindow *ctlTabWindow::GetMessageWindow() { - if( m_messageWindow == 0 ) + if (m_messageWindow == 0) { // We don't have a variable window yet - go ahead and create one (*m_tabMap)[ID_MSG_PAGE] = GetPageCount(); - m_messageWindow = new ctlMessageWindow( this, ID_MSG_PAGE ); - AddPage( m_messageWindow, _( "DBMS Messages" ), true ); + m_messageWindow = new ctlMessageWindow(this, ID_MSG_PAGE); + AddPage(m_messageWindow, _("DBMS Messages"), true); } - return( m_messageWindow ); + return m_messageWindow; } //////////////////////////////////////////////////////////////////////////////// -// getStackWindow() +// GetStackWindow() // // This function returns a pointer to our child stack-trace window // (m_stackWindow) and creates that window when we first need it. // - -ctlStackWindow *ctlTabWindow::getStackWindow( ) +ctlStackWindow *ctlTabWindow::GetStackWindow() { - if( m_stackWindow == 0 ) + if (m_stackWindow == 0) { // We don't have a stack-trace window yet - go ahead and create one - m_stackWindow = new ctlStackWindow( this, -1 ); - AddPage( m_stackWindow, _( "Stack" ), true ); + m_stackWindow = new ctlStackWindow(this, -1); + AddPage(m_stackWindow, _("Stack"), true); } - return( m_stackWindow ); + return m_stackWindow; } diff --git a/pgadmin/debugger/ctlVarWindow.cpp b/pgadmin/debugger/ctlVarWindow.cpp index 155bc31..8bb309f 100644 --- a/pgadmin/debugger/ctlVarWindow.cpp +++ b/pgadmin/debugger/ctlVarWindow.cpp @@ -18,66 +18,64 @@ // App headers #include "debugger/ctlVarWindow.h" -IMPLEMENT_CLASS( ctlVarWindow, wxGrid ) +IMPLEMENT_CLASS(ctlVarWindow, wxGrid) //////////////////////////////////////////////////////////////////////////////// // ctlVarWindow constructor // // Initialize the grid control and clear it out.... // - -ctlVarWindow::ctlVarWindow( wxWindow *parent, wxWindowID id ) - : wxGrid( parent, id ), - m_cells( NULL ), - m_nameFont( GetDefaultCellFont()) +ctlVarWindow::ctlVarWindow(wxWindow *parent, wxWindowID id) + : wxGrid(parent, id), + m_cells(NULL), + m_nameFont(GetDefaultCellFont()) { wxWindowBase::SetFont(settings->GetSystemFont()); // Create the grid control - CreateGrid( 0, 0 ); - SetRowLabelSize( 0 ); // Turn off the row labels + CreateGrid(0, 0); + SetRowLabelSize(0); // Turn off the row labels // Set up three columns: name, value, and data type - AppendCols( 3 ); - SetColLabelValue( COL_NAME, _( "Name" )); - SetColLabelValue( COL_TYPE, _( "Type" )); - SetColLabelValue( COL_VALUE, _( "Value" )); + AppendCols(3); + SetColLabelValue(COL_NAME, _("Name")); + SetColLabelValue(COL_TYPE, _("Type")); + SetColLabelValue(COL_VALUE, _("Value")); - EnableDragGridSize( true ); + EnableDragGridSize(true); // EDB wants to hide certain PL variables. To do that, we // keep a hash of hidden names and a hash of hidden types... - m_hiddenNames.insert( wxT( "found" )); - m_hiddenNames.insert( wxT( "rowcount" )); - m_hiddenNames.insert( wxT( "sqlcode" )); - m_hiddenNames.insert( wxT( "sqlerrm" )); - m_hiddenNames.insert( wxT( "_found" )); - m_hiddenNames.insert( wxT( "_rowcount" )); - m_hiddenNames.insert( wxT( "sqlstate" )); + m_hiddenNames.insert(wxT("found")); + m_hiddenNames.insert(wxT("rowcount")); + m_hiddenNames.insert(wxT("sqlcode")); + m_hiddenNames.insert(wxT("sqlerrm")); + m_hiddenNames.insert(wxT("_found")); + m_hiddenNames.insert(wxT("_rowcount")); + m_hiddenNames.insert(wxT("sqlstate")); - m_hiddenTypes.insert( wxT( "refcursor" )); + m_hiddenTypes.insert(wxT("refcursor")); } //////////////////////////////////////////////////////////////////////////////// -// addVar() +// AddVar() // // Adds (or updates) the given variable in the 'local variables' window. If // we find a variabled named 'name' in the window, we simply update the value, // otherwise, we create a new entry in the grid // - -void ctlVarWindow::addVar( wxString name, wxString value, wxString type, bool readOnly ) +void ctlVarWindow::AddVar(wxString name, wxString value, wxString type, bool readOnly) { // If this is a 'hidden' variable, just ignore it - if( m_hiddenNames.find( name ) != m_hiddenNames.end()) + if (m_hiddenNames.find(name) != m_hiddenNames.end()) return; - if( m_hiddenTypes.find( type ) != m_hiddenTypes.end()) + if (m_hiddenTypes.find(type) != m_hiddenTypes.end()) return; - if( m_cells == NULL ) + if (m_cells == NULL) { // This is the first variable we're adding to this grid, // layout the grid and set the column headers. @@ -86,11 +84,11 @@ void ctlVarWindow::addVar( wxString name, wxString value, wxString type, bool re } // Try to find an existing grid cell for this variable... - wxString key( name ); + wxString key(name); - wsCellHash::iterator cell = m_cells->find( key ); + wsCellHash::iterator cell = m_cells->find(key); - if( cell == m_cells->end()) + if (cell == m_cells->end()) { // Can't find this variable in the grid, go ahead and add it @@ -100,19 +98,19 @@ void ctlVarWindow::addVar( wxString name, wxString value, wxString type, bool re newCell.m_type = type; newCell.m_value = value; - AppendRows( 1 ); + AppendRows(1); - SetRowLabelValue( newCell.m_row, key ); + SetRowLabelValue(newCell.m_row, key); - SetCellValue( newCell.m_row, COL_NAME, key ); - SetCellValue( newCell.m_row, COL_TYPE, type ); - SetCellValue( newCell.m_row, COL_VALUE, value ); + SetCellValue(newCell.m_row, COL_NAME, key); + SetCellValue(newCell.m_row, COL_TYPE, type); + SetCellValue(newCell.m_row, COL_VALUE, value); - SetCellFont( newCell.m_row, COL_NAME, m_nameFont ); + SetCellFont(newCell.m_row, COL_NAME, m_nameFont); - SetReadOnly( newCell.m_row, COL_NAME, true ); - SetReadOnly( newCell.m_row, COL_TYPE, true ); - SetReadOnly( newCell.m_row, COL_VALUE, readOnly ); + SetReadOnly(newCell.m_row, COL_NAME, true); + SetReadOnly(newCell.m_row, COL_TYPE, true); + SetReadOnly(newCell.m_row, COL_VALUE, readOnly); (*m_cells)[key] = newCell; } @@ -123,23 +121,23 @@ void ctlVarWindow::addVar( wxString name, wxString value, wxString type, bool re cell->second.m_value = value; - if( GetCellValue( cell->second.m_row, COL_VALUE ).IsSameAs( value )) - SetCellTextColour( cell->second.m_row, COL_VALUE, *wxBLACK ); + if (GetCellValue(cell->second.m_row, COL_VALUE).IsSameAs(value)) + SetCellTextColour(cell->second.m_row, COL_VALUE, *wxBLACK); else - SetCellTextColour( cell->second.m_row, COL_VALUE, *wxRED ); + SetCellTextColour(cell->second.m_row, COL_VALUE, *wxRED); - SetCellValue( cell->second.m_row, COL_VALUE, value ); + SetCellValue(cell->second.m_row, COL_VALUE, value); // FIXME: why is this part conditional? // FIXME: why do we need this code? can the type ever change? - if( GetCellValue( cell->second.m_row, COL_TYPE) == wxT( "" )) + if (GetCellValue(cell->second.m_row, COL_TYPE) == wxT("")) { - SetCellValue( cell->second.m_row, COL_TYPE, type ); + SetCellValue(cell->second.m_row, COL_TYPE, type); } } - // AutoSizeColumns( false ); + // AutoSizeColumns(false); } //////////////////////////////////////////////////////////////////////////////// @@ -148,30 +146,37 @@ void ctlVarWindow::addVar( wxString name, wxString value, wxString type, bool re // Removes the given variable from the 'local variables' window. // -void ctlVarWindow::delVar( wxString name ) +void ctlVarWindow::DelVar(wxString name) { - if( name.IsEmpty()) + if (name.IsEmpty()) { delete m_cells; m_cells = NULL; - if( GetNumberRows()) - DeleteRows( 0, GetNumberRows()); + if (GetNumberRows()) + DeleteRows(0, GetNumberRows()); } else { - + for (int row = 0; row < GetNumberRows(); row++) + { + if (GetCellValue(row, COL_NAME) == name) + { + DeleteRows(row, 1); + break; + } + } } } -wxString ctlVarWindow::getVarName( int row ) +wxString ctlVarWindow::GetVarName(int row) { - return( GetCellValue( row, COL_NAME )); + return(GetCellValue(row, COL_NAME)); } -wxString ctlVarWindow::getVarValue( int row ) +wxString ctlVarWindow::GetVarValue(int row) { - return( GetCellValue( row, COL_VALUE )); + return(GetCellValue(row, COL_VALUE)); } diff --git a/pgadmin/debugger/dbgController.cpp b/pgadmin/debugger/dbgController.cpp new file mode 100644 index 0000000..4025c94 --- /dev/null +++ b/pgadmin/debugger/dbgController.cpp @@ -0,0 +1,819 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2013, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// dbgController.cpp - debugger controller +// - Flow and data controller for the debugger +// +////////////////////////////////////////////////////////////////////////// + +#include "pgAdmin3.h" + +// wxWindows headers +#include + +#include "ctl/ctlAuiNotebook.h" +#include "db/pgConn.h" +#include "db/pgQueryThread.h" +#include "db/pgQueryResultEvent.h" +#include "schema/pgObject.h" +#include "schema/pgTrigger.h" +#include "debugger/dbgConst.h" +#include "debugger/dbgBreakPoint.h" +#include "debugger/dbgController.h" +#include "debugger/dbgModel.h" +#include "debugger/ctlStackWindow.h" +#include "debugger/ctlMessageWindow.h" +#include "debugger/ctlTabWindow.h" +#include "debugger/frmDebugger.h" +#include "debugger/dlgDirectDbg.h" + +#include "utils/pgDefs.h" + +#include + +const wxString dbgController::ms_cmdDebugSPLV1( + wxT("SELECT edb_oid_debug(%ld, %ld)")); +const wxString dbgController::ms_cmdDebugSPLV2( + wxT("SELECT edb_oid_debug(%ld)")); + +const wxString dbgController::ms_cmdDebugPLPGSQLV1( + wxT("SELECT plpgsql_oid_debug(%ld, %ld)")); +const wxString dbgController::ms_cmdDebugPLPGSQLV2( + wxT("SELECT plpgsql_oid_debug(%ld)")); + +const wxString dbgController::ms_cmdAttachToPort( + wxT("SELECT * FROM pldbg_attach_to_port(%s)")); +const wxString dbgController::ms_cmdWaitForBreakpointV1( + wxT("SELECT\n") + wxT(" p.pkg AS pkg, p.func AS func, p.targetName AS targetName,\n") + wxT(" p.linenumber AS linenumber, pldbg_get_source($1::INTEGER, p.pkg, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func AS s.pkg = p.pkg) AS args\n") + wxT("FROM pldbg_wait_for_breakpoint($1::INTEGER) p")); +const wxString dbgController::ms_cmdWaitForBreakpointV2( + wxT("SELECT\n") + wxT(" p.func AS func, p.targetName AS targetName, \n") + wxT(" p.linenumber AS linenumber,\n") + wxT(" pldbg_get_source($1::INTEGER, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func) AS args\n") + wxT("FROM pldbg_wait_for_breakpoint($1::INTEGER) p")); + +const wxString dbgController::ms_cmdGetVars( + wxT("SELECT\n") + wxT(" name, varClass, value,\n") + wxT(" pg_catalog.format_type(dtype, NULL) as dtype, isconst\n") + wxT("FROM pldbg_get_variables(%s) ORDER BY varClass")); +const wxString dbgController::ms_cmdGetStack( + wxT("SELECT * FROM pldbg_get_stack(%s) ORDER BY level")); +const wxString dbgController::ms_cmdGetBreakpoints( + wxT("SELECT * FROM pldbg_get_breakpoints(%s)")); + +const wxString dbgController::ms_cmdStepOverV1( + wxT("SELECT\n") + wxT(" p.pkg AS pkg, p.func AS func, p.targetName AS targetName,\n") + wxT(" p.linenumber AS linenumber, pldbg_get_source($1::INTEGER, p.pkg, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func AS s.pkg = p.pkg) AS args\n") + wxT("FROM pldbg_step_over($1::INTEGER) p")); +const wxString dbgController::ms_cmdStepOverV2( + wxT("SELECT\n") + wxT(" p.func, p.targetName, p.linenumber,\n") + wxT(" pldbg_get_source($1::INTEGER, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func) AS args\n") + wxT("FROM pldbg_step_over($1::INTEGER) p")); +const wxString dbgController::ms_cmdStepIntoV1( + wxT("SELECT\n") + wxT(" p.pkg AS pkg, p.func AS func, p.targetName AS targetName,\n") + wxT(" p.linenumber AS linenumber, pldbg_get_source($1::INTEGER, p.pkg, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func AS s.pkg = p.pkg) AS args\n") + wxT("FROM pldbg_step_into($1::INTEGER) p")); +const wxString dbgController::ms_cmdStepIntoV2( + wxT("SELECT\n") + wxT(" p.func, p.targetName, p.linenumber,\n") + wxT(" pldbg_get_source($1::INTEGER, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func) AS args\n") + wxT("FROM pldbg_step_into($1::INTEGER) p")); +const wxString dbgController::ms_cmdContinueV1( + wxT("SELECT\n") + wxT(" p.pkg AS pkg, p.func AS func, p.targetName AS targetName,\n") + wxT(" p.linenumber AS linenumber, pldbg_get_source($1::INTEGER, p.pkg, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func AS s.pkg = p.pkg) AS args\n") + wxT("FROM pldbg_continue($1::INTEGER) p")); +const wxString dbgController::ms_cmdContinueV2( + wxT("SELECT\n") + wxT(" p.func, p.targetName, p.linenumber,\n") + wxT(" pldbg_get_source($1::INTEGER, p.func) AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func) AS args\n") + wxT("FROM pldbg_continue($1::INTEGER) p")); + +const wxString dbgController::ms_cmdSetBreakpointV1( + wxT("SELECT * FROM pldbg_set_breakpoint(%s,%s,%s,%d)")); +const wxString dbgController::ms_cmdSetBreakpointV2( + wxT("SELECT * FROM pldbg_set_breakpoint(%s,%s,%d)")); + +const wxString dbgController::ms_cmdClearBreakpointV1( + wxT("SELECT * FROM pldbg_drop_breakpoint(%s,%s,%s,%d)")); +const wxString dbgController::ms_cmdClearBreakpointV2( + wxT("SELECT * FROM pldbg_drop_breakpoint(%s,%s,%d)")); + +const wxString dbgController::ms_cmdSelectFrameV1( + wxT("SELECT\n") + wxT(" p.pkg AS pkg, p.func AS func, p.targetName AS targetName,\n") + wxT(" p.linenumber AS linenumber,\n") + wxT(" CASE WHEN $2::INTEGER <> 0 THEN pldbg_get_source($1::INTEGER, p.func) ELSE '' END AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func AS s.pkg = p.pkg) AS args\n") + wxT("FROM pldbg_select_frame($1::INTEGER, $2::INTEGER) p")); +const wxString dbgController::ms_cmdSelectFrameV2( + wxT("SELECT\n") + wxT(" p.func AS func, p.targetName AS targetName, p.linenumber AS linenumber,\n") + wxT(" CASE WHEN $2::INTEGER <> 0 THEN pldbg_get_source($1::INTEGER, p.func) ELSE '' END AS src,\n") + wxT(" (SELECT\n") + wxT(" s.args\n") + wxT(" FROM pldbg_get_stack($1::INTEGER) s\n") + wxT(" WHERE s.func = p.func) AS args\n") + wxT("FROM pldbg_select_frame($1::INTEGER, $2::INTEGER) p")); + +const wxString dbgController::ms_cmdDepositValue( + wxT("SELECT * FROM pldbg_deposit_value(%s,%s,%d,%s)")); +const wxString dbgController::ms_cmdAbortTarget( + wxT("SELECT * FROM pldbg_abort_target(%s)")); + +const wxString dbgController::ms_cmdAddBreakpointEDB( + wxT("SELECT * FROM pldbg_set_global_breakpoint(%s, %s, %s, -1, %s)")); +const wxString dbgController::ms_cmdAddBreakpointPG( + wxT("SELECT * FROM pldbg_set_global_breakpoint(%s, %s, -1, %s)")); + +const wxString dbgController::ms_cmdCreateListener( + wxT("SELECT * from pldbg_create_listener()")); +const wxString dbgController::ms_cmdWaitForTarget( + wxT("SELECT * FROM pldbg_wait_for_target(%s)")); + +dbgController::dbgController(frmMain *main, pgObject *_obj, bool _directDebugging) + : m_ver(DEBUGGER_UNKNOWN_API), m_sessionType(DBG_SESSION_TYPE_UNKNOWN), + m_terminated(false), m_frm(NULL), m_dbgConn(NULL), m_dbgThread(NULL), + m_execConnThread(NULL), m_model(NULL) +{ + // Create the connection for listening the debugger port and doing the + // debugging operations. + // i.e. + // - step in, step over, continue, fetch stack-frames, fetch variables, etc. + m_dbgConn = _obj->GetConnection()->Duplicate( + wxT("pgAdmin Debugger - Global Listener")); + + if (!m_dbgConn->IsAlive()) + { + wxLogError(_("Couldn't create the connection for the debugger")); + + delete m_dbgConn; + m_dbgConn = NULL; + + throw (std::runtime_error("Couldn't create the connection for the debugger")); + } + + m_dbgConn->ExecuteVoid(wxT("SET log_min_messages TO fatal")); + + OID target; + if (_obj->GetMetaType() == PGM_TRIGGER) + { + target = ((pgTrigger *)_obj)->GetFunctionOid(); + } + else + { + target = _obj->GetOid(); + } + + // Fetch the target information + try + { + m_model = new dbgModel((Oid)target, m_dbgConn); + } + catch (const std::runtime_error &error) + { + // Catching the runtime error thrown by dbgTargetInfo, so that we can + // delete the created connection object and then rethrow it + m_dbgConn->Close(); + delete m_dbgConn; + m_dbgConn = NULL; + + // Rethrow the error + throw error; + } + + if (m_model->GetTarget()->HasVariadic() && + m_model->GetTarget()->GetLanguage() == wxT("edbspl")) + { + wxLogError( + _("A 'edbspl' target having variadic argument is not supported by this version of pgAdmin debugger!")); + + throw (std::runtime_error( + "Unsupported target specified!")); + } + + // Fetch the debugger version + wxString strCntProxyInfo = m_dbgConn->ExecuteScalar( + wxT("SELECT COUNT(*) FROM pg_catalog.pg_proc p\n") + wxT(" LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid\n") + wxT("WHERE n.nspname = ANY(current_schemas(false))\n") + wxT(" AND p.proname = 'pldbg_get_proxy_info'"), false); + + if(!strCntProxyInfo.IsEmpty() && m_dbgConn->GetLastResultStatus() == PGRES_TUPLES_OK) + { + if (strCntProxyInfo == wxT("0")) + { + m_ver = DEBUGGER_V1_API; + } + wxString strVer = m_dbgConn->ExecuteScalar( + wxT("SELECT proxyapiver FROM pldbg_get_proxy_info()"), false); + + if (!strVer.IsEmpty() && m_dbgConn->GetLastResultStatus() == PGRES_TUPLES_OK) + { + long ver; + strVer.ToLong(&ver); + + switch (ver) + { + case DEBUGGER_V3_API: + case DEBUGGER_V2_API: + m_ver = (DebuggerApiVersion)ver; + break; + default: + wxLogError( + wxString::Format( + _("pgAdmin does not support this version of the debugger plugin (v:%ld)"), + ver)); + + m_dbgConn->Close(); + delete m_dbgConn; + m_dbgConn = NULL; + + delete m_model; + m_model = NULL; + + throw (std::runtime_error( + "Unsupported version of debugger plugin installed")); + break; + } + } + else + { + wxLogError( + wxString::Format( + _("Couldn't determine the debugger plugin version.\n%s"), + m_dbgConn->GetLastError().c_str())); + + m_dbgConn->Close(); + delete m_dbgConn; + m_dbgConn = NULL; + + delete m_model; + m_model = NULL; + + throw (std::runtime_error( + "Couldn't determine the debugger version (function execution error).")); + } + } + else + { + wxLogError( + wxString::Format( + _("Couldn't determine the debugger plugin version.\n%s"), + m_dbgConn->GetLastError().c_str())); + + m_dbgConn->Close(); + delete m_dbgConn; + m_dbgConn = NULL; + + delete m_model; + m_model = NULL; + + throw (std::runtime_error( + "Couldn't determine the debugger version (function check)")); + } + + // Store debugging type + if (_directDebugging) + { + m_sessionType = DBG_SESSION_TYPE_DIRECT; + m_frm = new frmDebugger( + main, this, wxString::Format( + _("Debugger - %s"), + m_model->GetTarget()->GetQualifiedName().c_str())); + } + else + { + m_sessionType = DBG_SESSION_TYPE_INCONTEXT; + m_frm = new frmDebugger( + main, this, _("Global Debugger")); + } + + if (!Start()) + m_frm->EnableToolsAndMenus(false); +} + + +dbgController::~dbgController() +{ + if (m_dbgConn) + { + m_dbgConn->Close(); + delete m_dbgConn; + m_dbgConn = NULL; + } + + if (m_model) + { + delete m_model; + m_model = NULL; + } +} + + +bool dbgController::Start() +{ + m_frm->EnableToolsAndMenus(false); + m_frm->Show(true); + + if (!m_dbgConn->IsAlive()) + { + if (!m_dbgConn->Reconnect()) + { + // Unable to re-establish the connection for debugger + return false; + } + } + m_dbgThread = new pgQueryThread( + m_dbgConn, this, &(dbgController::NoticeHandler), this); + + if (m_dbgThread->Create() != wxTHREAD_NO_ERROR) + { + delete m_dbgThread; + m_dbgThread = NULL; + + return false; + } + + m_dbgThread->Run(); + + wxTheApp->Yield(true); + + m_terminated = false; + + if (m_sessionType == DBG_SESSION_TYPE_DIRECT) + { + pgConn *conn = m_dbgConn->Duplicate( + wxT("pgAdmin Debugger - Target Invoker")); + + conn->ExecuteVoid(wxT("SET log_min_messages TO fatal")); + + + if (m_model->GetTarget()->RequireUserInput()) + { + dlgDirectDbg dlg(m_frm, this, conn); + + if (dlg.ShowModal() != wxID_OK) + { + // Close the connection for the debugging listener + m_dbgConn->Close(); + + // Close the connection for the debugging executor + conn->Close(); + delete conn; + conn = NULL; + + // Declare that execution has been cancelled + m_terminated = true; + m_frm->EnableToolsAndMenus(false); + + return false; + } + } + + m_execConnThread = new pgQueryThread( + conn, this, &(dbgController::NoticeHandler), this); + + if (m_execConnThread->Create() != wxTHREAD_NO_ERROR) + { + delete m_execConnThread; + m_execConnThread = NULL; + + conn->Close(); + delete conn; + conn = NULL; + + Stop(); + + return false; + } + return ExecuteTarget(); + } + else if (m_sessionType == DBG_SESSION_TYPE_INCONTEXT) + { + // TODO:: Ask for the target session and different targets + dbgBreakPointList &breakpoints = m_model->GetBreakPoints(); + + WX_CLEAR_ARRAY(breakpoints); + + dbgTargetInfo *target = m_model->GetTarget(); + breakpoints.Append(new dbgBreakPoint( + wxString::Format(wxT("%ld"), target->GetOid()), + wxString::Format(wxT("%ld"), target->GetPkgOid()))); + + m_dbgThread->AddQuery(ms_cmdCreateListener, NULL, (long)RESULT_ID_LISTENER_CREATED); + } + + return true; +} + + +bool dbgController::ExecuteTarget() +{ + dbgTargetInfo *target = m_model->GetTarget(); + wxString strDebugCmdPkgInitializer, strDebugCmdTarget; + + if (m_ver <= DEBUGGER_V2_API) + { + if (target->GetLanguage() == wxT("edbspl")) + { + strDebugCmdPkgInitializer = wxString::Format( + ms_cmdDebugSPLV1, target->GetPkgInitOid(), target->GetPkgOid()); + strDebugCmdTarget = wxString::Format( + ms_cmdDebugSPLV1, target->GetOid(), target->GetPkgOid()); + } + else + { + strDebugCmdPkgInitializer = wxString::Format( + ms_cmdDebugPLPGSQLV1, target->GetPkgInitOid(), target->GetPkgOid()); + strDebugCmdTarget = wxString::Format( + ms_cmdDebugPLPGSQLV1, target->GetOid(), target->GetPkgOid()); + } + } + else + { + if (target->GetLanguage() == wxT("edbspl")) + { + strDebugCmdPkgInitializer = wxString::Format( + ms_cmdDebugSPLV2, target->GetPkgInitOid()); + strDebugCmdTarget = wxString::Format(ms_cmdDebugSPLV2, target->GetOid()); + } + else + { + strDebugCmdPkgInitializer = wxString::Format( + ms_cmdDebugPLPGSQLV2, target->GetPkgInitOid()); + strDebugCmdTarget = wxString::Format(ms_cmdDebugPLPGSQLV2, target->GetOid()); + } + } + if (target->DebugPackageConstructor()) + { + m_execConnThread->AddQuery(strDebugCmdPkgInitializer); + } + m_execConnThread->AddQuery(strDebugCmdTarget); + + if (target->AddForExecution(m_execConnThread)) + { + // Start the execution thread + m_execConnThread->Run(); + } + else + { + wxLogError(_("Couldn't determine how to execute the selected target.")); + + Stop(); + + return false; + } + return true; +} + + +void dbgController::NoticeHandler(void *_arg, const char *_msg) +{ + dbgController *controller = (dbgController *)_arg; + + if (controller->m_terminated) + return; + + wxMBConv *conv = controller->m_dbgConn->GetConv(); + + wxString strMsg = wxString(_msg, *conv); + + if (strMsg.EndsWith(wxT("\n"))) + { + strMsg.RemoveLast(); + } + + if (strMsg.Find(wxT("PLDBGBREAK")) != wxNOT_FOUND) + { + // NOTICE: PLDBGBREAK 1 + wxStringTokenizer tokens(strMsg, wxT(":\n")); + + tokens.GetNextToken(); // NOTICE: + tokens.GetNextToken(); // PLDBGBREAK + + controller->m_model->GetPort() = tokens.GetNextToken(); // + + wxCommandEvent btnEvt(wxEVT_COMMAND_BUTTON_CLICKED, MENU_ID_START_DEBUGGING); + controller->AddPendingEvent(btnEvt); + } + else + { + wxCommandEvent btnEvt(wxEVT_COMMAND_BUTTON_CLICKED, MENU_ID_NOTICE_RECEIVED); + + btnEvt.SetString(strMsg); + controller->AddPendingEvent(btnEvt); + } +} + + +// Debugging actions (called from the frmDebugger) +void dbgController::ClearBreakpoint(int _lineNo) +{ + if (m_terminated) + return; + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdClearBreakpointV1, m_model->GetSession().c_str(), + m_model->GetDisplayedPackage().c_str(), + m_model->GetDisplayedFunction().c_str(), _lineNo + 1), + NULL, (long) RESULT_ID_NEW_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdClearBreakpointV2, m_model->GetSession().c_str(), + m_model->GetDisplayedFunction().c_str(), _lineNo + 1), + NULL, RESULT_ID_NEW_BREAKPOINT); + } +} + + +void dbgController::SetBreakpoint(int _lineNo) +{ + if (m_terminated) + return; + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdSetBreakpointV1, m_model->GetSession().c_str(), + m_model->GetDisplayedPackage().c_str(), + m_model->GetDisplayedFunction().c_str(), _lineNo + 1), + NULL, RESULT_ID_NEW_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdSetBreakpointV2, m_model->GetSession().c_str(), + m_model->GetDisplayedFunction().c_str(), _lineNo + 1), + NULL, RESULT_ID_NEW_BREAKPOINT); + } +} + + +void dbgController::Countinue() +{ + if (m_terminated) + return; + + pgParamsArray *params = new pgParamsArray; + params->Add(new pgParam(PGOID_TYPE_INT4, &(m_model->GetSession()))); + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery(ms_cmdContinueV1, params, RESULT_ID_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery(ms_cmdContinueV2, params, RESULT_ID_BREAKPOINT); + } + + // Do not allow any action at this time + m_frm->EnableToolsAndMenus(false); +} + + +void dbgController::StepOver() +{ + if (m_terminated) + return; + + pgParamsArray *params = new pgParamsArray; + params->Add(new pgParam(PGOID_TYPE_INT4, &(m_model->GetSession()))); + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery(ms_cmdStepOverV1, params, RESULT_ID_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery(ms_cmdStepOverV2, params, RESULT_ID_BREAKPOINT); + } + + // Do not allow any action at this time + m_frm->EnableToolsAndMenus(false); +} + + +void dbgController::StepInto() +{ + if (m_terminated) + return; + + pgParamsArray *params = new pgParamsArray; + params->Add(new pgParam(PGOID_TYPE_INT4, &(m_model->GetSession()))); + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery(ms_cmdStepIntoV1, params, RESULT_ID_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery(ms_cmdStepIntoV2, params, RESULT_ID_BREAKPOINT); + } + + // Do not allow any action at this time + m_frm->EnableToolsAndMenus(false); +} + + +bool dbgController::Stop() +{ + if (m_terminated) + return true; + + m_frm->EnableToolsAndMenus(false); + + switch(m_sessionType) + { + case DBG_SESSION_TYPE_DIRECT: + { + // We must send the abort target command to the executor + // session + if (m_execConnThread) + { + if (m_execConnThread->IsRunning()) + { + if (m_dbgConn->GetStatus() != CONNECTION_BAD) + { + pgConn *conn = m_dbgThread->GetConn(); + + pgSet *set = conn->ExecuteSet( + wxString::Format(ms_cmdAbortTarget, m_model->GetSession().c_str())); + + if (set) + delete set; + + wxTheApp->Yield(true); + + // The execution thread will be null, because the + // target will complete with an error, and it will + // result in the target-complete event. And, that will + // release this thread. + while (m_execConnThread != NULL) + { + wxMilliSleep(10); + } + } + else + { + m_execConnThread->CancelExecution(); + wxTheApp->Yield(true); + m_execConnThread->Wait(); + } + } + + if (m_execConnThread) + { + // We also need to deallocate the momory for the connection + pgConn *conn = m_execConnThread->GetConn(); + + delete m_execConnThread; + m_execConnThread = NULL; + + conn->Close(); + delete conn; + conn = NULL; + } + } + } + break; + } + + if (m_dbgThread) + { + if (m_dbgThread->IsRunning()) + { + m_dbgThread->CancelExecution(); + m_dbgThread->Wait(); + } + + delete m_dbgThread; + m_dbgThread = NULL; + } + + // Disconnect the debugger connection + m_dbgConn->Close(); + m_terminated = true; + + return true; +} + + +void dbgController::DepositValue(const wxString &_name, const wxString &_val) +{ + if (m_terminated) + return; + + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdDepositValue, m_model->GetSession().c_str(), + m_dbgConn->qtDbString(_name).c_str(), -1, + m_dbgConn->qtDbString(_val).c_str()), + NULL, RESULT_ID_DEPOSIT_VALUE); +} + + +void dbgController::SelectFrame(int _frameNo) +{ + if (m_terminated) + return; + + wxString strFrame = wxString::Format(wxT("%d"), _frameNo); + pgParamsArray *params = new pgParamsArray; + + params->Add(new pgParam(PGOID_TYPE_INT4, &(m_model->GetSession()))); + params->Add(new pgParam(PGOID_TYPE_INT4, &strFrame)); + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery(ms_cmdSelectFrameV1, params, RESULT_ID_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery(ms_cmdSelectFrameV2, params, RESULT_ID_BREAKPOINT); + } +} + +void dbgController::UpdateBreakpoints() +{ + if (m_terminated) + return; + + m_dbgThread->AddQuery( + wxString::Format(ms_cmdGetBreakpoints, m_model->GetSession().c_str()), + NULL, RESULT_ID_GET_BREAKPOINTS); +} + + +// Closing Debugger +void dbgController::CloseDebugger() +{ + if (Stop()) + { + if (m_dbgConn) + { + delete m_dbgConn; + + m_dbgConn = NULL; + } + } +} + +dbgTargetInfo *dbgController::GetTargetInfo() +{ + return m_model->GetTarget(); +} diff --git a/pgadmin/debugger/dbgDbResult.cpp b/pgadmin/debugger/dbgDbResult.cpp deleted file mode 100644 index 2843ba4..0000000 --- a/pgadmin/debugger/dbgDbResult.cpp +++ /dev/null @@ -1,21 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgDbResult.cpp - debugger -// -////////////////////////////////////////////////////////////////////////// - -#include "pgAdmin3.h" - -// wxWindows headers -#include - -// App headers -#include "debugger/dbgDbResult.h" - -DEFINE_EVENT_TYPE( dbgDbResult ) - diff --git a/pgadmin/debugger/dbgEvents.cpp b/pgadmin/debugger/dbgEvents.cpp new file mode 100644 index 0000000..ef9bc48 --- /dev/null +++ b/pgadmin/debugger/dbgEvents.cpp @@ -0,0 +1,703 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2013, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// dbgEvents.cpp - debugger controller events +// - This files contains the functions related to the event handling of the +// debugger controller. +// +////////////////////////////////////////////////////////////////////////// + +#include "pgAdmin3.h" + +// wxWindows headers +#include +#include + +#include "ctl/ctlAuiNotebook.h" +#include "db/pgConn.h" +#include "db/pgQueryThread.h" +#include "db/pgQueryResultEvent.h" +#include "schema/pgObject.h" +#include "debugger/dbgConst.h" +#include "debugger/dbgBreakPoint.h" +#include "debugger/dbgController.h" +#include "debugger/dbgModel.h" +#include "debugger/ctlStackWindow.h" +#include "debugger/ctlMessageWindow.h" +#include "debugger/ctlTabWindow.h" +#include "debugger/frmDebugger.h" +#include "debugger/dlgDirectDbg.h" +#include "utils/pgDefs.h" + +BEGIN_EVENT_TABLE(dbgController, wxEvtHandler) + EVT_BUTTON(MENU_ID_NOTICE_RECEIVED, dbgController::OnNoticeReceived) + EVT_BUTTON(MENU_ID_START_DEBUGGING, dbgController::OnStartDebugging) + + EVT_PGQUERYRESULT(RESULT_ID_DIRECT_TARGET_COMPLETE, dbgController::ResultTargetComplete) + EVT_PGQUERYRESULT(RESULT_ID_ATTACH_TO_PORT, dbgController::ResultPortAttach) + EVT_PGQUERYRESULT(RESULT_ID_LISTENER_CREATED, dbgController::ResultListenerCreated) + + EVT_PGQUERYRESULT(RESULT_ID_TARGET_READY, dbgController::ResultTargetReady) + + EVT_PGQUERYRESULT(RESULT_ID_DEL_BREAKPOINT, dbgController::ResultDeletedBreakpoint) + + EVT_PGQUERYRESULT(RESULT_ID_BREAKPOINT, dbgController::ResultBreakpoint) + EVT_PGQUERYRESULT(RESULT_ID_NEW_BREAKPOINT, dbgController::ResultNewBreakpoint) + EVT_PGQUERYRESULT(RESULT_ID_NEW_BREAKPOINT_WAIT, dbgController::ResultNewBreakpointWait) + + EVT_PGQUERYRESULT(RESULT_ID_GET_VARS, dbgController::ResultVarList) + EVT_PGQUERYRESULT(RESULT_ID_GET_STACK, dbgController::ResultStack) + EVT_PGQUERYRESULT(RESULT_ID_GET_BREAKPOINTS, dbgController::ResultBreakpoints) + + EVT_PGQUERYRESULT(RESULT_ID_DEPOSIT_VALUE, dbgController::ResultDepositValue) +END_EVENT_TABLE() + + +// Event functions +void dbgController::OnNoticeReceived(wxCommandEvent &_ev) +{ + m_frm->GetMessageWindow()->AppendText(_ev.GetString()); + m_frm->GetTabWindow()->SelectTab(ID_MSG_PAGE); +} + + +void dbgController::OnStartDebugging(wxCommandEvent &_ev) +{ + m_dbgThread->AddQuery( + wxString::Format(ms_cmdAttachToPort, m_model->GetPort().c_str()), + NULL, RESULT_ID_ATTACH_TO_PORT); +} + + +void dbgController::ResultTargetComplete(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + m_frm->EnableToolsAndMenus(false); + m_frm->SetStatusText(_("Debugging Completed")); + m_frm->CloseProgressBar(); + + wxTheApp->Yield(true); + + m_terminated = true; + + switch(qry->ReturnCode()) + { + case pgQueryResultEvent::PGQ_CONN_LOST: + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Execution of the debugging target terminated due to connection lost.\n"), + m_execConnThread->GetId())); + break; + // This is very unlikely, unless we made mistake creating the query + case pgQueryResultEvent::PGQ_STRING_INVALID: + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Execution of the debugging target terminated due to empty query string.\n"), + m_execConnThread->GetId())); + break; + case pgQueryResultEvent::PGQ_ERROR_PREPARE_CALLABLE: + case pgQueryResultEvent::PGQ_ERROR_EXECUTE_CALLABLE: + case pgQueryResultEvent::PGQ_ERROR_SEND_QUERY: + case pgQueryResultEvent::PGQ_ERROR_CONSUME_INPUT: + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Execution of the debugging target terminated due to execution error (%d).\n"), + m_execConnThread->GetId(), qry->ReturnCode())); + break; + case pgQueryResultEvent::PGQ_EXECUTION_CANCELLED: + m_frm->SetStatusText(_("Debugging Cancelled")); + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Execution of the debugging target cancelled.\n"), + m_execConnThread->GetId())); + break; + case pgQueryResultEvent::PGQ_RESULT_ERROR: + { + wxString strErr = qry->GetErrorMessage(); + + m_frm->SetStatusText(_("Execution completed with an error.")); + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Function/Procedure terminated with an error.\n%s"), + m_execConnThread->GetId(), strErr.c_str())); + + m_frm->GetMessageWindow()->AppendText(qry->GetMessage()); + m_frm->GetMessageWindow()->SetFocus(); + } + + break; + case PGRES_FATAL_ERROR: + case PGRES_NONFATAL_ERROR: + case PGRES_BAD_RESPONSE: + { + wxString strErr = qry->GetErrorMessage(); + m_frm->SetStatusText(_("Execution completed with an error.")); + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Function/Procedure terminated with an error.\n%s"), + m_execConnThread->GetId(), strErr.c_str())); + + m_frm->GetMessageWindow()->AppendText(qry->GetMessage()); + m_frm->GetMessageWindow()->SetFocus(); + } + + break; + case PGRES_TUPLES_OK: + { + m_frm->SetStatusText(_("Execution completed.")); + pgSet *set = qry->ResultSet(); + m_frm->GetMessageWindow()->AppendText(set->GetCommandStatus()); + + m_frm->GetResultWindow()->FillResult(set); + } + + break; + default: + m_frm->SetStatusText(_("Execution completed.")); + wxLogInfo( + wxString::Format( + _("Debugger(%ld): Execution of the debugging function/procedure completed\n"), + m_execConnThread->GetId())); + + m_frm->GetMessageWindow()->AppendText(qry->GetMessage()); + m_frm->GetMessageWindow()->SetFocus(); + + break; + + } + + if (m_execConnThread->IsRunning()) + { + m_execConnThread->CancelExecution(); + } + m_execConnThread->Wait(); + + pgConn *execConn = m_execConnThread->GetConn(); + delete m_execConnThread; + + m_execConnThread = NULL; + execConn->Close(); + delete execConn; + + pgQueryThread *oldDebugThread = m_dbgThread; + m_dbgThread = NULL; + + oldDebugThread->CancelExecution(); + oldDebugThread->Wait(); + + delete oldDebugThread; + + // We won't keep the connection open, we will open it only when required + m_dbgConn->Close(); + + m_frm->EnableToolsAndMenus(false); +} + + +void dbgController::ResultPortAttach(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Error attaching the proxy session."))) + { + return; + } + + + if (qry->ReturnCode() != PGRES_TUPLES_OK) + { + Stop(); + + return; + } + + m_frm->EnableToolsAndMenus(false); + + wxString strSession = qry->ResultSet()->GetVal(0); + m_model->GetSession() = strSession; + + pgParamsArray *params = new pgParamsArray; + + params->Add(new pgParam(PGOID_TYPE_INT4, &strSession)); + + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery(ms_cmdWaitForBreakpointV1, params, RESULT_ID_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery(ms_cmdWaitForBreakpointV2, params, RESULT_ID_BREAKPOINT); + } + + qry->Release(); +} + + +bool dbgController::HandleQuery(pgBatchQuery *_qry, const wxString &_err) +{ + if (!_qry) + return false; + + switch(_qry->ReturnCode()) + { + case pgQueryResultEvent::PGQ_CONN_LOST: + // This is very unlikely, unless we made mistake creating the query + case pgQueryResultEvent::PGQ_STRING_INVALID: + case PGRES_EMPTY_QUERY: + case pgQueryResultEvent::PGQ_ERROR_PREPARE_CALLABLE: + case pgQueryResultEvent::PGQ_ERROR_EXECUTE_CALLABLE: + case pgQueryResultEvent::PGQ_ERROR_SEND_QUERY: + case pgQueryResultEvent::PGQ_ERROR_CONSUME_INPUT: + case pgQueryResultEvent::PGQ_RESULT_ERROR: + case PGRES_BAD_RESPONSE: + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + break; + + // This is unlikely, we do not generate an event, when execution is + // cancelled + case pgQueryResultEvent::PGQ_EXECUTION_CANCELLED: + break; + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + return true; + // Hmm - where did it come from? + // We never call a function, which results into these results + // Anyway - we will return true as we have the result + case PGRES_COPY_IN: + case PGRES_COPY_OUT: + return true; + } + + wxTheApp->Yield(true); + + if (!m_dbgConn->IsAlive()) + { + wxMessageBox( + _("Connection to the database server lost, we can not debug furthe!"), + _("Connection Lost"), wxICON_ERROR | wxOK); + } + else + { + wxString strErr; + if (!_err.IsEmpty()) + strErr = _err + wxT("\n\n"); + strErr += _qry->GetErrorMessage(); + + wxMessageBox(strErr, _("Execution Error"), wxICON_ERROR | wxOK); + + wxLogQuietError(strErr); + } + + Stop(); + m_terminated = true; + m_frm->EnableToolsAndMenus(false); + + m_frm->SetStatusText(_("Debugging aborted...")); + + return false; +} + + +void dbgController::ResultBreakpoint(pgQueryResultEvent &_ev) +{ + if (m_frm) + m_frm->CloseProgressBar(); + + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, wxEmptyString)) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + wxString func, + pkg = wxT("0"); + + pgSet *set = qry->ResultSet(); + + if (set->HasColumn(wxT("pkg"))) + { + pkg = set->GetVal(wxT("pkg")); + } + func = set->GetVal(wxT("func")); + + m_model->GetFocusedPackage() = pkg; + m_model->GetFocusedFunction() = func; + + int lineNo = (int)(set->GetLong(wxT("linenumber")) - 1); + + if (lineNo < 0) + lineNo = -1; + else + lineNo -= 1; + m_model->GetCurrLineNo() = lineNo; + + wxString strSource = set->GetVal(wxT("src")); + + if (strSource.IsEmpty()) + strSource = _(""); + + dbgCachedStack src(pkg, func, set->GetVal(wxT("targetname")), + set->GetVal(wxT("args")), strSource); + + m_model->AddSource(func, src); + m_frm->DisplaySource(src); + + m_dbgThread->AddQuery( + wxString::Format(ms_cmdGetStack, m_model->GetSession().c_str()), + NULL, RESULT_ID_GET_STACK); + + m_frm->EnableToolsAndMenus(true); + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultVarList(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Error fetching the variables."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + ctlVarWindow *paramWin = NULL, *pkgVarWin = NULL, *varWin = NULL; + pgSet *set = qry->ResultSet(); + + while (!set->Eof()) + { + switch(set->GetVal(wxT("varclass"))[0]) + { + case 'A': + { + if (paramWin == NULL) + paramWin = m_frm->GetParamWindow(true); + + paramWin->AddVar( + set->GetVal(wxT("name")), + set->GetVal(wxT("value")), + set->GetVal(wxT("dtype")), + set->GetBool(wxT("isconst"))); + + break; + } + case 'P': + { + if (pkgVarWin == NULL) + pkgVarWin = m_frm->GetPkgVarWindow(true); + + pkgVarWin->AddVar( + set->GetVal(wxT("name")), + set->GetVal(wxT("value")), + set->GetVal(wxT("dtype")), + set->GetBool(wxT("isconst"))); + + break; + } + default: + { + if (varWin == NULL) + varWin = m_frm->GetVarWindow(true); + + varWin->AddVar( + set->GetVal(wxT("name")), + set->GetVal(wxT("value")), + set->GetVal(wxT("dtype")), + set->GetBool(wxT("isconst"))); + + break; + } + } + set->MoveNext(); + } + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultStack(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Error fetching the call stacks."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + pgSet *set = qry->ResultSet(); + + wxArrayString stackArr; + ctlStackWindow *stackWin = m_frm->GetStackWindow(); + + // Fetched the stack, now fetch the break-points + m_dbgThread->AddQuery( + wxString::Format(ms_cmdGetBreakpoints, m_model->GetSession().c_str()), + NULL, RESULT_ID_GET_BREAKPOINTS); + + stackWin->ClearStack(); + + while (!set->Eof()) + { + // The result set contains one tuple per frame: + // package, function, linenumber, args + stackArr.Add( + wxString::Format( + wxT("%s(%s)@%s"), + set->GetVal(wxT("targetname")).c_str(), + set->GetVal(wxT("args")).c_str(), + set->GetVal(wxT("linenumber")).c_str())); + set->MoveNext(); + } + stackWin->SetStack(stackArr); + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultBreakpoints(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Error fetching the break-points."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + pgSet *set = qry->ResultSet(); + int lineCol = set->ColNumber(wxT("linenumber")); + + m_dbgThread->AddQuery( + wxString::Format(ms_cmdGetVars, m_model->GetSession().c_str()), + NULL, RESULT_ID_GET_VARS); + + m_frm->ClearBreakpointMarkers(); + + while (!set->Eof()) + { + // The result set contains one tuple per breakpoint: + // packageOID, functionOID, linenumber + m_frm->MarkBreakpoint((int)(set->GetLong(lineCol) - 1)); + set->MoveNext(); + } + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultNewBreakpoint(pgQueryResultEvent &_ev) +{ + // We will wait for the last new break-point, which will be handled in + // dbgController::ResultNewBreakpointWait function + if (!HandleQuery(_ev.GetQuery(), _("Could not create the break-point."))) + return; + + // Release the result-set + _ev.GetQuery()->Release(); +} + + +void dbgController::ResultNewBreakpointWait(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Could not create the break-point."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + pgSet *set = _ev.GetQuery()->ResultSet(); + m_frm->EnableToolsAndMenus(false); + + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdWaitForTarget, m_model->GetSession().c_str()), + NULL, RESULT_ID_TARGET_READY); + + m_frm->LaunchWaitingDialog(); + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultDeletedBreakpoint(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Could not drop the break-point."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + pgSet *set = qry->ResultSet(); + + if (set->GetBool(0)) + { + m_frm->SetStatusText(_("Breakpoint dropped")); + } + } + + // Release the result-set + qry->Release(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// ResultDepositValue() +// +// This event handler is called when the proxy completes a 'deposit' operation +// (in response to an earlier call to pldbg_deposit_value()). +// +// We schedule a refresh of our variable window(s) for the next idle period. +// +void dbgController::ResultDepositValue(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, _("Could not deposit the new value."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + pgSet *set = qry->ResultSet(); + + if (set->GetBool(0)) + { + m_frm->SetStatusText(_("Value changed")); + + m_dbgThread->AddQuery( + wxString::Format(ms_cmdGetVars, m_model->GetSession().c_str()), + NULL, RESULT_ID_GET_VARS); + } + else + { + wxLogError(_("Could not deposit the new value.")); + } + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultListenerCreated(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + if (!HandleQuery(qry, + _("Could not create the proxy listener for the in-direct debugging."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + pgSet *set = qry->ResultSet(); + + // We now have a global listener and a session handle. Grab the session + // handle (we'll need it for just about everything else). + m_model->GetSession() = set->GetVal(0); + + // Now create any global breakpoints that the user requested. We start + // by asking the server to resolve the breakpoint name into an OID (or, + // a pair of OID's if the target is defined in a package). As each + // (targetInof) result arrives, we add a break-point at the resulting + // OID. + dbgBreakPointList breakpoints = m_model->GetBreakPoints(); + + unsigned int idx = 1; + long resultId; + + for (dbgBreakPointList::Node *node = breakpoints.GetFirst(); node; + node = node->GetNext(), ++idx) + { + dbgBreakPoint *breakpoint = node->GetData(); + + if(idx < breakpoints.GetCount()) + { + resultId = RESULT_ID_NEW_BREAKPOINT; + } + else + { + resultId = RESULT_ID_NEW_BREAKPOINT_WAIT; + } + + /* + * In EnterpriseDB versions <= 9.1 the + * pldbg_set_global_breakpoint function took five arguments, + * the 2nd argument being the package's OID, if any. Starting + * with 9.2, the package OID argument is gone, and the function + * takes four arguments like the community version has always + * done. + */ + if (m_dbgConn->GetIsEdb() && !m_dbgConn->BackendMinimumVersion(9, 2)) + { + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdAddBreakpointEDB, m_model->GetSession().c_str(), + breakpoint->GetFunctionOid().c_str(), + breakpoint->GetPackageOid().c_str(), + m_model->GetTargetPid().c_str()), + NULL, resultId); + } + else + { + m_dbgThread->AddQuery( + wxString::Format( + ms_cmdAddBreakpointPG, m_model->GetSession().c_str(), + breakpoint->GetFunctionOid().c_str(), + m_model->GetTargetPid().c_str()), + NULL, resultId); + } + } + } + + // Release the result-set + qry->Release(); +} + + +void dbgController::ResultTargetReady(pgQueryResultEvent &_ev) +{ + pgBatchQuery *qry = _ev.GetQuery(); + + m_frm->CloseProgressBar(); + + if (!HandleQuery(qry, + _("Error fetching information related the target."))) + return; + + if (qry->ReturnCode() == PGRES_TUPLES_OK) + { + m_frm->EnableToolsAndMenus(false); + + pgParamsArray *params = new pgParamsArray; + + params->Add(new pgParam(PGOID_TYPE_INT4, &(m_model->GetSession()))); + if (m_ver <= DEBUGGER_V2_API) + { + m_dbgThread->AddQuery(ms_cmdWaitForBreakpointV1, params, RESULT_ID_BREAKPOINT); + } + else + { + m_dbgThread->AddQuery(ms_cmdWaitForBreakpointV2, params, RESULT_ID_BREAKPOINT); + } + } + + // Release the result-set + qry->Release(); +} diff --git a/pgadmin/debugger/dbgModel.cpp b/pgadmin/debugger/dbgModel.cpp new file mode 100644 index 0000000..5cec1c1 --- /dev/null +++ b/pgadmin/debugger/dbgModel.cpp @@ -0,0 +1,63 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2013, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// dbgModel.cpp - debugger model +// - It contains the data and information related the debugging session +// +////////////////////////////////////////////////////////////////////////// + +#include "pgAdmin3.h" + +// wxWindows headers +#include + +#include "db/pgConn.h" +#include "db/pgQueryThread.h" +#include "db/pgQueryResultEvent.h" +#include "debugger/dbgModel.h" + + +dbgModel::dbgModel(Oid _target, pgConn *_conn) + : m_target(NULL), m_currLineNo(-1), m_targetPid(wxT("NULL")) +{ + m_target = new dbgTargetInfo(_target, _conn); +} + + +bool dbgModel::GetSource(const wxString &_funcOid, dbgCachedStack *_cached) +{ + dbgSourceHash::iterator match = m_sourceMap.find(_funcOid); + + if (match == m_sourceMap.end()) + return false; + else + { + if (_cached) + { + *_cached = match->second; + } + + return true; + } +} + + +void dbgModel::ClearCachedSource() +{ + m_sourceMap.clear(); + + // Put a dummy entry for invalid function OID to the cache. This is + // displayed at least for inline code blocks, as we currently have no way + // to fetch the source for those + m_sourceMap[wxT("0")] = dbgCachedStack(wxT("0"), wxT("0"), wxT(""), wxT(""), _("")); +} + + +void dbgModel::AddSource(const wxString &_funcOid, const dbgCachedStack &_source) +{ + m_sourceMap[_funcOid] = _source; +} diff --git a/pgadmin/debugger/dbgPgConn.cpp b/pgadmin/debugger/dbgPgConn.cpp deleted file mode 100644 index dd666db..0000000 --- a/pgadmin/debugger/dbgPgConn.cpp +++ /dev/null @@ -1,544 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgPgConn.cpp - debugger -// -////////////////////////////////////////////////////////////////////////// - -#include "pgAdmin3.h" - -// wxWindows headers -#include - -// App headers -#include "debugger/dbgPgConn.h" -#include "utils/sysLogger.h" - -#include - -#ifdef __WXMSW__ -#include -typedef u_long in_addr_t; -#else -#include -#include -#include - -#ifndef INADDR_NONE -#define INADDR_NONE (-1) -#endif - -#endif - -//////////////////////////////////////////////////////////////////////////////// -// dbgPgConn constructors -// -// A dbgPgConn object encapsulates the connection to a PostgreSQL server. This -// class is a wrapper around a Pgconn that provides a convenient constructor -// and a few member functions. -// -// The constructor creates a new thread and connects to the specified server - -dbgPgConn::dbgPgConn(frmDebugger *frame, const wxString &server, const wxString &database, const wxString &userName, const wxString &password, const wxString &port, int sslmode, const wxString &applicationname) - : m_frame(frame) -{ - Init( server, database, userName, password, port, sslmode, applicationname, true ); -} - -dbgPgConn::dbgPgConn(frmDebugger *frame, const dbgConnProp &props, bool startThread ) - : m_frame(frame) -{ - Init( props.m_host, props.m_database, props.m_userName, props.m_password, props.m_port, props.m_sslMode, props.m_applicationName, startThread ); -} - -void dbgPgConn::Init( const wxString &server, const wxString &database, const wxString &username, const wxString &password, const wxString &port, int sslmode, const wxString &applicationname, bool startThread ) -{ - bool utfConnectString = false; - bool libcConnectString = false; - - m_pgConn = NULL; - m_majorVersion = 0; - m_minorVersion = 0; - m_debuggerApiVersion = DEBUGGER_UNKNOWN_API; - m_isEdb = false; - - if( startThread ) - m_workerThread = new dbgPgThread( *this ); - else - m_workerThread = NULL; - - wxString msg; - wxString delimiter; - - // To keep the user interface thread responsive while we're waiting for the - // PostgreSQL server, we create a separate thread to interact with the - // database - the worker thread sleeps until the GUI thread signals that it - // has some work to do. When the result set arrives from the server, the - // worker thread creates a DBResult event and posts it to the GUI thread's - // event queue. - - if( m_workerThread ) - { - m_workerThread->Create(); - m_workerThread->Run(); - } - - // Figure out the hostname/IP address - struct hostent *host; - in_addr_t addr; - wxString hostip, hostname; - -#ifdef __WXMSW__ - struct in_addr ipaddr; -#else - unsigned long ipaddr; -#endif - -#ifndef __WXMSW__ - if (!(server.IsEmpty() || server.StartsWith(wxT("/")))) - { -#endif - addr = inet_addr(server.ToAscii()); - if (addr == INADDR_NONE) // szServer is not an IP address - { - host = gethostbyname(server.ToAscii()); - if (host == NULL) - { - wxLogError(__("Could not resolve hostname %s"), server.c_str()); - return; - } - - memcpy(&(ipaddr), host->h_addr, host->h_length); - hostip = wxString::FromAscii(inet_ntoa(*((struct in_addr *) host->h_addr_list[0]))); - hostname = server; - } - else - { - hostip = server; - hostname = server; - } -#ifndef __WXMSW__ - } - else - hostname = server; -#endif - - // Build up a connection string - wxString connectParams; - - if(hostname.Length()) - { - connectParams.Append(wxT( "host=")); - connectParams.Append(hostname); - - msg += delimiter + server; - delimiter = _(":"); - } - - if (hostip.Length()) - { - connectParams.Append(wxT(" hostaddr=")); - connectParams.Append(hostip); - } - - if(port.Length()) - { - connectParams += wxT(" port="); - connectParams += port; - - msg += delimiter + port; - delimiter = _(":"); - } - - - if(database.Length()) - { - connectParams.Append(wxT(" dbname=")); - connectParams.Append(qtConnString(database)); - - msg += delimiter + database; - delimiter = _(":"); - } - - if(username.Length()) - { - connectParams.Append(wxT(" user=")); - connectParams.Append(username ); - - msg += delimiter + username; - delimiter = _(":"); - } - - if(password.Length()) - { - connectParams.Append(wxT(" password=")); - connectParams.Append(password); - } - - switch (sslmode) - { - case 1: - connectParams.Append(wxT(" sslmode=require")); - break; - - case 2: - connectParams.Append(wxT(" sslmode=prefer")); - break; - - case 3: - connectParams.Append(wxT(" sslmode=allow")); - break; - - case 4: - connectParams.Append(wxT(" sslmode=disable")); - break; - - case 5: - connectParams.Append(wxT(" sslmode=verify-ca")); - break; - - case 6: - connectParams.Append(wxT(" sslmode=verify-full")); - break; - - default: - break; - } - - connectParams.Trim(true); - connectParams.Trim(false); - -#ifdef HAVE_CONNINFO_PARSE - if (!applicationname.IsEmpty()) - { - // Check connection string with application_name - char *errmsg; - wxString connectParams_with_applicationname = connectParams + wxT(" application_name='") + applicationname + wxT("'"); - if (PQconninfoParse(connectParams_with_applicationname.mb_str(wxConvUTF8), &errmsg)) - { - utfConnectString = true; - connectParams = connectParams_with_applicationname; - } - else if (PQconninfoParse(connectParams_with_applicationname.mb_str(wxConvLibc), &errmsg)) - { - libcConnectString = true; - connectParams = connectParams_with_applicationname; - } - } -#endif - - m_frame->getStatusBar()->SetStatusText( wxString::Format(_( "Connecting to %s" ), msg.c_str()), 1 ); - wxCharBuffer cstrUTF = connectParams.mb_str(wxConvUTF8); - wxCharBuffer cstrLibc = connectParams.mb_str(wxConvLibc); - - if (!libcConnectString) - m_pgConn = PQconnectdb(cstrUTF); - else - m_pgConn = PQconnectdb(cstrLibc); - - if (PQstatus(m_pgConn) != CONNECTION_OK) - { - PQfinish(m_pgConn); - m_pgConn = PQconnectdb(cstrLibc); - } - - if( PQstatus( m_pgConn ) == CONNECTION_OK ) - { - m_frame->getStatusBar()->SetStatusText( wxString::Format(_( "Connected to %s" ), msg.c_str()), 1 ); - PQsetClientEncoding( m_pgConn, "UNICODE" ); - } - else - { - throw( std::runtime_error( PQerrorMessage( m_pgConn ))); - } -} - -dbgPgConn::~dbgPgConn() -{ - Close(); -} - -//////////////////////////////////////////////////////////////////////////////// -// isConnected() -// -// Returns true if the connection to the PostgreSQL server is alive and well, -// otherwise, returns false. - -bool dbgPgConn::isConnected( void ) const -{ - if( m_pgConn && PQstatus( m_pgConn ) == CONNECTION_OK ) - return( true ); - else - return( false ); -} - -//////////////////////////////////////////////////////////////////////////////// -// getName() -// -// This function returns a user-friendly descriptive name for this connection. -// The result is "hostname/database". - -const wxString dbgPgConn::getName() const -{ - wxString result = wxString( PQhost( m_pgConn ), wxConvUTF8 ); - - result += wxT( "/" ); - result.Append( wxString( PQdb( m_pgConn ), wxConvUTF8 )); - - return( result ); - -} - -//////////////////////////////////////////////////////////////////////////////// -// getHost() -// -// Returns the name of the host (or the IP address) that we're connected to. - -const wxString dbgPgConn::getHost() const -{ - return( wxString( PQhost( m_pgConn ), wxConvUTF8 )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getDatabase() -// -// Returns the name of the database that we're connected to - -const wxString dbgPgConn::getDatabase() const -{ - return( wxString( PQdb( m_pgConn ), wxConvUTF8 )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getConnection() -// -// Returns the libpq connection handle for this dbgPgConn - -PGconn *dbgPgConn::getConnection() -{ - return( m_pgConn ); -} - -//////////////////////////////////////////////////////////////////////////////// -// startCommand() -// -// The GUI thread invokes startCommand() when the user asks us to execute a -// command. We pass off the real work to the worker thread. - -void dbgPgConn::startCommand( const wxString &command, wxEvtHandler *caller, wxEventType eventType, dbgPgParams *params ) -{ - wxLogSql(wxT("%s"), command.c_str()); - - m_workerThread->startCommand(command, caller, eventType, params); -} - -PGresult *dbgPgConn::waitForCommand( const wxString &command ) -{ - wxLogSql(wxT("%s"), command.c_str()); - - PGresult *result = PQexec( m_pgConn, command.mb_str( wxConvUTF8 )); - - return( result ); -} - -//////////////////////////////////////////////////////////////////////////////// -// setNoticeHandler() -// -// Register a NOTICE handler with the libpq library - libpq will invoke the -// given handler whenever a NOTICE arrives on this connection. libpq will -// pass 'arg' to the handler. 'handler' is typically a static function and -// 'arg' is often a pointer to an object. That lets you use a regular member -// function as a callback (because 'arg' is mapped into a 'this' pointer by the -// callback function). - -void dbgPgConn::setNoticeHandler( PQnoticeProcessor handler, void *arg ) -{ - PQnoticeProcessor p = NULL; - p = PQsetNoticeProcessor( m_pgConn, handler, arg ); -} - -void dbgPgConn::Close() -{ - // Attempt to cancel any ongoing query - Cancel(); - - // Wait a tenth of a second or so for things to sort themselves out. - // Otherwise on Windows things can get funky here ... - wxMilliSleep(100); - - if (m_workerThread) - { - m_workerThread->Die(); - m_workerThread->Wait(); - - delete m_workerThread; - m_workerThread = NULL; - } - - if (m_pgConn) - PQfinish(m_pgConn); - - m_pgConn = NULL; -} - -void dbgPgConn::Cancel() -{ - // Attempt to cancel any ongoing query - if (m_pgConn) - { - PGcancel *cancel = PQgetCancel(m_pgConn); - char errbuf[256]; - PQcancel(cancel, errbuf, sizeof(errbuf)); - PQfreeCancel(cancel); - } -} - -// Check the backend version -bool dbgPgConn::BackendMinimumVersion(int major, int minor) -{ - if (!m_majorVersion) - { - wxString version = GetVersionString(); - sscanf(version.ToAscii(), "%*s %d.%d", &m_majorVersion, &m_minorVersion); - m_isEdb = version.Upper().Matches(wxT("ENTERPRISEDB*")); - - // EnterpriseDB 8.3 beta 1 & 2 and possibly later actually have PostgreSQL 8.2 style - // catalogs. This is expected to change either before GA, but in the meantime we - // need to check the catalogue version in more detail, and if we don't see what looks - // like a 8.3 catalog, force the version number back to 8.2. Yuck. - if (m_isEdb && m_majorVersion == 8 && m_minorVersion == 3) - { - PGresult *res; - wxString result; - - res = waitForCommand(wxT( "SELECT count(*) FROM pg_attribute WHERE attname = 'proconfig' AND attrelid = 'pg_proc'::regclass")); - - if (PQresultStatus(res) == PGRES_TUPLES_OK) - { - // Retrieve the query result and return it. - result = wxString(PQgetvalue(res, 0, 0), wxConvUTF8); - - // Cleanup & exit - PQclear(res); - } - if (result == wxT("0")) - m_minorVersion = 2; - } - else - m_isGreenplum = version.Upper().Matches(wxT("*GREENPLUM DATABASE*")); - } - - return m_majorVersion > major || (m_majorVersion == major && m_minorVersion >= minor); -} - -// Check the EDB backend version -bool dbgPgConn::EdbMinimumVersion(int major, int minor) -{ - return BackendMinimumVersion(major, minor) && GetIsEdb(); -} - -// Get the debugger API version -DebuggerApiVersions dbgPgConn::DebuggerApiVersion() -{ - if (m_debuggerApiVersion > 0) - return m_debuggerApiVersion; - - // The v1 protocol didn't have pldbg_get_proxy_info() - wxString result; - - PGresult *res = waitForCommand(wxT( "SELECT count(*) FROM pg_proc WHERE proname = 'pldbg_get_proxy_info';")); - - if (PQresultStatus(res) == PGRES_TUPLES_OK) - { - // Retrieve the query result and return it. - if (wxString(PQgetvalue(res, 0, 0), wxConvUTF8) == wxT("0")) - { - PQclear(res); - m_debuggerApiVersion = DEBUGGER_V1_API; - return DEBUGGER_V1_API; - } - - PQclear(res); - } - else - { - wxLogError(wxT("%s"), wxString(PQerrorMessage(m_pgConn), wxConvUTF8).c_str()); - return DEBUGGER_UNKNOWN_API; - } - - // We have pldbg_get_proxy_info, so use it to get the API version - res = waitForCommand(wxT( "SELECT proxyapiver FROM pldbg_get_proxy_info();")); - - if (PQresultStatus(res) == PGRES_TUPLES_OK) - { - // Retrieve the query result and return it. - m_debuggerApiVersion = (DebuggerApiVersions)atoi(wxString(PQgetvalue(res, 0, 0), wxConvUTF8).ToAscii()); - PQclear(res); - } - else - { - wxLogError(wxT("%s"), wxString(PQerrorMessage(m_pgConn), wxConvUTF8).c_str()); - return DEBUGGER_UNKNOWN_API; - } - - return m_debuggerApiVersion; -} - - -wxString dbgPgConn::GetVersionString() -{ - PGresult *res; - wxString result; - - res = waitForCommand(wxT( "SELECT version();")); - - if (PQresultStatus(res) == PGRES_TUPLES_OK) - { - // Retrieve the query result and return it. - result = wxString(PQgetvalue(res, 0, 0), wxConvUTF8); - - // Cleanup & exit - PQclear(res); - } - - return result; -} - -bool dbgPgConn::GetIsEdb() -{ - // to retrieve edb flag - BackendMinimumVersion(0, 0); - return m_isEdb; -} - -bool dbgPgConn::GetIsGreenplum() -{ - // to retrieve edb flag - BackendMinimumVersion(0, 0); - return m_isGreenplum; -} - -wxString dbgPgConn::qtDbString(const wxString &value) -{ - wxString result = value; - - result.Replace(wxT("\\"), wxT("\\\\")); - result.Replace(wxT("'"), wxT("''")); - result.Append(wxT("'")); - - if (BackendMinimumVersion(8, 1)) - { - if (result.Contains(wxT("\\"))) - result.Prepend(wxT("E'")); - else - result.Prepend(wxT("'")); - } - else - result.Prepend(wxT("'")); - - return result; -} \ No newline at end of file diff --git a/pgadmin/debugger/dbgPgThread.cpp b/pgadmin/debugger/dbgPgThread.cpp deleted file mode 100644 index 9c80d8b..0000000 --- a/pgadmin/debugger/dbgPgThread.cpp +++ /dev/null @@ -1,363 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgPgThread.cpp - debugger -// -////////////////////////////////////////////////////////////////////////// - -#include "pgAdmin3.h" - -// wxWindows headers -#include -#include -#include -#include - -// App headers -#include "debugger/dbgPgThread.h" -#include "debugger/dbgDbResult.h" -#include "debugger/frmDebugger.h" -#include "debugger/dbgPgConn.h" - -#include - -WX_DEFINE_LIST( ThreadCommandList ); - -//////////////////////////////////////////////////////////////////////////////// -// dbgPgThread constructor -// -// A dbgPgThread object encapsulates a separate execution thread that interacts -// with a PostgreSQL server. We use a separate thread to keep the main thread -// (and thus the user interface) responsive while we're waiting for the server. - -dbgPgThread::dbgPgThread( dbgPgConn &owner ) - : wxThread(wxTHREAD_JOINABLE), - m_owner( owner ), - m_queueCounter(), - m_queueMutex(), - m_commandQueue(), - m_currentCommand( NULL ), - run( 0 ), - die( false ) -{ - conv = &wxConvLibc; -} - -//////////////////////////////////////////////////////////////////////////////// -// startCommand() -// -// This function is called by the GUI thread when the user decides to -// execute a command. To keep the GUI thread responsive, we use a -// separate thread to interact with the PostgreSQL server. This function -// wakes up the worker thread. - -void dbgPgThread::startCommand( const wxString &command, wxEvtHandler *caller, wxEventType eventType, dbgPgParams *params ) -{ - // Save the command text (and the event handler that we should - // notify on completion) in the command queue and then wake up the - // worker thread. The worker thread sleeps until we increment - // m_queueCounter - - m_queueMutex.Lock(); - m_commandQueue.Append( new dbgPgThreadCommand( command, caller, eventType, params )); - - wxLogInfo( wxT( "Queueing: %s" ), command.c_str()); - - m_queueMutex.Unlock(); - - m_queueCounter.Post(); -} - -//////////////////////////////////////////////////////////////////////////////// -// Die() -// -// Instruct the thread to kill itself. - -void dbgPgThread::Die() -{ - wxLogInfo( wxT( "Telling the query thread to die..." )); - die = true; - - m_queueCounter.Post(); -} - -//////////////////////////////////////////////////////////////////////////////// -// Entry() -// -// This is the entry point (and main loop) for the worker thread. It simply -// waits for a command from the GUI thread (see the startCommand() function) -// and then sends the command to the PostgreSQL server. This function also -// waits for a result set from the server - when the result set arrives, we -// send an event to the GUI thread. - -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; -} - -//////////////////////////////////////////////////////////////////////////////// -// noticeHandler() -// -// This function is invoked when a NOTICE is received from the PostgreSQL -// server. We watch for a specially-formatted NOTICE that the PL debugger -// raises when it reaches a breakpoint. - -void dbgPgThread::noticeHandler( void *arg, const char *message ) -{ - // Remove the last char from the message as it'll be a \n - wxString msg = wxString(message, wxConvUTF8); - - if (msg.EndsWith(wxT("\n"))) - msg.RemoveLast(); - - wxLogInfo(wxT("%s"), msg.c_str()); - - dbgPgThread *thread = (dbgPgThread *)arg; - wxEvtHandler *caller = thread->m_currentCommand->getCaller(); - - if( strstr( message, "PLDBGBREAK" )) - { - wxStringTokenizer tokens( wxString( message, wxConvUTF8 ), wxT( ":\n" )); - - wxString NOTICE = tokens.GetNextToken(); // NOTICE: - wxString PLDBGBREAK = tokens.GetNextToken(); // PLDBGBREAK: - wxString PORT = tokens.GetNextToken(); // port - - PGconn *conn = thread->m_owner.getConnection(); - - // Create a dbgConnProp object that contains the same information in a - // more convenient format - - dbgConnProp *debugProps = new dbgConnProp; - - debugProps->m_host = wxString( PQhost( conn ), wxConvUTF8 ); - debugProps->m_database = wxString( PQdb( conn ), wxConvUTF8 ); - debugProps->m_userName = wxString( PQuser( conn ), wxConvUTF8 ); - debugProps->m_debugPort = PORT; - debugProps->m_port = wxString( PQport( conn ), wxConvUTF8 ); - debugProps->m_password = wxString( PQpass( conn ), wxConvUTF8 ); - - wxCommandEvent buttonEvent( wxEVT_COMMAND_BUTTON_CLICKED, MENU_ID_SPAWN_DEBUGGER ); - buttonEvent.SetClientData((wxClientData *)debugProps ); - caller->AddPendingEvent( buttonEvent ); - } - else if( strstr( message, "INFO" )) - { - if( strstr( message, "CONTEXT:" ) == NULL ) - { - wxCommandEvent buttonEvent( wxEVT_COMMAND_BUTTON_CLICKED, MENU_ID_NOTICE_RECEIVED ); - - wxString strippedMessage( wxString( message, wxConvUTF8 )); - - strippedMessage.Replace( wxT( "INFO: " ), wxT( "" ), false ); - - buttonEvent.SetString( strippedMessage ); - caller->AddPendingEvent( buttonEvent ); - } - } - else - { - wxCommandEvent buttonEvent( wxEVT_COMMAND_BUTTON_CLICKED, MENU_ID_NOTICE_RECEIVED ); - - buttonEvent.SetString( wxString( message, wxConvUTF8 ) ); - caller->AddPendingEvent( buttonEvent ); - } -} - -dbgPgThreadCommand *dbgPgThread::getNextCommand() -{ - dbgPgThreadCommand *result; - - m_queueMutex.Lock(); - - wxLogInfo( wxT( "%d commands in queue" ), m_commandQueue.GetCount()); - - ThreadCommandList::Node *node = m_commandQueue.GetFirst(); - - result = node->GetData(); - - m_commandQueue.DeleteNode( node ); - - m_queueMutex.Unlock(); - - return( result ); -} diff --git a/pgadmin/debugger/dbgResultset.cpp b/pgadmin/debugger/dbgResultset.cpp deleted file mode 100644 index 9c5f231..0000000 --- a/pgadmin/debugger/dbgResultset.cpp +++ /dev/null @@ -1,157 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgResultset.cpp - debugger -// -////////////////////////////////////////////////////////////////////////// - -#include "pgAdmin3.h" - -// wxWindows headers -#include - -// App headers -#include "debugger/dbgResultset.h" - -//////////////////////////////////////////////////////////////////////////////// -// dbgResultset constructor -// -// A dbgResultset object encapsulates a result set produced by executing a -// database command. This class is a wrapper around a PGresult handle that -// provides a few convenient member functions. -// - -dbgResultset::dbgResultset( PGresult *handle ) - : m_handle( handle ) -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// getString() -// -// This function, given a column number and a row number, returns the value in -// that slot in the result set (in the form of a string). -// -// Notice that row defaults to 0, which is handy if you have a single-row result -// set. - -const wxString dbgResultset::getString( int column, int row ) -{ - return( wxString( PQgetvalue( m_handle, row, column ), wxConvUTF8 )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getString() -// -// This function, given a column name and a row number, returns the value in -// that slot in the result set (in the form of a string). -// -// Notice that row defaults to 0. - -const wxString dbgResultset::getString( const wxString &columnName, int row ) -{ - return( getString( PQfnumber( m_handle, columnName.mb_str( wxConvUTF8 )), row )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getLong() -// -// This function, given a column number and a row number, converts the value -// in that slot into a long and returns that long. -// -// Notice that row defaults to 0. - -long dbgResultset::getLong( int column, int row ) -{ - return( atoi( PQgetvalue( m_handle, row, column ))); -} - -//////////////////////////////////////////////////////////////////////////////// -// getLong() -// -// This function, given a column name and a row number, converts the value -// in that slot into a long and returns that long. -// -// Notice that row defaults to 0. - -long dbgResultset::getLong( const wxString &columnName, int row ) -{ - return( getLong( PQfnumber( m_handle, columnName.mb_str( wxConvUTF8 )), row )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getBool() -// -// This function, given a column number and a row number, converts the value -// in that slot into a bool. -// -// Notice that row defaults to 0. - -bool dbgResultset::getBool( int column, int row ) -{ - return( PQgetvalue( m_handle, row, column )[0] == 't' ? true : false ); -} - -//////////////////////////////////////////////////////////////////////////////// -// getBool() -// -// This function, given a column name and a row number, converts the value -// in that slot into a bool. -// -// Notice that row defaults to 0. - -bool dbgResultset::getBool( const wxString &columnName, int row ) -{ - return( getBool( PQfnumber( m_handle, columnName.mb_str( wxConvUTF8 )), row )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getCommandStatus() -// -// This function returns the command status contained in the result set. (The -// command status will be a value such as PGRES_TUPLES_OK or PGRES_FATAL_ERROR) - -ExecStatusType dbgResultset::getCommandStatus() -{ - return( PQresultStatus( m_handle )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getErrorMessage() -// -// This function returns the error message (if any) contained in the result set - -const wxString dbgResultset::getErrorMessage() -{ - return( wxString( PQresultErrorMessage( m_handle ), wxConvUTF8 )); -} - -//////////////////////////////////////////////////////////////////////////////// -// getRawErrorMessage() -// -// This function returns the error message (if any) contained in the result set -// -// Use this variant (as opposed to getErrorMessage()) when you need the error -// message in the same character encoding provided by the server - -const char *dbgResultset::getRawErrorMessage() -{ - return( PQresultErrorMessage( m_handle )); -} - -//////////////////////////////////////////////////////////////////////////////// -// columnExists() -// -// Check if a column exists in the resultset - -bool dbgResultset::columnExists(const wxString &columnName) -{ - if(PQfnumber(m_handle, columnName.mb_str(wxConvUTF8)) != -1) - return(true); - else - return(false); -} diff --git a/pgadmin/debugger/dbgTargetInfo.cpp b/pgadmin/debugger/dbgTargetInfo.cpp index 300cb82..9795b1f 100644 --- a/pgadmin/debugger/dbgTargetInfo.cpp +++ b/pgadmin/debugger/dbgTargetInfo.cpp @@ -14,33 +14,15 @@ // wxWindows headers #include -#include -#include // App headers #include "debugger/dbgTargetInfo.h" -#include "debugger/dbgResultset.h" -#include "debugger/dbgPgConn.h" +#include "debugger/dbgConst.h" +#include "utils/misc.h" +#include "utils/pgfeatures.h" #include -WX_DEFINE_OBJARRAY( wsArgInfoArray ); - -#define COL_TARGET_NAME "targetname" -#define COL_SCHEMA_NAME "nspname" -#define COL_LANGUAGE_NAME "lanname" -#define COL_ARG_NAMES "argnames" -#define COL_ARG_MODES "argmodes" -#define COL_ARG_TYPES "argtypenames" -#define COL_ARG_TYPEOIDS "argtypeoids" -#define COL_ARG_DEFVALS "argdefvals" -#define COL_IS_FUNCTION "isfunc" -#define COL_TARGET_OID "target" -#define COL_PACKAGE_OID "pkg" -#define COL_FQ_NAME "fqname" /* Fully qualified name */ -#define COL_RETURNS_SET "proretset" -#define COL_RETURN_TYPE "rettype" - //////////////////////////////////////////////////////////////////////////////// // dbgTargetInfo constructor // @@ -61,234 +43,594 @@ WX_DEFINE_OBJARRAY( wsArgInfoArray ); // This class offers a number of (inline) member functions that you can call // to extract the above information after it's been queried from the server. -dbgTargetInfo::dbgTargetInfo( const wxString &target, dbgPgConn *conn, char targetType ) +dbgTargetInfo::dbgTargetInfo(Oid _target, pgConn *_conn) + : m_args(NULL), m_inputParamCnt(0), m_hasVariadic(false) { - wxString query = - wxT("select t.*, ") - wxT(" pg_catalog.oidvectortypes( t.argtypes ) as argtypenames, t.argtypes as argtypeoids,") - wxT(" l.lanname, n.nspname, p.proretset, y.typname AS rettype") - wxT(" from") - wxT(" pldbg_get_target_info( '%s', '%c' ) t , pg_namespace n, pg_language l, pg_proc p, pg_type y") - wxT(" where") - wxT(" n.oid = t.schema and ") - wxT(" l.oid = t.targetlang and " ) - wxT(" p.oid = t.target and ") - wxT(" y.oid = t.returntype"); - - dbgResultset *result = new dbgResultset(conn->waitForCommand(wxString::Format(query, target.c_str(), targetType))); - - if(result->getCommandStatus() != PGRES_TUPLES_OK) - throw( std::runtime_error( result->getRawErrorMessage())); - - m_name = result->getString( wxString(COL_TARGET_NAME, wxConvUTF8)); - m_schema = result->getString( wxString(COL_SCHEMA_NAME, wxConvUTF8)); - m_language = result->getString( wxString(COL_LANGUAGE_NAME, wxConvUTF8)); - m_argNames = result->getString( wxString(COL_ARG_NAMES, wxConvUTF8)); - m_argModes = result->getString( wxString(COL_ARG_MODES, wxConvUTF8)); - m_argTypes = result->getString( wxString(COL_ARG_TYPES, wxConvUTF8)); - m_argTypeOids = result->getString( wxString(COL_ARG_TYPEOIDS, wxConvUTF8)); - - // get arg defvals if they exist - if (result->columnExists(wxString(COL_ARG_DEFVALS, wxConvUTF8))) - m_argDefVals = result->getString( wxString(COL_ARG_DEFVALS, wxConvUTF8)); - - if (result->columnExists(wxString(COL_IS_FUNCTION, wxConvUTF8))) - m_isFunction = result->getBool( wxString(COL_IS_FUNCTION, wxConvUTF8)); + wxMBConv *conv = _conn->GetConv(); + wxString targetQuery = + wxT("SELECT\n") + wxT(" p.proname AS name, l.lanname, p.proretset, p.prorettype, y.typname AS rettype,\n") + wxT(" CASE WHEN proallargtypes IS NOT NULL THEN\n") + wxT(" pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT\n") + wxT(" pg_catalog.format_type(p.proallargtypes[s.i], NULL)\n") + wxT(" FROM\n") + wxT(" pg_catalog.generate_series(0, pg_catalog.array_upper(\n") + wxT(" p.proallargtypes, 1)) AS s(i)), ',')\n") + wxT(" ELSE\n") + wxT(" pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT\n") + wxT(" pg_catalog.format_type(p.proargtypes[s.i], NULL)\n") + wxT(" FROM\n") + wxT(" pg_catalog.generate_series(0, pg_catalog.array_upper(\n") + wxT(" p.proargtypes, 1)) AS s(i)), ',')\n") + wxT(" END AS proargtypenames,\n") + wxT(" CASE WHEN proallargtypes IS NOT NULL THEN\n") + wxT(" pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT proallargtypes[s.i] FROM\n") + wxT(" pg_catalog.generate_series(0, pg_catalog.array_upper(proallargtypes, 1)) s(i)), ',')\n") + wxT(" ELSE\n") + wxT(" pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT proargtypes[s.i] FROM\n") + wxT(" pg_catalog.generate_series(0, pg_catalog.array_upper(proargtypes, 1)) s(i)), ',')\n") + wxT(" END AS proargtypes,\n") + wxT(" pg_catalog.array_to_string(p.proargnames, ',') AS proargnames,\n") + wxT(" pg_catalog.array_to_string(proargmodes, ',') AS proargmodes,\n"); + + if (_conn->GetIsEdb()) + { + targetQuery += + wxT(" CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg,\n") + wxT(" CASE WHEN n.nspparent <> 0 THEN n.nspname ELSE '' END AS pkgname,\n") + wxT(" CASE WHEN n.nspparent <> 0 THEN (SELECT oid FROM pg_proc WHERE pronamespace=n.oid AND proname='cons') ELSE 0 END AS pkgconsoid,\n") + wxT(" CASE WHEN n.nspparent <> 0 THEN g.oid ELSE n.oid END AS schema,\n") + wxT(" CASE WHEN n.nspparent <> 0 THEN g.nspname ELSE n.nspname END AS schemaname,\n") + wxT(" NOT (l.lanname = 'edbspl' AND protype = '1') AS isfunc,\n"); + } else - m_isFunction = true; - - m_oid = result->getLong( wxString(COL_TARGET_OID, wxConvUTF8)); + { + targetQuery += + wxT(" 0 AS pkg,\n") + wxT(" '' AS pkgname,\n") + wxT(" 0 AS pkgconsoid,\n") + wxT(" n.oid AS schema,\n") + wxT(" n.nspname AS schemaname,\n") + wxT(" true AS isfunc,\n"); + } + if (_conn->BackendMinimumVersion(8, 4)) + { + targetQuery += wxT(" pg_catalog.pg_get_function_identity_arguments(p.oid) AS signature,"); + } + else if (_conn->BackendMinimumVersion(8, 1)) + { + targetQuery += + wxT(" CASE\n") + wxT(" WHEN proallargtypes IS NOT NULL THEN pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT\n") + wxT(" CASE\n") + wxT(" WHEN p.proargmodes[s.i] = 'i' THEN ''\n") + wxT(" WHEN p.proargmodes[s.i] = 'o' THEN 'OUT '\n") + wxT(" WHEN p.proargmodes[s.i] = 'b' THEN 'INOUT '\n") + wxT(" WHEN p.proargmodes[s.i] = 'v' THEN 'VARIADIC '\n") + wxT(" END ||\n") + wxT(" CASE WHEN COALESCE(p.proargnames[s.i], '') = '' THEN ''\n") + wxT(" ELSE p.proargnames[s.i] || ' '\n") + wxT(" END ||\n") + wxT(" pg_catalog.format_type(p.proallargtypes[s.i], NULL)\n") + wxT(" FROM\n") + wxT(" pg_catalog.generate_series(1, pg_catalog.array_upper(p.proallargtypes, 1)) AS s(i)\n") + wxT(" WHERE p.proargmodes[s.i] != 't'\n") + wxT(" ), ', ')\n") + wxT(" ELSE\n") + wxT(" pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT\n") + wxT(" CASE\n") + wxT(" WHEN COALESCE(p.proargnames[s.i+1], '') = '' THEN ''\n") + wxT(" ELSE p.proargnames[s.i+1] || ' '\n") + wxT(" END ||\n") + wxT(" pg_catalog.format_type(p.proargtypes[s.i], NULL)\n") + wxT(" FROM\n") + wxT(" pg_catalog.generate_series(1, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n") + wxT(" ), ', ')\n") + wxT(" END AS signature,\n"); + } + else + { + targetQuery += wxT(" '' AS signature,"); + } - if (result->columnExists(wxString(COL_PACKAGE_OID, wxConvUTF8))) - m_pkgOid = result->getLong( wxString(COL_PACKAGE_OID, wxConvUTF8)); + if (_conn->HasFeature(FEATURE_FUNCTION_DEFAULTS)) + { + // EnterpriseDB 8.3R2 + if(!_conn->BackendMinimumVersion(8, 4)) + { + targetQuery += + wxT(" pg_catalog.array_to_string(ARRAY(\n") + wxT(" SELECT\n") + wxT(" CASE WHEN p.proargdefvals[x.j] != '-' THEN\n") + wxT(" pg_catalog.pg_get_expr(p.proargdefvals[x.j], 'pg_catalog.pg_class'::regclass, true)\n") + wxT(" ELSE '-' END\n") + wxT(" FROM\n") + wxT(" pg_catalog.generate_series(1, pg_catalog.array_upper(p.proargdefvals, 1)) AS x(j)\n") + wxT(" ), ',') AS proargdefaults,\n") + wxT(" CASE WHEN p.proargdefvals IS NULL THEN '0'\n") + wxT(" ELSE pg_catalog.array_upper(p.proargdefvals, 1)::text END AS pronargdefaults\n"); + } + else + { + targetQuery += + wxT(" pg_catalog.pg_get_expr(p.proargdefaults, 'pg_catalog.pg_class'::regclass, false) AS proargdefaults,\n") + wxT(" p.pronargdefaults\n"); + } + } else - m_pkgOid = 0; + { + targetQuery += + wxT(" '' AS proargdefaults, 0 AS pronargdefaults\n"); + } + targetQuery += + wxT("FROM\n") + wxT(" pg_catalog.pg_proc p\n") + wxT(" LEFT JOIN pg_catalog.pg_namespace n ON p.pronamespace = n.oid\n") + wxT(" LEFT JOIN pg_catalog.pg_language l ON p.prolang = l.oid\n") + wxT(" LEFT JOIN pg_catalog.pg_type y ON p.prorettype = y.oid\n"); + if(_conn->GetIsEdb()) + { + targetQuery += + wxT(" LEFT JOIN pg_catalog.pg_namespace g ON n.nspparent = g.oid\n"); + } + targetQuery += + wxString::Format(wxT("WHERE p.oid = %ld"), (long)_target); + + pgSet *set = _conn->ExecuteSet(targetQuery); + + if (conv == NULL) + { + conv = &wxConvLibc; + } + + if (!set || _conn->GetLastResultStatus() != PGRES_TUPLES_OK) + { + if (set) + delete set; + wxLogError(_("Could not fetch information about the debugger target.\n") + + _conn->GetLastError()); + + throw (std::runtime_error( + (const char *)(_conn->GetLastError().c_str()))); + } + + if (set->NumRows() == 0) + { + delete set; + + wxLogError(_("Can't find the debugging target")); + throw (std::runtime_error("Can't find target!")); + } + + m_oid = _target; + m_name = set->GetVal(wxT("name")); + m_schema = set->GetVal(wxT("schemaname")); + m_package = set->GetVal(wxT("pkgname")); + m_language = set->GetVal(wxT("lanname")); + m_returnType = set->GetVal(wxT("rettype")); + m_funcSignature = set->GetVal(wxT("signature")); + m_isFunction = set->GetBool(wxT("isfunc")); + m_returnsSet = set->GetBool(wxT("proretset")); + m_pkgOid = set->GetLong(wxT("pkg")); + m_pkgInitOid = set->GetLong(wxT("pkgconsoid")); + m_schemaOid = set->GetLong(wxT("schema")); + m_fqName = m_schema + wxT(".") + + (m_pkgOid == 0 ? wxT("") : (m_package + wxT("."))) + m_name; + + wxArrayString argModes, argNames, argTypes, argTypeOids, argDefVals, + argBaseTypes; + + // Fetch Argument Modes (if available) + if (!set->IsNull(set->ColNumber(wxT("proargmodes")))) + { + wxString tmp; + tmp = set->GetVal(wxT("proargmodes")); + + if (!tmp.IsEmpty()) + getArrayFromCommaSeparatedList(tmp, argModes); + } + // Fetch Argument Names (if available) + if (!set->IsNull(set->ColNumber(wxT("proargnames")))) + { + wxString tmp; + tmp = set->GetVal(wxT("proargnames")); + + if (!tmp.IsEmpty()) + getArrayFromCommaSeparatedList(tmp, argNames); + } + // Fetch Argument Type-Names (if available) + if (!set->IsNull(set->ColNumber(wxT("proargtypenames")))) + { + wxString tmp; + tmp = set->GetVal(wxT("proargtypenames")); - m_fqName = result->getString( wxString(COL_FQ_NAME, wxConvUTF8)); - m_returnsSet = result->getBool( wxString(COL_RETURNS_SET, wxConvUTF8)); - m_returnType = result->getString( wxString(COL_RETURN_TYPE, wxConvUTF8)); + if (!tmp.IsEmpty()) + getArrayFromCommaSeparatedList(tmp, argTypes); + } + // Fetch Argument Type-Names (if available) + if (!set->IsNull(set->ColNumber(wxT("proargtypes")))) + { + wxString tmp; + tmp = set->GetVal(wxT("proargtypes")); + if (!tmp.IsEmpty()) + getArrayFromCommaSeparatedList(tmp, argTypeOids); + } - // Parse out the argument types, names, and modes + size_t nArgDefs = (size_t)set->GetLong(wxT("pronargdefaults")); + // Fetch Argument Default Values (if available) + if (!set->IsNull(set->ColNumber(wxT("proargdefaults"))) && nArgDefs != 0) + { + wxString tmp; + tmp = set->GetVal(wxT("proargdefaults")); - // By creating a tokenizer with wxTOKEN_STRTOK and a delimiter string - // that contains ",{}", we can parse out PostgreSQL array strings like: - // {int, varchar, numeric} + if (!tmp.IsEmpty()) + getArrayFromCommaSeparatedList(tmp, argDefVals); + } - wxStringTokenizer names(m_argNames, wxT( ",{}" ), wxTOKEN_STRTOK); - wxStringTokenizer types(m_argTypes, wxT( ",{}" ), wxTOKEN_STRTOK); - wxStringTokenizer typeOids(m_argTypeOids, wxT( ",{}" ), wxTOKEN_STRTOK); - wxStringTokenizer modes(m_argModes, wxT( ",{}" ), wxTOKEN_STRTOK); - wxStringTokenizer defvals(m_argDefVals, wxT( ",{}" ), wxTOKEN_STRTOK); + wxString argName, argDefVal; + short argMode; + Oid argTypeOid; + size_t argCnt = argTypes.Count(); - // Create one wsArgInfo for each target argument + // This function/procedure does not take any arguments + if (argCnt == 0) + { + return; + } - m_argInCount = m_argOutCount = m_argInOutCount = 0; - int argCount = 0; + size_t idx = 0; + m_args = new pgDbgArgs(); - while( types.HasMoreTokens()) + for (; idx < argCnt; idx++) { - argCount++; + argTypeOid = (Oid)strtoul(argTypeOids[idx].mb_str(wxConvUTF8), 0, 10); + argDefVal = wxEmptyString; - wxString argName = names.GetNextToken(); - wxString defVal; + argName = wxEmptyString; + if (idx < argNames.Count()) + argName = argNames[idx]; - if( argName.IsEmpty()) - argName.Printf( wxT( "$%d" ), argCount ); + if (argName.IsEmpty()) + argName.Printf( wxT( "dbgParam%d" ), (idx + 1)); + + if (idx < argModes.Count()) + { + wxString tmp = argModes[idx]; + switch ((tmp.c_str())[0]) + { + case 'i': + argMode = pgParam::PG_PARAM_IN; + m_inputParamCnt++; + break; + case 'b': + m_inputParamCnt++; + argMode = pgParam::PG_PARAM_INOUT; + break; + case 'o': + argMode = pgParam::PG_PARAM_OUT; + break; + case 'v': + m_inputParamCnt++; + argMode = pgParam::PG_PARAM_VARIADIC; + m_hasVariadic = true; + break; + case 't': + continue; + default: + m_inputParamCnt++; + argMode = pgParam::PG_PARAM_IN; + break; + } + } + else + { + m_inputParamCnt++; + argMode = pgParam::PG_PARAM_IN; + } // In EDBAS 90, if an SPL-function has both an OUT-parameter // and a return value (which is not possible on PostgreSQL otherwise), // the return value is transformed into an extra OUT-parameter // named "_retval_" - if (argName == wxT("_retval_") && conn->EdbMinimumVersion(9, 0)) + if (argName == wxT("_retval_") && _conn->EdbMinimumVersion(9, 0)) { // this will be the return type for this object - m_returnType = types.GetNextToken(); - - // consume uniformly, mode will definitely be "OUT" - modes.GetNextToken(); + m_returnType = argTypes[idx]; - // ignore OID also.. - typeOids.GetNextToken(); continue; } - wsArgInfo argInfo( argName, types.GetNextToken(), modes.GetNextToken(), typeOids.GetNextToken()); - - if( argInfo.getMode() == wxT( "i" )) - m_argInCount++; - else if( argInfo.getMode() == wxT( "o" )) - m_argOutCount++; - else if( argInfo.getMode() == wxT( "b" )) - m_argInOutCount++; - - // see if this arg has a def value and add if so. If we see an empty - // string "", what should we do? Infact "" might be a valid default - // value in some cases, so for now store "" too. Note that we will - // store the "" only if this is stringlike type and nothing otherwise.. - defVal = (defvals.GetNextToken()).Strip ( wxString::both ); - if (argInfo.isValidDefVal(defVal)) - { - // remove starting/trailing quotes - defVal.Replace( wxT( "\"" ), wxT( "" )); - argInfo.setValue(defVal); - } - - m_argInfo.Add( argInfo ); + m_args->Add(new dbgArgInfo(argName, argTypes[idx], argTypeOid, argMode)); } - // Get the package initializer function OID. On 8.1 or below, this is - // assumed to be the same as the package OID. On 8.2, we have an API :-) - if (conn->EdbMinimumVersion(8, 2)) + if (m_args->GetCount() == 0) { - PGresult *res; + delete m_args; + m_args = NULL; - m_pkgInitOid = 0; - res = conn->waitForCommand( wxT( "SELECT pldbg_get_pkg_cons(") + NumToStr(m_pkgOid) + wxT(");")); + return; + } - if (PQresultStatus(res) == PGRES_TUPLES_OK) - { - // Retrieve the query result and return it. - m_pkgInitOid = StrToLong(wxString(PQgetvalue(res, 0, 0), wxConvUTF8)); + if (nArgDefs != 0) + { + argCnt = m_args->GetCount(); - // Cleanup & exit - PQclear(res); + // Set the default as the value for the argument + for (idx = argCnt - 1; idx >= 0; idx--) + { + dbgArgInfo *arg = (dbgArgInfo *)((*m_args)[idx]); + + if (arg->GetMode() == pgParam::PG_PARAM_INOUT || + arg->GetMode() == pgParam::PG_PARAM_IN) + { + nArgDefs--; + + if (argDefVals[nArgDefs] != wxT("-")) + { + arg->SetDefault(argDefVals[nArgDefs]); + } + + if (nArgDefs == 0) + { + break; + } + } } } - else if (conn->GetIsEdb()) - m_pkgInitOid = m_pkgOid; - else - m_pkgInitOid = 0; } //////////////////////////////////////////////////////////////////////////////// // operator[] // -// This operator function makes it easy to index into the m_argInfo[] array +// This operator function makes it easy to index into the m_args array // using concise syntax. - -wsArgInfo &dbgTargetInfo::operator[]( int index ) +dbgArgInfo *dbgTargetInfo::operator[](int index) { - return( m_argInfo[index] ); + if (m_args == NULL) + return (dbgArgInfo *)NULL; + + if (index < 0 || index >= m_args->GetCount()) + return (dbgArgInfo *)NULL; + + return (dbgArgInfo *)((*m_args)[index]); } -//////////////////////////////////////////////////////////////////////////////// -// wsArgInfo constructor -// -// A wsArgInfo object contains information about a function (or procedure) -// argument. Inside of each wsArgInfo object, we store the name of the argument, -// the argument type, and the argument mode (IN (i), OUT (o), or INOUT (b)). -// -// Once the user has had a chance to enter values for each of the IN and INOUT -// arguments, we store those values inside of the corresponding wsArgInfo objects - -wsArgInfo::wsArgInfo( const wxString &argName, const wxString &argType, const wxString &argMode, const wxString &argTypeOid) - : m_name( argName.Strip( wxString::both )), - m_type( argType.Strip( wxString::both )), - m_mode( argMode == wxT( "" ) ? wxT( "i" ) : argMode.Strip( wxString::both )), - m_value() +dbgArgInfo::dbgArgInfo(const wxString &_name, const wxString &_type, Oid _typeOid, + short _mode) + : m_name(_name), m_type(_type), m_typeOid(_typeOid), m_mode(_mode), + m_hasDefault(false), m_useDefault(false), m_null(NULL) { - long oid; - argTypeOid.ToLong(&oid); - m_typeOid = (Oid)oid; + if (!_type.EndsWith(wxT("[]"), &m_baseType)) + { + m_baseType = wxEmptyString; + } } -//////////////////////////////////////////////////////////////////////////////// -// quoteValue() -// -// This function will extract the argument value from the given wsArgInfo object -// and will wrap that value in single quotes (unless the value has already been -// quoted). If the argument value (entered by the user) is blank, we return NULL -// instead of a quoted string. -// -// NOTE: we quote all value regardless of type - it's perfectly valid to quote a -// numeric (or boolean) value in PostgreSQL -const wxString wsArgInfo::quoteValue() +pgParam *dbgArgInfo::GetParam(wxMBConv *_conv) { - if (m_value == wxT("")) - return (wxT("NULL")); - else if (m_value == wxT("''")) - return (wxT("''")); - else if (m_value == wxT("\\'\\'")) - return (wxT("'\\'\\''")); - else if (m_value[0] == '\'' ) - return (m_value); - else - return(wxString(wxT("'") + m_value + wxT("'"))); + return new pgParam(m_typeOid, + (m_null ? (wxString *)NULL : &m_val), + _conv, m_mode); } -//////////////////////////////////////////////////////////////////////////////// -// isValidDefVal() -// -// This function checks if the passed in defvalue is sane or not. If it is -// empty, nothing needs to be done. However if the backend returns "", then -// we need to entertain this as valid input only for stringlike types. Else -// we ignore. Obviously if defval is anything other than "", we need to -// honor it -// - -bool wsArgInfo::isValidDefVal(const wxString defValue) +bool dbgTargetInfo::AddForExecution(pgQueryThread *_thread) { - if (defValue.IsEmpty()) + wxASSERT(_thread != NULL); + + if (_thread == NULL) return false; - if (defValue != wxT("\"\"")) - return true; + pgConn *conn = _thread->GetConn(); + + pgParamsArray *params = NULL; + wxString strQuery; + bool useCallable = false; + + // Basically - we can call the function/target three way + // 1. If it is a edbspl procedure, we can use callable statement + // 2. If the database server is of type EnterpriseDB, and + // function/procedure is type 'edbspl', we should use the anonymous + // function block for: + // a. Version < 9.0 + // b. Package function/procedure + // 3. Otherwise, we should use the simple function call (except using EXEC + // for the procedure in 'edbspl' instead of using SELECT) + if (_thread->SupportCallableStatement() && + m_language == wxT("edbspl") && + !m_isFunction) + { + useCallable = true; + strQuery = wxT("CALL ") + m_fqName + wxT("("); + + if (m_args) + { + params = new pgParamsArray(); + wxMBConv *conv = conn->GetConv(); + + for(int idx = 0; idx < m_args->GetCount(); idx++) + { + params->Add(((*m_args)[idx])->GetParam(conv)); + + if (idx != 0) + strQuery += wxT(", "); + strQuery += wxString::Format(wxT("$%d::"), idx + 1) + + ((*m_args)[idx])->GetTypeName(); + } + } + strQuery += wxT(")"); + } + else if (m_language == wxT("edbspl") && conn->GetIsEdb() && + (!conn->BackendMinimumVersion(9, 0) || m_pkgOid != 0)) + { + wxString strDeclare, strStatement; + + if (!m_isFunction) + { + strStatement = wxT("\tEXEC ") + m_fqName; + } + else if (m_args && m_args->GetCount() > 0 && conn->BackendMinimumVersion(8, 4)) + { + strStatement = wxT("\tPERFORM ") + m_fqName; + } + else + { + strStatement = wxT("\tSELECT ") + m_fqName; + } + + if (m_args && m_args->GetCount() > 0) + { + strStatement.Append(wxT("(")); + + for(int idx = 0, firstProcessed = false; idx < m_args->GetCount(); idx++) + { + dbgArgInfo *arg = (*m_args)[idx]; + + if (!conn->EdbMinimumVersion(8, 4) && + arg->GetMode() == pgParam::PG_PARAM_OUT && + (!m_isFunction || m_language == wxT("edbspl"))) + { + if (firstProcessed) + strStatement.Append(wxT(", ")); + firstProcessed = true; + + strStatement.Append(wxT("NULL::")).Append(arg->GetTypeName()); + } + else if (conn->EdbMinimumVersion(8, 4) && + (arg->GetMode() == pgParam::PG_PARAM_OUT || + arg->GetMode() == pgParam::PG_PARAM_INOUT)) + { + if (!m_isFunction || m_language == wxT("edbspl")) + { + wxString strParam = wxString::Format(wxT("param%d"), idx); + + strDeclare.Append(wxT("\t")) + .Append(strParam) + .Append(wxT(" ")) + .Append(arg->GetTypeName()); + + if (arg->GetMode() == pgParam::PG_PARAM_INOUT) + { + strDeclare.Append(wxT(" := ")) + .Append(arg->Null() ? wxT("NULL") : conn->qtDbString(arg->Value())) + .Append(wxT("::")) + .Append(arg->GetTypeName()); + } + strDeclare.Append(wxT(";\n")); + + if (firstProcessed) + strStatement.Append(wxT(", ")); + firstProcessed = true; + + strStatement.Append(strParam); + } + else if (arg->GetMode() == pgParam::PG_PARAM_INOUT) + { + if (firstProcessed) + strStatement.Append(wxT(", ")); + firstProcessed = true; + + strStatement.Append( + arg->Null() ? wxT("NULL") : conn->qtDbString(arg->Value())) + .Append(wxT("::")) + .Append(arg->GetTypeName()); + } + } + else + { + if (firstProcessed) + strStatement.Append(wxT(", ")); + firstProcessed = true; + + if (arg->GetMode() == pgParam::PG_PARAM_VARIADIC) + strStatement.Append(wxT("VARIADIC ")); + + strStatement.Append( + arg->Null() ? wxT("NULL") : conn->qtDbString(arg->Value())) + .Append(wxT("::")) + .Append(arg->GetTypeName()); + } + } + strStatement.Append(wxT(")")); + + if (conn->BackendMinimumVersion(8, 4)) + { + strQuery = wxT("DECLARE\n") + + strDeclare + wxT("BEGIN\n") + strStatement + wxT("END;"); + } + else + { + strQuery = strStatement; + } + } + else if (!m_isFunction && m_language == wxT("edbspl")) + { + strQuery = strStatement; + } + else + { + strQuery = strStatement.Append(wxT("()")); + } + } else { - // return true only if the type is a stringlike type.. - switch (getTypeOid()) + if (!m_isFunction) { - case PGOID_TYPE_CHAR: - case PGOID_TYPE_NAME: - case PGOID_TYPE_TEXT: - case PGOID_TYPE_BPCHAR: - case PGOID_TYPE_VARCHAR: - case PGOID_TYPE_CSTRING: - return true; - - default: - return false; + strQuery = wxT("EXEC ") + m_fqName + wxT("("); } + else if (m_returnType == wxT("record")) + { + strQuery = wxT("SELECT ") + m_fqName + wxT("("); + } + else + { + strQuery = wxT("SELECT * FROM ") + m_fqName + wxT("("); + } + + if (m_args) + { + params = new pgParamsArray(); + wxMBConv *conv = conn->GetConv(); + + for(int idx = 0; idx < m_args->GetCount(); idx++) + { + dbgArgInfo *arg = (*m_args)[idx]; + + if (arg->GetMode() != pgParam::PG_PARAM_OUT) + { + params->Add(arg->GetParam(conv)); + + if (idx != 0) + strQuery += wxT(", "); + + if (arg->GetMode() == pgParam::PG_PARAM_VARIADIC) + strQuery += wxT("VARIADIC "); + + strQuery += wxString::Format(wxT("$%d::"), idx + 1) + + ((*m_args)[idx])->GetTypeName(); + } + } + + /* + * The function may not have IN/IN OUT/VARIADIC arguments, but only + * OUT one(s). + */ + if (params->GetCount() == 0) + { + delete params; + params = NULL; + } + } + strQuery += wxT(")"); } + + _thread->AddQuery(strQuery, params, RESULT_ID_DIRECT_TARGET_COMPLETE, NULL, useCallable); + + return true; } diff --git a/pgadmin/debugger/debugger.cpp b/pgadmin/debugger/debugger.cpp index 445cd92..603a71a 100644 --- a/pgadmin/debugger/debugger.cpp +++ b/pgadmin/debugger/debugger.cpp @@ -14,15 +14,15 @@ // App headers #include "pgAdmin3.h" +#include "frm/frmMain.h" #include "debugger/debugger.h" -#include "debugger/dbgBreakPoint.h" -#include "debugger/ctlCodeWindow.h" -#include "debugger/dlgDirectDbg.h" -#include "debugger/frmDebugger.h" +#include "debugger/dbgController.h" #include "schema/pgFunction.h" #include "schema/pgTrigger.h" #include "schema/edbPackageFunction.h" +#include + /////////////////////////////////////////////////// // Debugger factory /////////////////////////////////////////////////// @@ -42,7 +42,7 @@ wxWindow *debuggerFactory::StartDialog(frmMain *form, pgObject *obj) if (res->GetVal(wxT("proname")).Contains(wxT(":"))) { wxLogError(_("Functions with a colon in the name cannot be debugged.")); - return 0; + return (wxWindow *)NULL; } if (res->GetLong(wxT("count")) != 1) @@ -56,49 +56,20 @@ wxWindow *debuggerFactory::StartDialog(frmMain *form, pgObject *obj) browser->DeleteChildren(coll->GetId()); coll->ShowTreeDetail(browser); } - return 0; + return (wxWindow *)NULL; } - // Setup the debugger frame - frmDebugger *debugger = new frmDebugger(form, wxString::Format(_("Debugger - %s"), obj->GetFullIdentifier().c_str())); - - // Setup the connection properties to be used by the debugger - dbgConnProp cp; - cp.m_database = obj->GetDatabase()->GetIdentifier(); - cp.m_host = obj->GetServer()->GetName(); - cp.m_password = obj->GetDatabase()->GetServer()->GetPassword(); - cp.m_port = NumToStr((long)obj->GetServer()->GetPort()); - cp.m_sslMode = obj->GetServer()->GetSSL(); - cp.m_userName = obj->GetServer()->GetUsername(); - - // Setup the debugging session - dlgDirectDbg *directDebugger = NULL; - directDebugger = debugger->addDirectDbg(cp); - if (directDebugger == NULL) + try { - return 0; + new dbgController(form, obj, true); } - - dbgBreakPointList &breakpoints = directDebugger->getBreakpointList(); - breakpoints.Append(new dbgBreakPoint(dbgBreakPoint::OID, NumToStr((long)obj->GetOid()), wxT("'NULL'"))); - if (!directDebugger->startDebugging()) + catch (const std::runtime_error &) { - ctlTree *browser = form->GetBrowser(); - wxTreeItemId item = browser->GetSelection(); - if (obj == browser->GetObject(item)) - { - pgCollection *coll = browser->GetParentCollection(obj->GetId()); - browser->DeleteChildren(coll->GetId()); - coll->ShowTreeDetail(browser); - } - return 0; + // just ignore this errors, we already logged them in native messages to + // the end-user } - // Return the debugger window to frmMain. - if (debugger && !directDebugger->GetCancelled()) - return debugger; - else - return 0; + return (wxWindow *)NULL; } bool debuggerFactory::CheckEnable(pgObject *obj) @@ -130,23 +101,14 @@ bool debuggerFactory::CheckEnable(pgObject *obj) if (obj->GetConnection()->GetIsEdb() && func->GetSource().Trim(false).StartsWith(wxT("$__EDBwrapped__$"))) return false; - if (func->GetReturnType() != wxT("trigger") && func->GetReturnType() != wxT("\"trigger\"")) + if (func->GetReturnType() != wxT("trigger") && + func->GetReturnType() != wxT("\"trigger\"")) { - if (func->GetLanguage() == wxT("plpgsql") && obj->GetDatabase()->CanDebugPlpgsql()) + if (func->GetLanguage() == wxT("plpgsql") && + obj->GetDatabase()->CanDebugPlpgsql()) return true; -#ifndef EDB_LIBPQ -#ifdef __WXMSW__ else if (func->GetLanguage() == wxT("edbspl") && - obj->GetConnection()->EdbMinimumVersion(8, 4) && - !obj->GetConnection()->EdbMinimumVersion(9, 0) && - !(PQiGetOutResult && PQiPrepareOut && PQiSendQueryPreparedOut)) - return false; -#else - else if (func->GetLanguage() == wxT("edbspl") && obj->GetConnection()->EdbMinimumVersion(8, 4)) - return false; -#endif -#endif - else if (func->GetLanguage() == wxT("edbspl") && obj->GetDatabase()->CanDebugEdbspl()) + obj->GetDatabase()->CanDebugEdbspl()) return true; else return false; @@ -194,16 +156,18 @@ wxWindow *breakpointFactory::StartDialog(frmMain *form, pgObject *obj) // deep down in query threads. // We also make sure the function name doesn't contain a : as that will // sent the debugger API nuts. - pgSet *res = obj->GetConnection()->ExecuteSet(wxT("SELECT count(*) AS count, proname FROM pg_proc WHERE oid = ") + dbgOid + wxT(" GROUP BY proname")); + pgSet *res = obj->GetConnection()->ExecuteSet( + wxT("SELECT count(*) AS count, proname FROM pg_proc WHERE oid = ") + dbgOid + wxT(" GROUP BY proname")); if (res->GetVal(wxT("proname")).Contains(wxT(":"))) { wxLogError(_("Functions with a colon in the name cannot be debugged.")); - return 0; + return (wxWindow *)NULL; } if (res->GetLong(wxT("count")) != 1) { wxLogError(_("The selected function could not be found.")); + ctlTree *browser = form->GetBrowser(); wxTreeItemId item = browser->GetSelection(); if (obj == browser->GetObject(item)) @@ -212,36 +176,20 @@ wxWindow *breakpointFactory::StartDialog(frmMain *form, pgObject *obj) browser->DeleteChildren(coll->GetId()); coll->ShowTreeDetail(browser); } - return 0; + return (wxWindow *)NULL; } - // Setup the debugger frame - frmDebugger *debugger = new frmDebugger(form, wxString::Format(_("Debugger - %s"), obj->GetFullIdentifier().c_str())); - debugger->Show(true); - debugger->Raise(); - - // Setup the connection properties to be used by the debugger - dbgConnProp cp; - cp.m_database = obj->GetDatabase()->GetIdentifier(); - cp.m_host = obj->GetServer()->GetName(); - cp.m_password = obj->GetDatabase()->GetServer()->GetPassword(); - cp.m_port = NumToStr((long)obj->GetServer()->GetPort()); - cp.m_sslMode = obj->GetServer()->GetSSL(); - cp.m_userName = obj->GetServer()->GetUsername(); - - // Setup the debugging session - ctlCodeWindow *globalDebugger = NULL; - globalDebugger = debugger->addDebug(cp); - if (globalDebugger == NULL) - return 0; - - dbgBreakPointList &breakpoints = globalDebugger->getBreakpointList(); - breakpoints.Append(new dbgBreakPoint(dbgBreakPoint::OID, dbgOid, wxT("'NULL'"))); - - globalDebugger->startGlobalDebugging(); - - // Return the debugger window to frmMain. - return debugger; + try + { + new dbgController(form, obj, false); + } + catch (const std::runtime_error &) + { + // just ignore this errors, we already logged them in native messages to + // the end-user + } + + return (wxWindow *)NULL; } bool breakpointFactory::CheckEnable(pgObject *obj) @@ -266,23 +214,15 @@ bool breakpointFactory::CheckEnable(pgObject *obj) pgFunction *func = (pgFunction *)obj; // If this is an EDB wrapped function, no debugging allowed - if (obj->GetConnection()->GetIsEdb() && func->GetSource().Trim(false).StartsWith(wxT("$__EDBwrapped__$"))) + if (obj->GetConnection()->GetIsEdb() && + func->GetSource().Trim(false).StartsWith(wxT("$__EDBwrapped__$"))) return false; - if (func->GetLanguage() == wxT("plpgsql") && obj->GetDatabase()->CanDebugPlpgsql()) + if (func->GetLanguage() == wxT("plpgsql") && + obj->GetDatabase()->CanDebugPlpgsql()) return true; -#ifndef EDB_LIBPQ -#ifdef __WXMSW__ else if (func->GetLanguage() == wxT("edbspl") && - obj->GetConnection()->EdbMinimumVersion(8, 4) && - !(PQiGetOutResult && PQiPrepareOut && PQiSendQueryPreparedOut)) - return false; -#else - else if (func->GetLanguage() == wxT("edbspl") && obj->GetConnection()->EdbMinimumVersion(8, 4)) - return false; -#endif -#endif - else if (func->GetLanguage() == wxT("edbspl") && obj->GetDatabase()->CanDebugEdbspl()) + obj->GetDatabase()->CanDebugEdbspl()) return true; else return false; @@ -303,12 +243,15 @@ bool breakpointFactory::CheckEnable(pgObject *obj) pgTrigger *trig = (pgTrigger *)obj; // If this is an EDB wrapped function, no debugging allowed - if (obj->GetConnection()->GetIsEdb() && trig->GetSource().Trim(false).StartsWith(wxT("$__EDBwrapped__$"))) + if (obj->GetConnection()->GetIsEdb() && + trig->GetSource().Trim(false).StartsWith(wxT("$__EDBwrapped__$"))) return false; - if (trig->GetLanguage() == wxT("plpgsql") && obj->GetDatabase()->CanDebugPlpgsql()) + if (trig->GetLanguage() == wxT("plpgsql") && + obj->GetDatabase()->CanDebugPlpgsql()) return true; - else if (trig->GetLanguage() == wxT("edbspl") && obj->GetDatabase()->CanDebugEdbspl()) + else if (trig->GetLanguage() == wxT("edbspl") && + obj->GetDatabase()->CanDebugEdbspl()) return true; else return false; diff --git a/pgadmin/debugger/dlgDirectDbg.cpp b/pgadmin/debugger/dlgDirectDbg.cpp index 2e1ef87..64d3202 100644 --- a/pgadmin/debugger/dlgDirectDbg.cpp +++ b/pgadmin/debugger/dlgDirectDbg.cpp @@ -16,38 +16,39 @@ #include // App headers -#include "debugger/dlgDirectDbg.h" -#include "debugger/frmDebugger.h" -#include "debugger/dbgTargetInfo.h" -#include "debugger/dbgPgConn.h" -#include "debugger/ctlResultGrid.h" -#include "debugger/dbgResultset.h" +#include "ctl/ctlAuiNotebook.h" +#include "db/pgConn.h" +#include "db/pgQueryThread.h" +#include "db/pgQueryResultEvent.h" +#include "schema/pgObject.h" #include "debugger/dbgConst.h" -#include "debugger/dbgDbResult.h" -#include "debugger/ctlCodeWindow.h" +#include "debugger/dbgBreakPoint.h" +#include "debugger/dbgController.h" +#include "debugger/dbgModel.h" +#include "debugger/ctlStackWindow.h" +#include "debugger/ctlMessageWindow.h" +#include "debugger/ctlTabWindow.h" +#include "debugger/frmDebugger.h" +#include "debugger/dlgDirectDbg.h" #include "images/debugger.pngc" -#include - #define lblMessage CTRL_STATIC("lblMessage") #define grdParams CTRL("grdParams", wxGrid) #define chkPkgInit CTRL_CHECKBOX("chkPkgInit") +#define btnDebug CTRL_BUTTON("wxID_OK") -IMPLEMENT_CLASS( dlgDirectDbg, pgDialog ) - -BEGIN_EVENT_TABLE( dlgDirectDbg, pgDialog ) - EVT_BUTTON( wxID_OK, dlgDirectDbg::OnOk ) - EVT_BUTTON( wxID_CANCEL, dlgDirectDbg::OnCancel ) - EVT_BUTTON( MENU_ID_SPAWN_DEBUGGER, dlgDirectDbg::OnDebug ) - EVT_BUTTON( MENU_ID_NOTICE_RECEIVED, dlgDirectDbg::OnNoticeReceived ) - - EVT_MENU( RESULT_ID_DIRECT_TARGET_COMPLETE, dlgDirectDbg::OnTargetComplete ) - - EVT_CLOSE( dlgDirectDbg::OnClose ) +IMPLEMENT_CLASS(dlgDirectDbg, pgDialog) +BEGIN_EVENT_TABLE(dlgDirectDbg, pgDialog) + EVT_BUTTON(wxID_OK, dlgDirectDbg::OnOk) + EVT_BUTTON(wxID_CANCEL, dlgDirectDbg::OnCancel) + EVT_GRID_CMD_LABEL_LEFT_CLICK(XRCID("grdParams"), dlgDirectDbg::OnClickGridLabel) + EVT_MENU(RESULT_ID_ARGS_UPDATED, dlgDirectDbg::ResultArgsUpdated) + EVT_MENU(RESULT_ID_ARGS_UPDATE_ERROR, dlgDirectDbg::ResultArgsUpdateError) END_EVENT_TABLE() + //////////////////////////////////////////////////////////////////////////////// // dlgDirectDbg constructor // @@ -62,855 +63,942 @@ END_EVENT_TABLE() // EXEC statement that invokes the target (with the parameter values // provided by the user). -dlgDirectDbg::dlgDirectDbg( frmDebugger *parent, wxWindowID id, const dbgConnProp &connProp ) - : m_connProp(connProp), - m_targetInfo(NULL), - m_conn(NULL), - m_codeWindow(NULL), - m_parent (parent), - m_cancelled (false) +dlgDirectDbg::dlgDirectDbg(frmDebugger *_parent, dbgController *_controller, + pgConn *_conn) + : m_controller(_controller), m_thread(NULL), m_conn(_conn) { wxWindowBase::SetFont(settings->GetSystemFont()); - LoadResource(m_parent, wxT("dlgDirectDbg")); + LoadResource(_parent, wxT("dlgDirectDbg")); // Icon SetIcon(*debugger_png_ico); RestorePosition(); -} - -//////////////////////////////////////////////////////////////////////////////// -// setupParamWindow() -// -// This function lays out the parameter prompt window. It contains a grid that -// displays the name and type of each IN (and IN/OUT) parameter and a place to -// enter a value for each of those parameters. It also contains an OK button -// and a CANCEL button -void dlgDirectDbg::setupParamWindow( ) -{ - // Add three columns to the grid control: - // (Parameter) Name, Type, and Value - grdParams->CreateGrid( 0, 3 ); - grdParams->SetColLabelValue( COL_NAME, _( "Name" )); - grdParams->SetColLabelValue( COL_TYPE, _( "Type" )); - grdParams->SetColLabelValue( COL_VALUE, _( "Value" )); - grdParams->SetRowLabelSize( 25 ); - grdParams->SetColSize( 0, 75 ); - grdParams->SetColSize( 1, 100 ); - grdParams->SetColSize( 2, grdParams->GetClientSize().x - 210 ); - grdParams->SetColLabelSize( 18 ); + grdParams->SetRowLabelSize(wxGRID_AUTOSIZE); + grdParams->SetColLabelSize(20); + grdParams->AutoSizeColumns(true); chkPkgInit->SetValue(false); - chkPkgInit->Disable(); -} - -//////////////////////////////////////////////////////////////////////////////// -// startDebugging() -// -// This function initializes *this with information about the target function -// (or procedure), constructs a grid that prompts the user for parameter values, -// and then displays the prompt dialog to the user. Call this function after -// you construct a dlgDirectDbg (obviously) when you're ready to display the -// prompt dialog to the user. - -bool dlgDirectDbg::startDebugging( void ) -{ - // First, figure out what kind of target we are going to debug. - // The caller filled in our m_breakpoint list with the name and - // type of each target that he's interested in. - // - // FIXME: For now, we only allow one initial breakpoint for direct - // debugging - you can create other breakpoints once you see - // the source code. - - dbgBreakPointList::Node *node = m_breakpoints.GetFirst(); - - wxASSERT_MSG( node != NULL, wxT( "Expected to find at least one target on the command line" )); - dbgBreakPoint *breakpoint = node->GetData(); + PopulateParamGrid(); - m_target = breakpoint->getTarget(); + LoadSettings(); - char targetType = 0; - - switch( breakpoint->getTargetType()) - { - case dbgBreakPoint::TRIGGER: - targetType = 't'; - break; - case dbgBreakPoint::FUNCTION: - targetType = 'f'; - break; - case dbgBreakPoint::PROCEDURE: - targetType = 'p'; - break; - case dbgBreakPoint::OID: - targetType = 'o'; - break; - default: - { - wxASSERT_MSG( false, wxT( "Unexpected target type" )); - break; - } - } - - if (!loadTargetInfo( m_target, m_connProp, targetType )) - return false; - - populateParamGrid(); - return true; + grdParams->AutoSize(); } + //////////////////////////////////////////////////////////////////////////////// -// loadTargetInfo() +// PopulateParamGrid() +// +// This function reads parameter descriptions from m_targetInfo and adds a +// new row to the grid control for each IN (or IN/OUT, VARAIADIC) parameter. +// Each row displays the parameter name, the data type, and an entry box +// where the user can type in a value for that parameter // -// This function establishes a connection to the server and creates a new -// dbgTargetInfo object that loads information about the debug target (that is, -// the function or procedure of interest). Call this function with two -// arguments: target should contain the signature of a function or procedure -// or the OID of a function or procedure and connProp should contain the -// information required to connect to the server (like the hostname, port number, -// and user name). - -bool dlgDirectDbg::loadTargetInfo( const wxString &target, const dbgConnProp &connProp, char targetType ) +void dlgDirectDbg::PopulateParamGrid() { - // Connect to the server using the connection properties contained in connProp + pgDbgArgs *args = m_controller->GetTargetInfo()->GetArgs(); - m_conn = new dbgPgConn(m_parent, connProp); + // If the target is defined within package, offer the user + // a chance to debug the initializer (there may or may not + // be an initializer, we don't really know at this point) + if(m_controller->GetTargetInfo()->GetPkgOid() == 0) + chkPkgInit->Disable(); + else + chkPkgInit->Enable(); - if( m_conn && m_conn->isConnected()) + if (!args) { - if( getenv( "DEBUGGER_INIT" )) - { - PQclear( m_conn->waitForCommand( wxString(getenv( "DEBUGGER_INIT" ), wxConvUTF8 ))); - } + grdParams->SetColLabelValue(COL_NAME, _("No arguments required")); + grdParams->SetColSize(0, 350); - // Our proxy API may throw (perfectly legitimate) errors at us (for example, - // if the target process ends while we are waiting for a breakpoint) - apparently - // those error messages scare the user when they show up in the log, so we'll - // just suppress logging for this session - - PQclear( m_conn->waitForCommand( wxT( "SET log_min_messages TO fatal" ))); + return; + } - // Now load information about the target into m_targetInfo (note: - // the dbgTargetInfo() constructor queries the server for all - // required information) + // Add seven columns to the grid control: + // - Name, Type, NULL?, EXPR?, Value, Default?, And Default Value + grdParams->CreateGrid(0, 7); + + grdParams->SetColLabelValue(COL_NAME, _("Name")); + grdParams->SetColLabelValue(COL_TYPE, _("Type")); + grdParams->SetColLabelValue(COL_NULL, _("Null?")); + grdParams->SetColLabelValue(COL_EXPR, _("Expr?")); + grdParams->SetColLabelValue(COL_VALUE, _("Value")); + grdParams->SetColLabelValue(COL_USE_DEF, _("Default?")); + grdParams->SetColLabelValue(COL_DEF_VAL, _("Default Value")); + + grdParams->SetColSize(COL_NAME, 100); + grdParams->SetColSize(COL_TYPE, 100); + grdParams->SetColSize(COL_NULL, 100); + grdParams->SetColSize(COL_EXPR, 100); + grdParams->SetColSize(COL_VALUE, 100); + grdParams->SetColSize(COL_USE_DEF, 100); + grdParams->SetColSize(COL_DEF_VAL, 100); + + size_t idx = 0; + + for(size_t pIdx = 0; pIdx < args->Count(); pIdx++) + { + dbgArgInfo *arg = (*args)[pIdx]; - try - { - m_targetInfo = new dbgTargetInfo( target, m_conn, targetType ); - } - catch( const std::runtime_error &error ) + // If this is an IN parameter (or an IN/OUT parameter), add + // a new row to the grid + if(arg->GetMode() != pgParam::PG_PARAM_OUT) { - wxLogError(wxT("%s"), wxString(error.what(), wxConvUTF8).c_str()); - m_conn->Close(); - return false; - } - - this->SetTitle(m_targetInfo->getName()); - } + grdParams->AppendRows(1); + grdParams->SetCellValue(idx, COL_NAME, arg->GetName()); - return true; -} + // Make it obvious which are variadics + if (arg->GetMode() != pgParam::PG_PARAM_VARIADIC) + { + grdParams->SetRowLabelValue(idx, wxEmptyString); + grdParams->SetCellValue(idx, COL_TYPE, arg->GetTypeName()); -//////////////////////////////////////////////////////////////////////////////// -// populateParamGrid() -// -// This function reads parameter descriptions from m_targetInfo and adds a new -// row to the grid control for each IN (or IN/OUT) parameter. Each row displays -// the parameter name, the data type, and an entry box where the user can type -// in a value for that parameter + if (!arg->IsArray()) + { + // Is the value an expression? + grdParams->SetCellEditor(idx, COL_EXPR, new ctlGridCellBoolEditor()); + grdParams->SetCellRenderer(idx, COL_EXPR, new wxGridCellBoolRenderer()); + grdParams->SetCellValue(idx, COL_EXPR, wxT("")); + } + else + { + grdParams->SetCellBackgroundColour(idx, COL_EXPR, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_VALUE, wxColour(235, 235, 235, 90)); + } -void dlgDirectDbg::populateParamGrid( ) -{ - // First, try to load default values from a previous invocation into - // m_targetInfo (assuming that we're debugging the same target this - // time around) + // Use the default value? + grdParams->SetCellRenderer(idx, COL_USE_DEF, new wxGridCellBoolRenderer()); + grdParams->SetCellEditor(idx, COL_USE_DEF, new ctlGridCellBoolEditor()); + } + else + { + grdParams->SetCellValue(idx, COL_TYPE, wxT("VARIADIC ") + arg->GetTypeName()); + grdParams->SetRowLabelValue(idx, wxEmptyString); - loadSettings(); + grdParams->AppendRows(1); + grdParams->SetRowLabelValue(idx + 1, wxT("+")); + grdParams->SetCellValue(idx + 1, COL_NAME, _("Click '+/-' to add/remove value to variadic")); - int i = 0; + grdParams->SetCellBackgroundColour(idx + 1, COL_EXPR, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx + 1, COL_VALUE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx + 1, COL_USE_DEF, wxColour(235, 235, 235, 90)); + } - for( int count = 0; count < m_targetInfo->getArgCount(); ++count ) - { - wsArgInfo &arg = ((*m_targetInfo)[count] ); + // Set value to NULL? + grdParams->SetCellEditor(idx, COL_NULL, new ctlGridCellBoolEditor(arg)); + grdParams->SetCellRenderer(idx, COL_NULL, new wxGridCellBoolRenderer()); + grdParams->SetCellValue(idx, COL_NULL, wxT("")); - // If this is an IN parameter (or an IN/OUT parameter), add - // a new row to the grid + if (arg->HasDefault()) + { + // Whenever the default value is available, use that by default + grdParams->SetCellValue(idx, COL_USE_DEF, wxT("1")); + grdParams->SetCellValue(idx, COL_DEF_VAL, arg->Default()); + + // When "Use Defalut?" is enabled, disable VALUE, NULL? and + // EXPR? columns + grdParams->SetReadOnly(idx, COL_USE_DEF, false); + grdParams->SetReadOnly(idx, COL_VALUE, false); + grdParams->SetReadOnly(idx, COL_EXPR, false); + } + else + { + grdParams->SetCellValue(idx, COL_USE_DEF, wxT("")); + grdParams->SetCellValue(idx, COL_DEF_VAL, wxT("-- NO DEFAULT VALUE --")); - if( arg.getMode() != wxT( "o" )) - { - grdParams->AppendRows( 1 ); - grdParams->SetCellValue( i, COL_NAME, arg.getName()); + // When default value is not available, we should ask the user + // to enter them + grdParams->SetReadOnly(idx, COL_USE_DEF, true); + grdParams->SetReadOnly(idx, COL_VALUE, arg->IsArray()); + grdParams->SetReadOnly(idx, COL_EXPR, arg->IsArray()); - // Make it obvious which are variadics - if (arg.getMode() != wxT( "v" )) - grdParams->SetCellValue( i, COL_TYPE, arg.getType()); - else - grdParams->SetCellValue( i, COL_TYPE, arg.getType() + wxT(" VARIADIC")); + grdParams->SetCellBackgroundColour(idx, COL_USE_DEF, wxColour(235, 235, 235, 90)); + if (arg->IsArray()) + { + grdParams->SetCellBackgroundColour(idx, COL_VALUE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_EXPR, wxColour(235, 235, 235, 90)); + } + } - grdParams->SetCellValue( i, COL_VALUE, arg.getValue()); + grdParams->SetReadOnly(idx, COL_NULL, false); + grdParams->SetReadOnly(idx, COL_NAME, true); + grdParams->SetReadOnly(idx, COL_TYPE, true); + grdParams->SetReadOnly(idx, COL_DEF_VAL, true); + grdParams->SetCellBackgroundColour(idx, COL_NAME, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_TYPE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_DEF_VAL, wxColour(235, 235, 235, 90)); - grdParams->SetReadOnly( i, COL_NAME, true ); - grdParams->SetReadOnly( i, COL_TYPE, true ); - grdParams->SetReadOnly( i, COL_VALUE, false ); + idx++; - i++; + if (arg->GetMode() != pgParam::PG_PARAM_VARIADIC && + arg->IsArray()) + { + grdParams->AppendRows(1); + + grdParams->SetRowLabelValue(idx, wxT("+")); + grdParams->SetCellValue(idx, COL_NAME, _("Click '+/-' to add/remove value to the array")); + + grdParams->SetReadOnly(idx, COL_NAME, true); + grdParams->SetReadOnly(idx, COL_TYPE, true); + grdParams->SetReadOnly(idx, COL_NULL, true); + grdParams->SetReadOnly(idx, COL_EXPR, true); + grdParams->SetReadOnly(idx, COL_VALUE, true); + grdParams->SetReadOnly(idx, COL_USE_DEF, true); + grdParams->SetReadOnly(idx, COL_DEF_VAL, true); + + grdParams->SetCellBackgroundColour(idx, COL_NAME, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_TYPE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_NULL, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_EXPR, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_VALUE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_USE_DEF, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(idx, COL_DEF_VAL, wxColour(235, 235, 235, 90)); + + idx++; + } } } // Move the cursor to the first value (so that the user // can just start typing) - grdParams->SetGridCursor( 0, COL_VALUE ); + grdParams->SetGridCursor(0, COL_VALUE); grdParams->SetFocus(); +} - // If the target is defined within package, offer the user - // a chance to debug the initializer (there may or may not - // be an initializer, we don't really know at this point) - - if( m_targetInfo->getPkgOid() == 0 ) - chkPkgInit->Disable(); - else - chkPkgInit->Enable(); +//////////////////////////////////////////////////////////////////////////////// +// LoadSettings() +// +// Loads default values from our .ini file. We save the OID of the most +// recent direct-debugging target when close a session. If we're direct- +// debugging the same target this time around, we load the argument values +// from the .ini file. - // Set the columns' size +void dlgDirectDbg::LoadSettings() +{ + long num, arrCnt; + wxString tmp, key; + bool initPkgCon = false; - grdParams->SetColSize(COL_NAME, 200); - grdParams->SetColSize(COL_TYPE, 100); - grdParams->SetColSize(COL_VALUE, 150); + settings->Read(wxT("Debugger/Proc/OID"), &num, -1L); - // If the target function has no parameters (and it's not defined within - // a package), there's no good reason to wait for the user to hit the Ok - // button before we invoke the target... + dbgTargetInfo *target = m_controller->GetTargetInfo(); + pgDbgArgs *args = m_controller->GetTargetInfo()->GetArgs(); - if((m_targetInfo->getArgInCount() + m_targetInfo->getArgInOutCount() == 0 ) && ( m_targetInfo->getPkgOid() == 0)) + if (num != target->GetOid()) { - grdParams->AppendRows( 1 ); - grdParams->SetReadOnly( i, COL_NAME, true ); - grdParams->SetReadOnly( i, COL_TYPE, true ); - grdParams->SetReadOnly( i, COL_VALUE, true ); - - grdParams->SetCellValue( 0, COL_NAME, _( "No arguments required" )); - wxFont font = grdParams->GetCellFont( 0, COL_NAME ); - font.SetStyle( wxFONTSTYLE_ITALIC ); - grdParams->SetCellFont( 0, COL_NAME, font ); + chkPkgInit->SetValue(target->GetPkgInitOid() != 0L); + chkPkgInit->Enable(target->GetPkgInitOid() != 0L); - activateDebugger(); + return; } else { - this->ShowModal(); + settings->Read(wxT("Debugger/Proc/initialize_package_constructor"), &initPkgCon, true); + + chkPkgInit->SetValue(&initPkgCon && target->GetPkgInitOid() != 0L); + chkPkgInit->Enable(target->GetPkgInitOid() != 0L); } -} -//////////////////////////////////////////////////////////////////////////////// -// OnOk() -// -// This event handler is called when the user clicks the OK button - we call the -// activateDebugger() function to set the required breakpoint and invoke the -// target (after nabbing any parameter values from the prompt dialog) + settings->Read(wxT("Debugger/Proc/args"), &num, 0L); -void dlgDirectDbg::OnOk( wxCommandEvent &event ) -{ - activateDebugger(); -} + if (num <= 0L) + return; -//////////////////////////////////////////////////////////////////////////////// -// loadSettings() -// -// Loads default values from our .ini file. We save the OID of the most -// recent direct-debugging target when close a session. If we're direct- -// debugging the same target this time around, we load the argument values -// from the .ini file. + if (!args) + return; -void dlgDirectDbg::loadSettings() -{ - long lastOID; + for (int cnt = 0, row = 0; cnt < num && cnt < args->Count();) + { + ctlGridCellBoolEditor *editor = + dynamic_cast( + grdParams->GetCellEditor(row, COL_NULL)); + dbgArgInfo *arg + = editor != NULL ? editor->GetArg() : NULL; - settings->Read( wxT( "Debugger/Proc/OID" ), &lastOID, -1 ); + if (arg == NULL) + { + row++; - if( lastOID == m_targetInfo->getOid()) - { - int count = 0; + continue; + } + + key = wxString::Format(wxT("Debugger/Proc/%d/"), cnt); + + // Use NULL? + settings->Read(key + wxT("NULL"), &tmp, wxEmptyString); + grdParams->SetCellValue(row, COL_NULL, tmp); + + // Use Default (if available) + settings->Read(key + wxT("USE_DEF"), &tmp, wxEmptyString); + grdParams->SetCellValue(row, COL_USE_DEF, tmp); + + // Is Array/VARIADIC? + settings->Read(key + wxT("ArrCnt"), &arrCnt, 0L); - for( int i = 0; i < m_targetInfo->getArgCount(); ++i ) + if (arrCnt > 0L) { - wsArgInfo &arg = (*m_targetInfo)[i]; + // An individual variable can be an expression or can be the 'NULL' value + wxString valKey, strVal, strExpr, strNull; - if( arg.getMode() != wxT( "o" )) + for (int idx = 0; idx < arrCnt; idx++, row++) { - settings->Read( wxString::Format( wxT( "Debugger/Proc/argValue%d" ), ++count ), &(arg.getValue()), wxT( "" )); + valKey = wxString::Format(wxT("Debugger/Proc/%d/%d/"), cnt, idx); + + // Use 'NULL'? + settings->Read(valKey + wxT("NULL"), &strNull, wxEmptyString); + + // Use EXPR? + settings->Read(valKey + wxT("EXPR"), &strExpr, wxEmptyString); + + // Value + settings->Read(valKey + wxT("VAL"), &strVal, wxEmptyString); + + if (arg->IsArray()) + { + int arrRow = row + 1; + + if (idx == 0) + { + grdParams->SetReadOnly(row, COL_EXPR, true); + grdParams->SetReadOnly(row, COL_VALUE, true); + + grdParams->SetCellBackgroundColour(row, COL_EXPR, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(row, COL_VALUE, wxColour(235, 235, 235, 90)); + + grdParams->SetRowLabelValue( + row, wxString::Format(wxT("%d"), cnt + 1)); + } + grdParams->InsertRows(arrRow, 1, false); + grdParams->SetRowLabelValue(arrRow, wxT("-")); + + grdParams->SetCellValue(arrRow, COL_TYPE, arg->GetBaseType()); + + // Is the value an expression? + grdParams->SetCellEditor(arrRow, COL_EXPR, new ctlGridCellBoolEditor()); + grdParams->SetCellRenderer(arrRow, COL_EXPR, + new wxGridCellBoolRenderer()); + grdParams->SetCellValue(arrRow, COL_EXPR, strExpr); + + // Set value to NULL? + grdParams->SetCellEditor(arrRow, COL_NULL, + new ctlGridCellBoolEditor(arg)); + grdParams->SetCellRenderer(arrRow, COL_NULL, + new wxGridCellBoolRenderer()); + grdParams->SetCellValue(arrRow, COL_NULL, strNull); + + // Set value + grdParams->SetCellValue(arrRow, COL_VALUE, strVal); + + grdParams->SetReadOnly(arrRow, COL_NULL, false); + grdParams->SetReadOnly(arrRow, COL_EXPR, false); + grdParams->SetReadOnly(arrRow, COL_VALUE, false); + + grdParams->SetCellBackgroundColour(arrRow, COL_NAME, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow, COL_TYPE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow, COL_USE_DEF, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow, COL_DEF_VAL, wxColour(235, 235, 235, 90)); + + grdParams->SetReadOnly(arrRow + 1, COL_NAME, true); + grdParams->SetReadOnly(arrRow + 1, COL_TYPE, true); + grdParams->SetReadOnly(arrRow + 1, COL_NULL, true); + grdParams->SetReadOnly(arrRow + 1, COL_EXPR, true); + grdParams->SetReadOnly(arrRow + 1, COL_VALUE, true); + grdParams->SetReadOnly(arrRow + 1, COL_USE_DEF, true); + grdParams->SetReadOnly(arrRow + 1, COL_DEF_VAL, true); + + grdParams->SetCellBackgroundColour(arrRow + 1, COL_NAME, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow + 1, COL_TYPE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow + 1, COL_NULL, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow + 1, COL_EXPR, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow + 1, COL_VALUE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow + 1, COL_USE_DEF, + wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(arrRow + 1, COL_DEF_VAL, + wxColour(235, 235, 235, 90)); + + grdParams->SetRowLabelValue(arrRow + 1, wxT("+")); + } + else + { + grdParams->SetCellValue(row, COL_VALUE, strVal); + grdParams->SetCellValue(row, COL_EXPR, strExpr); + grdParams->SetCellValue(row, COL_NULL, strNull); + + grdParams->SetReadOnly(row, COL_NULL, false); + grdParams->SetReadOnly(row, COL_EXPR, false); + grdParams->SetReadOnly(row, COL_VALUE, false); + + grdParams->SetRowLabelValue( + row, wxString::Format(wxT("%d"), cnt + 1)); + } } + if (arg->IsArray()) + row++; } + cnt++; } } -//////////////////////////////////////////////////////////////////////////////// -// saveSettings() -// -// Save default values to our .ini file. We save the OID of the most -// recent direct-debugging target when close a session. We also save the -// value of each argument - if you debug the same target again next time, -// loadSettings() will initialize the parameter-values window with the -// same parameter values that you entered in this session. -void dlgDirectDbg::saveSettings() +void dlgDirectDbg::OnOk(wxCommandEvent &_ev) { - settings->WriteLong( wxT( "Debugger/Proc/OID" ), m_targetInfo->getOid()); + if (m_thread) + return; + + grdParams->Enable(false); + btnDebug->Enable(false); - int count = 0; + m_thread = new dbgArgValueEvaluator(m_conn, this); - for( int i = 0; i < m_targetInfo->getArgCount(); ++i ) + if (m_thread->Create() != wxTHREAD_NO_ERROR) { - wsArgInfo &arg = ( *m_targetInfo)[i]; + delete m_thread; + m_thread = NULL; - if( arg.getMode() != wxT( "o" )) - { - settings->Write( wxString::Format( wxT( "Debugger/Proc/argName%d" ), ++count ), arg.getName()); - settings->Write( wxString::Format( wxT( "Debugger/Proc/argType%d" ), count ), arg.getType()); - settings->Write( wxString::Format( wxT( "Debugger/Proc/argValue%d" ), count ), wxString((arg.getValue() == wxT("NULL") ? wxEmptyString : arg.getValue().c_str()))); - } + wxLogError(_("Couldn't create the required thread for this.")); + EndModal(wxID_CANCEL); + + return; } + + m_thread->Run(); } -//////////////////////////////////////////////////////////////////////////////// -// OnCancel() -// -// This event handler is called when the user clicks the Cancel button - we -// close the connection to the server and then close ourself. -void dlgDirectDbg::OnCancel( wxCommandEvent &event ) +void dlgDirectDbg::OnCancel(wxCommandEvent &_ev) { - // This will raise close event which is handled by - // dlgDirectDbg::OnClose(). - m_cancelled = true; - Close(); -} + if (m_thread) + { + m_thread->CancelEval(); -//////////////////////////////////////////////////////////////////////////////// -// OnClose() -// -// wxWidgets invokes this event handler when the user closes the parameter -// window. We close the connection with server and raise close event for -// MainFrame. + m_thread->Wait(); -void dlgDirectDbg::OnClose( wxCloseEvent &event ) + delete m_thread; + m_thread = NULL; + } + _ev.Skip(); +} + +void dlgDirectDbg::OnClickGridLabel(wxGridEvent &_ev) { - // Destroy the grid - required as it seems to create threads in some cases - if (grdParams) + if (_ev.AltDown() || _ev.ControlDown() || _ev.ShiftDown()) { - grdParams->Destroy(); - delete grdParams; + _ev.Skip(); + + return; } - // Close the debugger (proxy) connection - if (m_conn) + int row = _ev.GetRow(); + int col = _ev.GetCol(); + + if (row < 0 || col > 0) { - m_conn->Close(); - delete m_conn; - m_conn = NULL; + _ev.Skip(); + + return; } - // Closing frmMain from here leads to recursive call - // to OnClose function on windows -#ifndef __WXWIN__ - // This will inform the MainWindow to close. - // if it's not visible yet. - if (m_parent->IsShown()) - event.Skip(); + wxString strLabel = grdParams->GetRowLabelValue(row); + + if (strLabel == wxT("+")) + { + wxASSERT(row != 0); + + ctlGridCellBoolEditor *editor = + dynamic_cast( + grdParams->GetCellEditor(row - 1, COL_NULL)); + dbgArgInfo *arg + = editor != NULL ? editor->GetArg() : NULL; + + grdParams->InsertRows(row, 1, false); + grdParams->SetRowLabelValue(row, wxT("-")); + + grdParams->SetCellValue(row, COL_TYPE, arg->GetBaseType()); + grdParams->SetCellBackgroundColour(row, COL_TYPE, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(row, COL_NAME, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(row, COL_USE_DEF, wxColour(235, 235, 235, 90)); + grdParams->SetCellBackgroundColour(row, COL_DEF_VAL, wxColour(235, 235, 235, 90)); + + // Is the value an expression? + grdParams->SetCellEditor(row, COL_EXPR, new ctlGridCellBoolEditor()); + grdParams->SetCellRenderer(row, COL_EXPR, new wxGridCellBoolRenderer()); + grdParams->SetCellValue(row, COL_EXPR, wxT("")); + + // Set value to NULL? + grdParams->SetCellEditor(row, COL_NULL, new ctlGridCellBoolEditor(arg)); + grdParams->SetCellRenderer(row, COL_NULL, new wxGridCellBoolRenderer()); + grdParams->SetCellValue(row, COL_NULL, wxT("1")); + + row++; + grdParams->SetRowLabelValue(row, wxT("+")); + } + else if (strLabel == wxT("-")) + { + dbgArgInfo *arg = NULL; + grdParams->DeleteRows(row, 1, false); + } else - m_parent->Close(); -#endif // __WXWIN__ + return; + + // Update the row labels + ctlGridCellBoolEditor *editor = NULL; + dbgArgInfo *arg = NULL, + *prev = NULL; + wxString strName; + + int totalRows = grdParams->GetNumberRows(), + idx = 0; + row = 0; - if ( this->IsModal() ) + while (row < totalRows) { - if ( m_cancelled ) - EndModal( wxID_CANCEL ); + editor = + dynamic_cast( + grdParams->GetCellEditor(row, COL_NULL)); + arg = editor != NULL ? editor->GetArg() : NULL; + strName = grdParams->GetCellValue(row, COL_NAME); + + if (strName.IsEmpty() && arg) + grdParams->SetRowLabelValue(row, wxT("-")); + else if (!strName.IsEmpty() && !arg) + grdParams->SetRowLabelValue(row, wxT("+")); else - EndModal( wxID_OK); + { + idx++; + grdParams->SetRowLabelValue(row, wxString::Format(wxT("%d"), idx)); + } + + row++; } } //////////////////////////////////////////////////////////////////////////////// -// activateDebugger() +// SaveSettings() // -// This function is called after the user has filled in any parameter values -// and clicked the Ok button. activateDebugger() extracts the paramter values -// from the grid control and copies those values into our dbgTargetInfo object -// (m_targetInfo). Next, we set a breakpoint at the target, and, finally, -// we invoke the target function/procedure - -bool dlgDirectDbg::activateDebugger( ) +// Save default values to our .ini file. We save the OID of the most +// recent direct-debugging target when close a session. We also save the +// value of each argument - if you debug the same target again next time, +// loadSettings() will initialize the parameter-values window with the +// same parameter values that you entered in this session. +// +void dlgDirectDbg::SaveSettings() { - // Unset the completed flag (if it exists) - if (m_codeWindow) - m_codeWindow->m_targetComplete = false; - - // Copy the parameter values from the grid into m_targetInfo - int i = 0; - - for( int count = 0; count < m_targetInfo->getArgCount(); ++count ) + wxString strName, strExpr, strVal, strNull, strDef, strKey, strValExpr; + dbgArgInfo *prev = NULL, + *arg = NULL; + int row = 0, + idx = -1, + arrCnt = 1; + + // Save the current function/procedure/trigger OID + settings->WriteLong(wxT("Debugger/Proc/OID"), + m_controller->GetTargetInfo()->GetOid()); + + // Save - whether we need to debug the package constructor + settings->WriteBool(wxT("Debugger/Proc/initialize_package_constructor"), + (chkPkgInit->IsEnabled() && chkPkgInit->GetValue())); + + for (; row < grdParams->GetNumberRows(); row++) { - wsArgInfo &arg = (*m_targetInfo)[count]; + ctlGridCellBoolEditor *editor = + dynamic_cast( + grdParams->GetCellEditor(row, COL_NULL)); + arg = editor != NULL ? editor->GetArg() : NULL; - // Populate the ArgInfo object's IN or INOUT variables only, OUT - // variables will be assigned NULL later on. + // This should be the information for add/remove values for an array + if (arg == NULL) + { + continue; + } + else if (prev != arg) + { + idx++; + prev = arg; - if(arg.getMode() != wxT("o")) + if (arg->IsArray()) + { + arrCnt = 0; + } + else + { + arrCnt = 1; + } + } + else { - arg.setValue( grdParams->GetCellValue(i, COL_VALUE)); - i++; + arrCnt++; } - } - // Write the target OID and argument values to our settings file - // so that we can default them next time around - saveSettings(); + settings->WriteInt(wxString::Format(wxT("Debugger/Proc/%d/ArrCnt"), idx), arrCnt); - // Now set a breakpoint at the target (note: the call to setBreakpoint() - // will hang until the server sends us a response) + if ((arrCnt == 0 && arg->IsArray()) || + (arrCnt == 1 && !arg->IsArray())) + { + // Use Default (if available) + settings->Write(wxString::Format(wxT("Debugger/Proc/%d/USE_DEF"), idx), + grdParams->GetCellValue(row, COL_USE_DEF)); - try - { - // Debug the initialiser. We can only do so once, so unset, and disable - // the option after setting the breakpoint - if( chkPkgInit->GetValue()) - setBreakpoint( m_targetInfo->getPkgOid(), m_targetInfo->getPkgInitOid()); + // Use NULL? + settings->Write(wxString::Format(wxT("Debugger/Proc/%d/NULL"), idx), + grdParams->GetCellValue(row, COL_NULL)); - chkPkgInit->SetValue(false); - chkPkgInit->Disable(); + if (arrCnt == 0 && arg->IsArray()) + continue; + } - setBreakpoint( m_targetInfo->getPkgOid(), m_targetInfo->getOid()); - } - catch( const std::runtime_error &error ) - { - wxMessageBox( wxString( error.what(), wxConvUTF8 ), _( "Cannot create breakpoint" ), wxOK | wxICON_ERROR ); - return( false ); - } + strKey = wxString::Format(wxT("Debugger/Proc/%d/%d/"), idx, arrCnt - 1); - // And invoke the target (note: the call to invokeTarget() will *NOT* - // wait for a result set from the server - instead, OnResultReady() will - // be called when the result set is ready) + // Use NULL? + settings->Write(strKey + wxT("NULL"), + grdParams->GetCellValue(row, COL_NULL)); - try - { - invokeTarget(); - } - catch( const std::runtime_error &error ) - { - wxMessageBox( wxString( error.what(), wxConvUTF8 ), _( "Cannot invoke target" ), wxOK | wxICON_ERROR ); - return( false ); + // Is value EXPR? + settings->Write(strKey + wxT("EXPR"), + grdParams->GetCellValue(row, COL_EXPR)); + + // Value + settings->Write(strKey + wxT("VAL"), + grdParams->GetCellValue(row, COL_VALUE)); } - return( true ); + // Save the number of arguments + settings->WriteLong(wxT("Debugger/Proc/args"), (long)(idx + 1)); } -//////////////////////////////////////////////////////////////////////////////// -// setBreakpoint() -// -// This function creates a breakpoint at the target. For now, we always create -// a breakpoint by calling edb_procoid_debug() or plpgsql_procoid_debug() with -// the OID of the target. Later, we'll change this function to use the -// new CREATE BREAKPOINT command. -void dlgDirectDbg::setBreakpoint(long pkgOid, long funcOid) +ctlGridCellBoolEditor::ctlGridCellBoolEditor(dbgArgInfo *_arg) + : m_arg(_arg) +{} + + +void ctlGridCellBoolEditor::BeginEdit(int _row, int _col, wxGrid *_grid) { - dbgResultset *result; + wxGridCellBoolEditor::BeginEdit(_row, _col, _grid); - if (m_conn->DebuggerApiVersion() <= DEBUGGER_V2_API) - { - if( m_targetInfo->getLanguage() == wxT( "edbspl" )) - result = new dbgResultset(m_conn->waitForCommand(wxString::Format(wxT("select edb_oid_debug( %ld, %ld );"), pkgOid, funcOid))); - else - result = new dbgResultset(m_conn->waitForCommand(wxString::Format(wxT("select plpgsql_oid_debug( %ld, %ld );"), pkgOid, funcOid))); - } - else + wxFocusEvent event (wxEVT_KILL_FOCUS); + if (m_control) { - if( m_targetInfo->getLanguage() == wxT( "edbspl" )) - result = new dbgResultset(m_conn->waitForCommand(wxString::Format(wxT("select edb_oid_debug(%ld);"), funcOid))); - else - result = new dbgResultset(m_conn->waitForCommand(wxString::Format(wxT("select plpgsql_oid_debug(%ld);"), funcOid))); + m_control->GetEventHandler()->AddPendingEvent(event); } - - if( result->getCommandStatus() != PGRES_TUPLES_OK ) - throw( std::runtime_error( result->getRawErrorMessage())); } -//////////////////////////////////////////////////////////////////////////////// -// invokeTarget() -// -// This function invokes the debugger target (that is, the function or procedure -// that the user wants to debug). If the target is a function, we generate a -// SELECT statement; if the target is a procedure, we generate an EXEC statement. -// In either case, we build the argument list from the argument values found -// in m_targetInfo -void dlgDirectDbg::invokeTarget() +wxGrid *dlgDirectDbg::GetParamsGrid() { - // If we have access the the EDB extended libpq functions, - // and this is a stored procedure, we should execute the - // procedure using the callable statement interface to allow - // us to retrieve the OUT/INOUT parameter results. - // Otherwise, just SELECT/EXEC it as per normal. -#ifdef __WXMSW__ - if (!m_conn->EdbMinimumVersion(9, 0) && - !m_targetInfo->getIsFunction() && - PQiGetOutResult && - PQiPrepareOut && - PQiSendQueryPreparedOut) - invokeTargetCallable(); - else -#else -#ifdef EDB_LIBPQ - if (!m_conn->EdbMinimumVersion(9, 0) && - !m_targetInfo->getIsFunction()) - invokeTargetCallable(); - else -#endif -#endif - invokeTargetStatement(); - - // Since parameter window has done its job, we need to hide - // it and let code window come in front. - if (m_codeWindow) - { - m_codeWindow->enableTools(); - m_codeWindow->resumeLocalDebugging(); - } + return grdParams; +} - this->Show( false ); +bool dlgDirectDbg::DebugPkgConstructor() +{ + return chkPkgInit->IsEnabled() && chkPkgInit->GetValue(); } -void dlgDirectDbg::invokeTargetCallable() + +wxGridCellEditor *ctlGridCellBoolEditor::Clone() const { - dbgPgParams *params = new dbgPgParams(); + return new ctlGridCellBoolEditor(m_arg); +} - wxString query = wxT("CALL ") + m_targetInfo->getFQName() + wxT("("); - // Setup the param struct. - params->nParams = m_targetInfo->getArgCount(); - params->paramTypes = new Oid[params->nParams]; - params->paramValues = new char*[params->nParams]; - params->paramModes = new int[params->nParams]; +void dlgDirectDbg::ResultArgsUpdated(wxCommandEvent &_ev) +{ + SaveSettings(); + SavePosition(); - // Iterate through the parameters, adding them to the param - // struct and the statement as we go. - for( int i = 0; i < params->nParams; ++i ) + if (m_thread) { - wsArgInfo &arg = (*m_targetInfo)[i]; + m_thread->CancelEval(); + m_thread->Wait(); - params->paramTypes[i] = arg.getTypeOid(); + delete m_thread; + m_thread = NULL; + } - if(arg.getMode() == wxT("o")) // OUT - { - params->paramModes[i] = 2; - params->paramValues[i] = 0; - } - else if(arg.getMode() == wxT("b")) // IN OUT - { - params->paramModes[i] = 3; - - int len = arg.getValue().Length() + 1; - char *tmp = new char[len]; - snprintf(tmp, len, "%s", (const char *)arg.getValue().mb_str(wxConvUTF8)); - if (strcmp(tmp, "") == 0) - params->paramValues[i] = 0; - else if (strcmp(tmp, "''") == 0) - params->paramValues[i] = (char *)""; - else if (strcmp(tmp, "\\'\\'") == 0) - params->paramValues[i] = (char *)"''"; - else - params->paramValues[i] = tmp; - } - else // IN + if (IsModal()) + EndModal(wxID_OK); + else + Destroy(); +} + + +void dlgDirectDbg::ResultArgsUpdateError(wxCommandEvent &_ev) +{ + if (_ev.GetInt() == pgQueryResultEvent::PGQ_CONN_LOST) + { + if(wxMessageBox( + _("Connection to the database server lost!\nDo you want to try to reconnect to the server?"), + _("Connection Lost"), wxICON_ERROR | wxICON_QUESTION | wxYES_NO) == wxID_YES) { - params->paramModes[i] = 1; - - int len = arg.getValue().Length() + 1; - char *tmp = new char[len]; - snprintf(tmp, len, "%s", (const char *)arg.getValue().mb_str(wxConvUTF8)); - if (strcmp(tmp, "") == 0) - params->paramValues[i] = 0; - else if (strcmp(tmp, "''") == 0) - params->paramValues[i] = (char *)""; - else if (strcmp(tmp, "\\'\\'") == 0) - params->paramValues[i] = (char *)"''"; - else - params->paramValues[i] = tmp; + if (m_thread) + { + m_thread->CancelEval(); + + m_thread->Wait(); + + delete m_thread; + m_thread = NULL; + } + m_conn->Reconnect(); + + return; } + EndModal(wxID_CANCEL); - if (i) - query += wxT(", "); - query += wxString::Format(wxT("$%d::"), i + 1) + arg.getType(); + return; + } + + if (m_thread) + { + m_thread->CancelEval(); + m_thread->Wait(); + delete m_thread; + m_thread = NULL; } - query += wxT(");"); + wxLogError(_ev.GetString()); - // And send the completed command to the server - we'll get - // a dbgDbResult event when the command completes (and that - // event will get routed to dlgDirectDbg::OnResultReady()) - m_conn->startCommand( query, GetEventHandler(), RESULT_ID_DIRECT_TARGET_COMPLETE, params ); + grdParams->Enable(true); + btnDebug->Enable(true); } -void dlgDirectDbg::invokeTargetStatement() + +void dbgArgValueEvaluator::NoticeHandler(void *, const char *) { - wxString query, declareStatement; - if (!m_conn->EdbMinimumVersion(8, 4)) - query = m_targetInfo->getIsFunction() ? wxT( "SELECT " ) : wxT( "EXEC " ); - else - { - query = wxT("SELECT"); - if (!m_targetInfo->getIsFunction()) - query = wxT("EXEC "); - else if (m_targetInfo->getLanguage() == wxT("edbspl")) - query = wxT("PERFORM "); - } + // Ignore the notices +} - // If this is a function, and the return type is not record, or - // we have at least one OUT/INOUT param, we should select from - // the function to get a full resultset. - if (m_targetInfo->getIsFunction() && m_targetInfo->getLanguage() != wxT("edbspl") && - (m_targetInfo->getReturnType() != wxT("record") || - m_targetInfo->getArgInOutCount() > 0 || - m_targetInfo->getArgOutCount() > 0)) - query.Append(wxT("* FROM ")); - // Stuff the verb (SELECT or EXEC), schema, and target name into the query - query.Append(m_targetInfo->getFQName()); +void *dbgArgValueEvaluator::Entry() +{ + bool argNull, nullVal, useDef; + wxString strVal, strValExpr; + dbgArgInfo *prev = NULL, + *arg = NULL; + + wxGrid *grd = m_dlg->GetParamsGrid(); - // Now append the argument list - query.Append(wxT("(")); + m_dlg->m_controller->GetTargetInfo()->DebugPackageConstructor() + = m_dlg->DebugPkgConstructor(); - for(int i = 0; i < m_targetInfo->getArgCount(); ++i) + m_conn->RegisterNoticeProcessor(dbgArgValueEvaluator::NoticeHandler, NULL); + + for (int row = 0, idx = 0, arrCnt = 1; + row < grd->GetNumberRows() && !m_cancelled; row++) { - wsArgInfo &arg = (*m_targetInfo)[i]; + ctlGridCellBoolEditor *editor = + dynamic_cast( + grd->GetCellEditor(row, dlgDirectDbg::COL_NULL)); + arg = editor != NULL ? editor->GetArg() : NULL; - if(arg.getMode() == wxT("o") && !m_conn->EdbMinimumVersion(8, 4)) - { - if (!m_targetInfo->getIsFunction() || m_targetInfo->getLanguage() == wxT("edbspl")) - query.Append( wxT("NULL::") + arg.getType() + wxT(", ")); - } - else if (m_conn->EdbMinimumVersion(8, 4) && (arg.getMode() == wxT("o") || arg.getMode() == wxT("b"))) + // This should be the information for add/remove values for an array + if (arg == NULL) { - if (!m_targetInfo->getIsFunction() || m_targetInfo->getLanguage() == wxT("edbspl")) + // prev was an array, can we fetch value for the same + if (prev && !prev->Null()) { - wxString strParam = wxString::Format(wxT("param%d"), i); - declareStatement += strParam + wxT(" ") + arg.getType(); - if (arg.getMode() == wxT("b")) + wxString res = + m_conn->ExecuteScalar( + wxT("SELECT ARRAY[") + strValExpr + wxT("]::") + prev->GetTypeName(), + false); + + if (m_cancelled) { - declareStatement += wxT(" := ") + arg.quoteValue(); - if (!arg.quoteValue().Contains(wxT("::"))) - declareStatement += wxT("::") + arg.getType(); + m_conn->RegisterNoticeProcessor(NULL, NULL); + + return (void *)NULL; } - declareStatement += wxT(";\n"); - query.Append(strParam + wxT(", ")); - } - else if (arg.getMode() != wxT("o")) - { - if (!arg.quoteValue().Contains(wxT("::"))) - query.Append( arg.quoteValue() + wxT("::") + arg.getType() + wxT(", ")); - else - query.Append( arg.quoteValue() + wxT(", ")); - } - } - else if(arg.getMode() == wxT("v")) - query.Append( arg.getValue() + wxT(", ")); - else - { - if (!arg.quoteValue().Contains(wxT("::"))) - query.Append( arg.quoteValue() + wxT("::") + arg.getType() + wxT(", ")); - else - query.Append( arg.quoteValue() + wxT(", ")); - } - } + if (m_conn->GetStatus() == PGCONN_BAD) + { + wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED, RESULT_ID_ARGS_UPDATE_ERROR); - if (query.EndsWith(wxT(", "))) - query = query.Left(query.Length() - 2); - else if (query.EndsWith(wxT("("))) - query = query.Left(query.Length() - 1); + ev.SetInt(pgQueryResultEvent::PGQ_CONN_LOST); - // And terminate the argument list - if(m_targetInfo->getArgInCount() + m_targetInfo->getArgInOutCount() == 0) - { - /* - * edbspl function/procedure with OUT parameter takes value/variable as an input - */ - if (m_conn->GetIsEdb()) - { - if (m_targetInfo->getArgCount() == 0) - { - if (m_targetInfo->getIsFunction() || m_targetInfo->getLanguage() != wxT("edbspl")) - query.Append(wxT("()")); - } - else if (m_targetInfo->getLanguage() != wxT("edbspl")) - query.Append(wxT("()")); - else - query.Append(wxT(")")); - } - else if (m_targetInfo->getIsFunction()) - query.Append(wxT("()")); - } - else - { - query.Append(wxT(")")); - } + m_dlg->GetEventHandler()->AddPendingEvent(ev); - if (m_conn->EdbMinimumVersion(8, 4) && (m_targetInfo->getLanguage() == wxT("edbspl") || !m_targetInfo->getIsFunction())) - { - wxString tmpQuery = wxT("DECLARE\n") - + declareStatement - + wxT("BEGIN\n") - + query + wxT(";\n") - + wxT("END;"); - query = tmpQuery; - } + m_conn->RegisterNoticeProcessor(NULL, NULL); - // And send the completed command to the server - we'll get - // a dbgDbResult event when the command completes (and that - // event will get routed to dlgDirectDbg::OnResultReady()) - m_conn->startCommand( query, GetEventHandler(), RESULT_ID_DIRECT_TARGET_COMPLETE ); -} + return (void *)NULL; + } + if (m_conn->GetLastResultStatus() == PGRES_TUPLES_OK) + { + prev->Value() = res; + } + else + { + wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED, RESULT_ID_ARGS_UPDATE_ERROR); + wxString strName = prev->GetName(); + wxString strMsg; + + if (strName.IsEmpty()) + { + ev.SetString( + wxString::Format( + _("Specified value for the argument#%d is not valid.\nPlease reenter the value for it."), + idx)); + } + else + { + ev.SetString( + wxString::Format( + _("Specified value for the argument - '%s' is not valid.\nPlease reenter the value for it."), + strName.c_str())); + } + ev.SetInt(pgQueryResultEvent::PGQ_RESULT_ERROR); + + m_dlg->GetEventHandler()->AddPendingEvent(ev); + + m_conn->RegisterNoticeProcessor(NULL, NULL); + + return (void *)NULL; + } + prev = NULL; + } -//////////////////////////////////////////////////////////////////////////////// -// OnTargetComplete() -// -// This event handler is called when the target function/procedure completes -// and a result set (or error) has been returned by the server. The event -// object contains a pointer to the result set. -// -// For now, we display an error message (if an error occurred) or write the -// command status to the status bar (if the target completed without error). -// -// We should really display the complete result set somewhere too. + continue; + } + else if (prev != arg) + { + idx++; + argNull = false; + nullVal = false; + useDef = false; -void dlgDirectDbg::OnTargetComplete( wxCommandEvent &event ) -{ - // Extract the result set handle from the event and log the status info + strValExpr = wxEmptyString; + } - PGresult *result = (PGresult *)event.GetClientData(); + if (prev != arg) + { + prev = arg; - wxLogInfo( wxT( "OnTargetComplete() called\n" )); - wxLogInfo( wxT( "%s\n" ), wxString(PQresStatus( PQresultStatus( result )), wxConvUTF8).c_str()); + // Use Default (if available) + arg->UseDefault() = wxGridCellBoolEditor::IsTrueValue( + grd->GetCellValue(row, dlgDirectDbg::COL_USE_DEF)); + // NULL? + arg->Null() = wxGridCellBoolEditor::IsTrueValue( + grd->GetCellValue(row, dlgDirectDbg::COL_NULL)); - // If the query failed, write the error message to the status line, otherwise, copy the result set into the grid - if(( PQresultStatus( result ) == PGRES_NONFATAL_ERROR ) || ( PQresultStatus( result ) == PGRES_FATAL_ERROR )) - { - wxString message( PQresultErrorMessage( result ), wxConvUTF8 ); + if (arg->UseDefault()) + strValExpr = arg->Default(); - message.Replace( wxT( "\n" ), wxT( " " )); + if (arg->IsArray()) + continue; + } - m_parent->getStatusBar()->SetStatusText( message, 1 ); - char *state = PQresultErrorField(result, PG_DIAG_SQLSTATE); + // Use NULL? + // + // Keep this just before 'Use Default' column as - we may have non-empty + // value in the 'VAL' column - // Don't bother telling the user that he aborted - he already knows! - // Depending on the stage, m_conn might not be set all! so check for - // that first - if (m_conn) + if (!arg->UseDefault() || !arg->Null()) { - if (state != NULL && strcmp(state, "57014")) - wxLogError( wxT( "%s\n" ), wxString(PQerrorMessage(m_conn->getConnection()), wxConvUTF8).c_str()); + if (!strValExpr.IsEmpty()) + { + strValExpr += wxT(", "); + } + + if (wxGridCellBoolEditor::IsTrueValue( + grd->GetCellValue(row, dlgDirectDbg::COL_NULL))) + { + strValExpr += wxT("NULL"); + } + else if (wxGridCellBoolEditor::IsTrueValue( + grd->GetCellValue(row, dlgDirectDbg::COL_EXPR))) + { + strValExpr += grd->GetCellValue(row, dlgDirectDbg::COL_VALUE); + } else - wxLogInfo( wxT( "%s\n" ), wxString(PQerrorMessage(m_conn->getConnection()), wxConvUTF8).c_str()); + { + strValExpr += m_conn->qtDbString( + grd->GetCellValue(row, dlgDirectDbg::COL_VALUE)); + } + strValExpr.Append(wxT("::")) + .Append(arg->IsArray() ? + arg->GetBaseType() : arg->GetTypeName()); } - } - else - { - wxString message( PQcmdStatus( result ), wxConvUTF8 ); + if (!arg->IsArray() && !arg->Null()) + { + pgSet *set = + m_conn->ExecuteSet(wxT("SELECT ") + strValExpr, false); - message.Replace( wxT( "\r" ), wxT( "" )); - message.Replace( wxT( "\n" ), wxT( " " )); + if (m_cancelled) + { + return (void *)NULL; + } + if (m_conn->GetStatus() == PGCONN_BAD) + { + wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED, RESULT_ID_ARGS_UPDATE_ERROR); - m_parent->getStatusBar()->SetStatusText( message, 1 ); + ev.SetString(_("Connection to the database server lost!")); + ev.SetInt(pgQueryResultEvent::PGQ_CONN_LOST); - // If this result set has any columns, add a result grid to the code window so - // we can show the results to the user + m_dlg->GetEventHandler()->AddPendingEvent(ev); - if( m_codeWindow && PQnfields( result )) - { - m_codeWindow->OnResultSet( result ); - } - } + m_conn->RegisterNoticeProcessor(NULL, NULL); - if (m_codeWindow) - { - m_codeWindow->m_targetComplete = true; - m_codeWindow->disableTools( ); - } + if (set) + delete set; - // Do not show if aborted - if ( m_codeWindow && m_codeWindow->m_targetAborted ) - return; + return (void *)NULL; + } + if (m_conn->GetLastResultStatus() == PGRES_TUPLES_OK && + set && set->NumRows() > 0L) + { + if (set->IsNull(0)) + { + arg->Null() = true; + } + else + { + arg->Null() = false; + arg->Value() = set->GetVal(0); + } + } + else + { + wxString strName = arg->GetName(); - this->Show( true ); -} + if (strName.IsEmpty()) + strName = wxString::Format(_("Param#%d"), idx); -//////////////////////////////////////////////////////////////////////////////// -// OnNoticeReceived() -// -// This event handler is called when a notice is received from the server (in -// response to our invoking the target). For now, we just forward this event -// to the debugger window (m_codeWindow) and the notification message is added -// to the debugger's message window. -// -// When/if we get around to adding a result set window to this class, we should -// also add a message window too and display notice messages here instead of in -// the debugger window. + wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED, RESULT_ID_ARGS_UPDATE_ERROR); -void dlgDirectDbg::OnNoticeReceived( wxCommandEvent &event ) -{ - if( m_codeWindow ) - m_codeWindow->OnNoticeReceived( event ); -} + ev.SetString( + wxString::Format(_("Please reenter the value for the argument - '%s'."), + strName.c_str())); + ev.SetInt(pgQueryResultEvent::PGQ_RESULT_ERROR); -//////////////////////////////////////////////////////////////////////////////// -// OnDebug() -// -// This event handler is called when a PLDBGBREAK notice is received from the -// server. A quick review: we've already set a breakpoint at the target and -// then we invoked the target (using the parameter values entered by the user). -// Now we're waiting for a result set from the target. Since we set a breakpoint -// inside of the target, the server will send us a specially crafted NOTICE -// that tells use which port to attach to in order to contact the debugger -// server - that's what 'event' contains. -// -// When we get the PLDBGBREAK message (inside of 'event'), we create a new -// debugger window by calling glMainFrame->addDebug() and let that window -// take over for a while. When the target finally completes, we'll get a -// a dbgDbResult event and handle the result set inside of OnResultReady() + m_dlg->GetEventHandler()->AddPendingEvent(ev); -void dlgDirectDbg::OnDebug( wxCommandEvent &event ) -{ - // This event contains a string of the form: - // /path/debugger -k --database=db --host=host --port=port --user=user &" - // We can use that string to launch a separate debugger client. - // - // The event also contains a pointer to a map that contains keyword=value - // pairs for the debugger connection properties. To get to that map, we - // call event.GetClientData(). Once we have the map, we can look for the - // debugger connection properties such as "database", "host", "port", ... + m_conn->RegisterNoticeProcessor(NULL, NULL); + + return (void *)NULL; - dbgConnProp *debugProps = (dbgConnProp *)event.GetClientData(); + } + } + } - m_codeWindow = m_parent->addDebug( *debugProps ); + wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED, RESULT_ID_ARGS_UPDATED); + m_dlg->GetEventHandler()->AddPendingEvent(ev); - if (m_codeWindow) - m_codeWindow->startLocalDebugging(); + m_conn->RegisterNoticeProcessor(NULL, NULL); - this->Show( false ); + return (void *)NULL; } -//////////////////////////////////////////////////////////////////////////////// -// getBreakpointList() -// -// This function returns a non-const reference to our breakpoint list. The -// caller typically populates this list before calling startDebugging() - we -// set a breakpoint for each member of the list -dbgBreakPointList &dlgDirectDbg::getBreakpointList() +void dbgArgValueEvaluator::CancelEval() { - return( m_breakpoints ); + m_cancelled = true; + + if (m_conn->GetTxStatus() == PGCONN_TXSTATUS_ACTIVE) + m_conn->CancelExecution(); } +dbgArgValueEvaluator::dbgArgValueEvaluator(pgConn *_conn, dlgDirectDbg *_dlg) + : wxThread(wxTHREAD_JOINABLE), m_conn(_conn), m_dlg(_dlg), m_cancelled(false) +{} diff --git a/pgadmin/debugger/frmDebugger.cpp b/pgadmin/debugger/frmDebugger.cpp index 19f2824..90893da 100644 --- a/pgadmin/debugger/frmDebugger.cpp +++ b/pgadmin/debugger/frmDebugger.cpp @@ -13,15 +13,19 @@ // wxWindows headers #include +#include #include // App headers -#include "debugger/frmDebugger.h" -#include "debugger/ctlCodeWindow.h" -#include "debugger/dbgPgConn.h" -#include "debugger/dlgDirectDbg.h" #include "ctl/ctlMenuToolbar.h" +#include "ctl/ctlSQLBox.h" +#include "ctl/ctlAuiNotebook.h" +#include "debugger/dbgController.h" +#include "debugger/ctlTabWindow.h" +#include "debugger/frmDebugger.h" +#include "frm/frmMain.h" +#include "debugger/dbgConst.h" #include "images/debugger.pngc" #include "images/clearAll.pngc" @@ -31,21 +35,22 @@ #include "images/stepInto.pngc" #include "images/stop.pngc" -IMPLEMENT_CLASS( frmDebugger, pgFrame ) +IMPLEMENT_CLASS(frmDebugger, pgFrame) -BEGIN_EVENT_TABLE( frmDebugger, pgFrame ) - EVT_MENU(MNU_EXIT, frmDebugger ::OnExit) - - EVT_MENU_RANGE(MENU_ID_TOGGLE_BREAK, MENU_ID_STOP, frmDebugger::OnDebugCommand) +BEGIN_EVENT_TABLE(frmDebugger, pgFrame) EVT_CLOSE(frmDebugger::OnClose) EVT_SIZE(frmDebugger::OnSize) EVT_ERASE_BACKGROUND(frmDebugger::OnEraseBackground) + EVT_AUI_PANE_CLOSE(frmDebugger::OnAuiUpdate) + + EVT_MENU_RANGE(MENU_ID_TOGGLE_BREAK, MENU_ID_STOP, frmDebugger::OnDebugCommand) EVT_STC_MARGINCLICK(wxID_ANY, frmDebugger::OnMarginClick) EVT_STC_UPDATEUI(wxID_ANY, frmDebugger::OnPositionStc) EVT_LISTBOX(wxID_ANY, frmDebugger::OnSelectFrame) EVT_GRID_CELL_CHANGE( frmDebugger::OnVarChange) + EVT_MENU(MNU_EXIT, frmDebugger ::OnExit) EVT_MENU(MENU_ID_VIEW_TOOLBAR, frmDebugger::OnToggleToolBar) EVT_MENU(MENU_ID_VIEW_STACKPANE, frmDebugger::OnToggleStackPane) EVT_MENU(MENU_ID_VIEW_OUTPUTPANE, frmDebugger::OnToggleOutputPane) @@ -53,8 +58,7 @@ BEGIN_EVENT_TABLE( frmDebugger, pgFrame ) EVT_MENU(MNU_CONTENTS, frmDebugger::OnContents) EVT_MENU(MNU_HELP, frmDebugger::OnHelp) - - EVT_AUI_PANE_CLOSE(frmDebugger::OnAuiUpdate) + EVT_TIMER(ID_TIMER, frmDebugger::OnTimer) END_EVENT_TABLE() //////////////////////////////////////////////////////////////////////////////// @@ -63,63 +67,81 @@ END_EVENT_TABLE() // frmDebugger manages the user interface for the workstation. This class // manages the toolbar, menu, status bar, and top-level windows. -frmDebugger::frmDebugger(frmMain *parent, const wxString &title) - : pgFrame(NULL, title), - m_standaloneDebugger(NULL), - m_standaloneDirectDbg(NULL), - m_parent(parent) +frmDebugger::frmDebugger(frmMain *_parent, dbgController *_controller, + const wxString &_title) : pgFrame(_parent, _title), m_menuBar(NULL), + m_toolBar(NULL), m_viewMenu(NULL), m_debugMenu(NULL), m_statusBar(NULL), + m_parent(_parent), m_controller(_controller), m_stackWindow(NULL), + m_tabWindow(NULL), m_codeViewer(NULL), m_progressBar(NULL), m_timer(this, ID_TIMER) { dlgName = wxT("frmDebugger"); RestorePosition(100, 100, 600, 500, 450, 300); wxWindowBase::SetFont(settings->GetSystemFont()); - manager.SetManagedWindow(this); - manager.SetFlags(wxAUI_MGR_DEFAULT | wxAUI_MGR_TRANSPARENT_DRAG); + m_manager.SetManagedWindow(this); + m_manager.SetFlags(wxAUI_MGR_DEFAULT | wxAUI_MGR_TRANSPARENT_DRAG); // Define the icon for this window SetIcon(*debugger_png_ico); // Create (and configure) the menu bar, toolbar, and status bar - m_menuBar = setupMenuBar(); - m_toolBar = setupToolBar(); - m_statusBar = setupStatusBar(); + m_menuBar = SetupMenuBar(); + m_toolBar = SetupToolBar(); + m_statusBar = SetupStatusBar(); - manager.AddPane(m_toolBar, wxAuiPaneInfo().Name(wxT("toolBar")).Caption(wxT("Toolbar")).ToolbarPane().Top().Row(1).Position(1).LeftDockable(false).RightDockable(false)); + m_manager.AddPane( + m_toolBar, + wxAuiPaneInfo().Name(wxT("toolBar")).Caption(wxT("Toolbar")) + .ToolbarPane().Top().Row(1).Position(1).LeftDockable(false) + .RightDockable(false)); // Now load the layout wxString perspective; - settings->Read(wxT("Debugger/frmDebugger/Perspective-") + wxString(FRMDEBUGGER_PERSPECTIVE_VER), &perspective, FRMDEBUGGER_DEFAULT_PERSPECTIVE); - manager.LoadPerspective(perspective, true); + settings->Read( + wxT("Debugger/frmDebugger/Perspective-") + + wxString(FRMDEBUGGER_PERSPECTIVE_VER), + &perspective, FRMDEBUGGER_DEFAULT_PERSPECTIVE); + + m_manager.LoadPerspective(perspective, true); // and reset the captions for the current language - manager.GetPane(wxT("toolBar")).Caption(_("Toolbar")); + m_manager.GetPane(wxT("toolBar")).Caption(_("Toolbar")); // Sync the View menu options - m_viewMenu->Check(MENU_ID_VIEW_TOOLBAR, manager.GetPane(wxT("toolBar")).IsShown()); + m_viewMenu->Check(MENU_ID_VIEW_TOOLBAR, m_manager.GetPane(wxT("toolBar")).IsShown()); - manager.Update(); + SetupDebugger(); + m_manager.Update(); } + frmDebugger::~frmDebugger() { // Only save the settings if the window was completely setup // This may not be the case if the params dialog was displayed, // and the user hit cancel before the main form opened. - wxAuiPaneInfo &pane = manager.GetPane(wxT("sourcePane")); + wxAuiPaneInfo &pane = m_manager.GetPane(wxT("sourcePane")); if (pane.IsOk()) - settings->Write(wxT("Debugger/frmDebugger/Perspective-") + wxString(FRMDEBUGGER_PERSPECTIVE_VER), manager.SavePerspective()); + settings->Write(wxT("Debugger/frmDebugger/Perspective-") + wxString(FRMDEBUGGER_PERSPECTIVE_VER), m_manager.SavePerspective()); - manager.UnInit(); + m_manager.UnInit(); if (m_parent) m_parent->RemoveFrame(this); + + if (m_controller) + { + PopEventHandler(); + delete m_controller; + m_controller = NULL; + } } + //////////////////////////////////////////////////////////////////////////////// -// onContents() -// onHelp() +// OnContents() +// OnHelp() // // Help menu options void frmDebugger::OnContents(wxCommandEvent &event) @@ -127,106 +149,486 @@ void frmDebugger::OnContents(wxCommandEvent &event) DisplayHelp(wxT("debugger"), HELP_PGADMIN); } + void frmDebugger::OnHelp(wxCommandEvent &event) { DisplayHelp(wxT("plpgsql"), HELP_POSTGRESQL); } + //////////////////////////////////////////////////////////////////////////////// -// addDebug() +// SetupDebugger() +// +// This function creates the debugger views... // -// This function creates a new debugger window... +void frmDebugger::SetupDebugger() +{ + wxWindowBase::SetFont(settings->GetSystemFont()); + + // Initializing Stack Frame Window + if (m_stackWindow == NULL) + { + m_stackWindow = new ctlStackWindow(this, WINDOW_ID_STACK, wxDefaultPosition, + wxDefaultSize, 0); + + m_manager.AddPane(m_stackWindow, + wxAuiPaneInfo().Name(wxT("stackPane")).Caption(_("Stack pane")).Right() + .MinSize(wxSize(100, 100)).BestSize(wxSize(250, 200))); + } + // Initializing Tab Window + if (m_tabWindow == NULL) + { + m_tabWindow = new ctlTabWindow(this, WINDOW_ID_TABS, wxDefaultPosition, + wxDefaultSize, wxAUI_NB_BOTTOM | wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_WINDOWLIST_BUTTON); -ctlCodeWindow *frmDebugger::addDebug( const dbgConnProp &connProps ) + m_manager.AddPane(m_tabWindow, + wxAuiPaneInfo().Name(wxT("outputPane")).Caption(_("Output pane")).Bottom() + .MinSize(wxSize(200, 100)).BestSize(wxSize(550, 300))); + } + // Initialing Code viewer Window + if (m_codeViewer == NULL) + { + m_codeViewer = new ctlSQLBox(this, -1); + + // Set up the markers that we use to indicate the current line and + // the break-point + m_codeViewer->MarkerDefine(MARKER_CURRENT, wxSTC_MARK_ARROW, *wxGREEN, + *wxGREEN); + m_codeViewer->MarkerDefine(MARKER_CURRENT_BG, wxSTC_MARK_BACKGROUND, *wxGREEN, + *wxGREEN); + m_codeViewer->MarkerDefine(MARKER_BREAKPOINT, wxSTC_MARK_CIRCLEPLUS, *wxRED, + *wxRED); + + m_codeViewer->SetMarginWidth(1, 16); + m_codeViewer->SetMarginType(1, wxSTC_MARGIN_SYMBOL); + + // Make sure that the text control tells us when the user clicks in the + // left margin + m_codeViewer->SetMarginSensitive(0, true); + m_codeViewer->SetMarginSensitive(1, true); + m_codeViewer->SetMarginSensitive(2, true); + + m_manager.AddPane(m_codeViewer, + wxAuiPaneInfo().Name(wxT("sourcePane")).Caption(_("Source pane")).Center() + .CaptionVisible(false).CloseButton(false).MinSize(wxSize(200, 100)) + .BestSize(wxSize(350, 200))); + } + + // Make sure the user can't edit the source code for this function + m_codeViewer->SetReadOnly(true); + + // Now (re)load the layout + wxString perspective; + settings->Read( + wxT("Debugger/frmDebugger/Perspective-") + + wxString(FRMDEBUGGER_PERSPECTIVE_VER), &perspective, + FRMDEBUGGER_DEFAULT_PERSPECTIVE); + m_manager.LoadPerspective(perspective, true); + + // And reset the captions + m_manager.GetPane(wxT("sourcePane")).Caption(_("Source pane")); + m_manager.GetPane(wxT("stackPane")).Caption(_("Stack pane")); + m_manager.GetPane(wxT("outputPane")).Caption(_("Output pane")); + + // Sync the View menu options + m_viewMenu->Check(MENU_ID_VIEW_STACKPANE, + m_manager.GetPane(wxT("stackPane")).IsShown()); + m_viewMenu->Check(MENU_ID_VIEW_OUTPUTPANE, + m_manager.GetPane(wxT("outputPane")).IsShown()); + + // Enable the options for these controls + m_viewMenu->Enable(MENU_ID_VIEW_OUTPUTPANE, true); + m_viewMenu->Enable(MENU_ID_VIEW_STACKPANE, true); + + m_manager.Update(); + + // force + EnableToolsAndMenus(false); + + PushEventHandler(m_controller); +} + + +//////////////////////////////////////////////////////////////////////////////// +// EnableToolsAndMenus +// +// Enable or disable the debugging tools and menus +// +// Parameters: +// - enable/disable (boolean) +// +void frmDebugger::EnableToolsAndMenus(bool enable) { - try + if (m_toolBar) { - m_standaloneDebugger = new ctlCodeWindow( this , -1, connProps ); - m_standaloneDebugger->Show( false ); - return( m_standaloneDebugger ); + m_toolBar->EnableTool(MENU_ID_STEP_INTO, enable); + m_toolBar->EnableTool(MENU_ID_STEP_OVER, enable); + m_toolBar->EnableTool(MENU_ID_CONTINUE, + (enable || + (m_controller && m_controller->CanRestart()))); + m_toolBar->EnableTool(MENU_ID_TOGGLE_BREAK, enable); + m_toolBar->EnableTool(MENU_ID_CLEAR_ALL_BREAK, enable); + m_toolBar->EnableTool(MENU_ID_STOP, enable); } - catch( const std::runtime_error &error ) + + if (m_debugMenu) { - wxLogError(wxT("%s"), wxString(error.what(), wxConvUTF8).c_str()); - return NULL; + m_debugMenu->Enable(MENU_ID_STEP_INTO, enable); + m_debugMenu->Enable(MENU_ID_STEP_OVER, enable); + m_debugMenu->Enable(MENU_ID_CONTINUE, + (enable || + (m_controller && m_controller->CanRestart()))); + m_debugMenu->Enable(MENU_ID_TOGGLE_BREAK, enable); + m_debugMenu->Enable(MENU_ID_CLEAR_ALL_BREAK, enable); + m_debugMenu->Enable(MENU_ID_STOP, enable); + } + if ((!m_controller || m_controller->IsTerminated())) + { + m_timer.Stop(); } } -dlgDirectDbg *frmDebugger::addDirectDbg( const dbgConnProp &connProp ) + +//////////////////////////////////////////////////////////////////////////////// +// GetLineNo +// +// This function returns the current line-number, where the execution flow +// has been stopped +// +// Returns: +// Current line-number +// +int frmDebugger::GetLineNo() { - try + if (m_codeViewer) { - m_standaloneDirectDbg = new dlgDirectDbg( this, -1, connProp ); - m_standaloneDirectDbg->setupParamWindow(); - return( m_standaloneDirectDbg ); + return (m_codeViewer->LineFromPosition(m_codeViewer->GetCurrentPos())); } - catch( const std::runtime_error &error ) + else { - wxLogError(wxT("%s"), wxString(error.what(), wxConvUTF8).c_str()); - return NULL; + return -1; } } + //////////////////////////////////////////////////////////////////////////////// -// OnDebugCommand() +// IsBreakpoint(int) // -// This event handler is invoked when the user clicks one of the debugger -// tools (on the debugger toolbar) - we simply forward the event to the -// debugger window. +// This function helps checking if break-point is set on the particular line +// +// Parameters: +// - line-number +// +// Returns: +// - True if a break-point is set on the line-no, otherwise false +// +bool frmDebugger::IsBreakpoint(int _lineNo) +{ + if (m_codeViewer) + return (m_codeViewer->MarkerGet(_lineNo) & + MARKERINDEX_TO_MARKERMASK(MARKER_BREAKPOINT) ? true : false); + else + return false; +} + -void frmDebugger::OnDebugCommand( wxCommandEvent &event ) +//////////////////////////////////////////////////////////////////////////////// +// ClearAllBreakpoints +// +// This function clears all the break-points +// +void frmDebugger::ClearAllBreakpoints() { - if (m_standaloneDebugger) - m_standaloneDebugger->OnCommand( event ); + int lineNo = 0; + bool updateBreakpoints = false; + + if (m_codeViewer) + { + while((lineNo = m_codeViewer->MarkerNext(lineNo, + MARKERINDEX_TO_MARKERMASK(MARKER_BREAKPOINT))) != -1) + { + // Clear the break-point at particular location + m_controller->ClearBreakpoint(lineNo); + updateBreakpoints = true; + } + + // Update break-points only if required + if (updateBreakpoints) + m_controller->UpdateBreakpoints(); + } } + +void frmDebugger::ClearBreakpointMarkers() +{ + int lineNo = 0; + if (m_codeViewer) + { + while((lineNo = m_codeViewer->MarkerNext(lineNo, + MARKERINDEX_TO_MARKERMASK(MARKER_BREAKPOINT))) != -1) + { + m_codeViewer->MarkerDelete(lineNo++, MARKER_BREAKPOINT); + } + } +} + + +void frmDebugger::MarkBreakpoint(int _line) +{ + if (m_codeViewer) + { + m_codeViewer->MarkerAdd(_line, MARKER_BREAKPOINT); + } +} + + //////////////////////////////////////////////////////////////////////////////// -// OnSelectFrame() +// UnhilightCurrentLine // -// This event handler is invoked when the user clicks one of the stack frames -// - we simply forward the event to the debugger window. +// This function removes the hilight from the current line +// +void frmDebugger::UnhilightCurrentLine() +{ + if (m_codeViewer) + { + int lineNo + = m_codeViewer->MarkerNext(0, MARKERINDEX_TO_MARKERMASK(MARKER_CURRENT)); + + if(lineNo != -1) + { + m_codeViewer->MarkerDelete(lineNo, MARKER_CURRENT); + m_codeViewer->MarkerDelete(lineNo, MARKER_CURRENT_BG); + } + } +} + -void frmDebugger::OnSelectFrame( wxCommandEvent &event ) +//////////////////////////////////////////////////////////////////////////////// +// OnDebugCommand(wxCommandEvent) +// +// This function handles the user clicks one of the debugger tools and menus +// and take care of actions requires. These events are toggle break-point, +// clear all break-points, debugging continue, step-in, step-over and stop +// debugging. +// +// Parameters +// - event object +// +void frmDebugger::OnDebugCommand(wxCommandEvent &_event) { - if (m_standaloneDebugger) - m_standaloneDebugger->OnSelectFrame( event ); + switch(_event.GetId()) + { + case MENU_ID_TOGGLE_BREAK: + { + int lineNo = GetLineNo(); + + // This event should have not been called + if (lineNo == -1) + return; + + // The user wants to set or clear a breakpoint at the line that + // contains the insertion point (the caret) + if (IsBreakpoint(lineNo)) + { + m_controller->ClearBreakpoint(lineNo); + } + else + { + m_controller->SetBreakpoint(lineNo); + } + m_controller->UpdateBreakpoints(); + } + break; + + case MENU_ID_CLEAR_ALL_BREAK: + // The user wants to clear all the breakpoint + ClearAllBreakpoints(); + + break; + + case MENU_ID_CONTINUE: + // The user wants to continue execution (as opposed to + // single-stepping through the code). Unhilite all + // variables and tell the debugger server to continue. + if (m_controller) + { + if (m_controller->CanRestart()) + { + m_controller->Start(); + } + else + { + LaunchWaitingDialog(_("Waiting for target (continue)...")); + m_controller->Countinue(); + UnhilightCurrentLine(); + EnableToolsAndMenus(false); + } + } + break; + + case MENU_ID_STEP_OVER: + // The user wants to step-over a function invocation (or + // just single-step). Unhilite all variables and tell the + // debugger server to step-over + LaunchWaitingDialog(_("Waiting for target (step over)...")); + m_controller->StepOver(); + + GetStatusBar()->SetStatusText(_("Waiting for target (step over)..."), 1); + UnhilightCurrentLine(); + EnableToolsAndMenus(false); + + break; + + case MENU_ID_STEP_INTO: + // The user wants to step-into a function invocation (or + // just single-step). Unhilite all variables and tell the + // debugger server to step-into + LaunchWaitingDialog(_("Waiting for target (step over)...")); + m_controller->StepInto(); + + GetStatusBar()->SetStatusText(_("Waiting for target (step into)..."), 1); + UnhilightCurrentLine(); + EnableToolsAndMenus(false); + + break; + + case MENU_ID_STOP: + EnableToolsAndMenus(false); + m_controller->Stop(); + UnhilightCurrentLine(); + + break; + + default: + break; + } } + //////////////////////////////////////////////////////////////////////////////// -// OnMarginClick() +// OnSelectFrame() +// +// This function handles the user click on one of the stack-frames, and +// asks the debugger server to switch to that frame, update the breakpoint +// markers, and send a list of variables that are in-scope in the selected +// frame // -// This event handler is invoked when the user clicks one of the stack frames -// - we simply forward the event to the debugger window. +// Parametes: +// Selection Event Object +// +void frmDebugger::OnSelectFrame(wxCommandEvent &_ev) +{ + if(_ev.GetSelection() != -1) + { + m_controller->SelectFrame(_ev.GetSelection()); + } +} -void frmDebugger::OnMarginClick( wxStyledTextEvent &event ) + +//////////////////////////////////////////////////////////////////////////////// +// OnMarginClick() +// +// This function handles the user clicks in the margin to the left of a +// line of source code. We use the margin to display breakpoint indicators +// so it makes sense that if you click on an breakpoint indicator, we will +// clear that breakpoint. If you click on a spot that does not contain a +// breakpoint indicator (but it's still in the margin), we create a new +// breakpoint at that line +// +// Parametes: +// Selection Event Object +// +void frmDebugger::OnMarginClick(wxStyledTextEvent &event) { - if (m_standaloneDebugger) - m_standaloneDebugger->OnMarginClick( event ); + int lineNo; + + // Check that the user clicked on the line number or breakpoint margin + // We don't want to set a breakpoint when the user folds/unfolds code + if (!(event.GetMargin() == 0 || event.GetMargin() == 1)) + return; + + lineNo = m_codeViewer->LineFromPosition(event.GetPosition()); + + if (lineNo <= 0) + return; + + // If we already have a breakpoint at the clickpoint, disable it, otherwise + // create a new breakpoint + if(m_codeViewer->MarkerGet(lineNo) & + MARKERINDEX_TO_MARKERMASK(MARKER_BREAKPOINT)) + { + m_controller->ClearBreakpoint(lineNo); + } + else + { + m_controller->SetBreakpoint(lineNo); + } + m_controller->UpdateBreakpoints(); } + //////////////////////////////////////////////////////////////////////////////// // OnPositionStc() // -// This event handler is invoked when the user positions the cursor in the -// code window -// - we simply forward the event to the debugger window. - -void frmDebugger::OnPositionStc( wxStyledTextEvent &event ) +// This function handles the event called when the user positions the cursor +// in the code viewer. We will update the current line number etc in the +// status bar. +// +// Parametes: +// wxStyledTextEvent Object +// +void frmDebugger::OnPositionStc(wxStyledTextEvent &event) { - if (m_standaloneDebugger) - m_standaloneDebugger->OnPositionStc( event ); + if (m_codeViewer) + { + wxString strPos; + + int pos = m_codeViewer->GetCurrentPos(); + + strPos.Printf(_("Ln %d Col %d Ch %d"), m_codeViewer->LineFromPosition(pos) + 1, + m_codeViewer->GetColumn(pos) + 1, pos + 1); + + GetStatusBar()->SetStatusText(strPos, 2); + } } + //////////////////////////////////////////////////////////////////////////////// // OnVarChange() // -// This event handler is invoked when the user edits a variable value -// - we simply forward the event to the debugger window. - -void frmDebugger::OnVarChange( wxGridEvent &event ) +// This functions handles the event occured when the user edits a variable +// value. Submit the changed value to the debugger server +// +void frmDebugger::OnVarChange(wxGridEvent &_ev) { - if (m_standaloneDebugger) - m_standaloneDebugger->OnVarChange( event ); + ctlVarWindow *varWin = NULL; + wxString varName, varValue; + + if(_ev.GetId() == ID_PARAMGRID) + { + varWin = GetParamWindow(false); + varName = varWin->GetVarName(_ev.GetRow()); + varValue = varWin->GetVarValue(_ev.GetRow()); + } + else if (_ev.GetId() == ID_VARGRID) + { + varWin = GetVarWindow(false); + varName = varWin->GetVarName(_ev.GetRow()); + varValue = varWin->GetVarValue(_ev.GetRow()); + } + else if (_ev.GetId() == ID_PKGVARGRID) + { + varWin = GetPkgVarWindow(false); + varName = wxT("@") + varWin->GetVarName(_ev.GetRow()); + varValue = varWin->GetVarValue(_ev.GetRow()); + } + else + { + // Theorically - the execution flow must not reach at this point. + return; + } + + m_controller->DepositValue(varName, varValue); } //////////////////////////////////////////////////////////////////////////////// @@ -234,113 +636,124 @@ void frmDebugger::OnVarChange( wxGridEvent &event ) // // This event handler is called when a resize event occurs (that is, when // wxWidgets needs to size the application window) - -void frmDebugger::OnSize( wxSizeEvent &event ) +// +void frmDebugger::OnSize(wxSizeEvent &event) { event.Skip(); } + void frmDebugger::OnEraseBackground(wxEraseEvent &event) { event.Skip(); } + //////////////////////////////////////////////////////////////////////////////// // setupToolBar() // // This function creates the standard toolbar - -ctlMenuToolbar *frmDebugger::setupToolBar( void ) +// +ctlMenuToolbar *frmDebugger::SetupToolBar(void) { m_toolBar = new ctlMenuToolbar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxTB_NODIVIDER); - m_toolBar->SetToolBitmapSize( wxSize( 16, 16 )); + m_toolBar->SetToolBitmapSize(wxSize(16, 16)); - m_toolBar->AddTool( MENU_ID_STEP_INTO, wxEmptyString, *stepInto_png_bmp, _( "Step into" )); - m_toolBar->AddTool( MENU_ID_STEP_OVER, wxEmptyString, *stepOver_png_bmp, _( "Step over" )); - m_toolBar->AddTool( MENU_ID_CONTINUE, wxEmptyString, *continue_png_bmp, _( "Continue" )); + m_toolBar->AddTool(MENU_ID_STEP_INTO, wxEmptyString, *stepInto_png_bmp, + _("Step into")); + m_toolBar->AddTool(MENU_ID_STEP_OVER, wxEmptyString, *stepOver_png_bmp, + _("Step over")); + m_toolBar->AddTool(MENU_ID_CONTINUE, wxEmptyString, *continue_png_bmp, + _("Continue/Start")); m_toolBar->AddSeparator(); - m_toolBar->AddTool( MENU_ID_TOGGLE_BREAK, wxEmptyString, *setBreak_png_bmp, _( "Toggle breakpoint" )); - m_toolBar->AddTool( MENU_ID_CLEAR_ALL_BREAK, wxEmptyString, *clearAll_png_bmp, _( "Clear all breakpoints" )); + m_toolBar->AddTool(MENU_ID_TOGGLE_BREAK, wxEmptyString, *setBreak_png_bmp, + _("Toggle breakpoint")); + m_toolBar->AddTool(MENU_ID_CLEAR_ALL_BREAK, wxEmptyString, *clearAll_png_bmp, + _("Clear all breakpoints")); m_toolBar->AddSeparator(); - m_toolBar->AddTool( MENU_ID_STOP, wxEmptyString, *stop_png_bmp, _( "Stop debugging" )); + m_toolBar->AddTool(MENU_ID_STOP, wxEmptyString, *stop_png_bmp, + _("Stop debugging")); - m_toolBar->EnableTool(MENU_ID_STEP_INTO, false); - m_toolBar->EnableTool(MENU_ID_STEP_OVER, false); - m_toolBar->EnableTool(MENU_ID_CONTINUE, false); - m_toolBar->EnableTool(MENU_ID_TOGGLE_BREAK, false); - m_toolBar->EnableTool(MENU_ID_CLEAR_ALL_BREAK, false); - m_toolBar->EnableTool(MENU_ID_STOP, false); + m_toolBar->EnableTool(MENU_ID_STEP_INTO, false); + m_toolBar->EnableTool(MENU_ID_STEP_OVER, false); + m_toolBar->EnableTool(MENU_ID_CONTINUE, false); + m_toolBar->EnableTool(MENU_ID_TOGGLE_BREAK, false); + m_toolBar->EnableTool(MENU_ID_CLEAR_ALL_BREAK, false); + m_toolBar->EnableTool(MENU_ID_STOP, false); m_toolBar->Realize(); - return( m_toolBar ); + return(m_toolBar); } + //////////////////////////////////////////////////////////////////////////////// -// setupStatusBar() +// SetupStatusBar() // // This function initializes the status bar (we don't have one at the moment // so this function simply returns) - -wxStatusBar *frmDebugger::setupStatusBar( void ) +// +wxStatusBar *frmDebugger::SetupStatusBar(void) { - wxStatusBar *bar = CreateStatusBar( 3, wxST_SIZEGRIP ); - int widths[] = { 0, -1, 130 }; + wxStatusBar *bar = CreateStatusBar(3, wxST_SIZEGRIP); + int widths[] = { 0, -1, 130 }; bar->SetStatusWidths(3, widths); - bar->SetStatusText(_( "Initializing..."), 1); + bar->SetStatusText(_("Initializing..."), 1); - return( bar ); + return(bar); } + //////////////////////////////////////////////////////////////////////////////// -// setupMenuBar() +// SetupMenuBar() // // This function creates the standard menu bar - -wxMenuBar *frmDebugger::setupMenuBar(void) +// +wxMenuBar *frmDebugger::SetupMenuBar(void) { m_menuBar = new wxMenuBar; + // Don't append the File menu on Mac, as the Exit option + // will be moved to the application menu, leaving File empty. +#ifndef __WXMAC__ wxMenu *fileMenu = new wxMenu; fileMenu->Append(MNU_EXIT, _("E&xit\tAlt-F4"), _("Exit debugger window")); -// Don't append the File menu on Mac, as the Exit option -// will be moved to the application menu, leaving File empty. -#ifndef __WXMAC__ + m_menuBar->Append(fileMenu, _("&File")); #endif m_debugMenu = new wxMenu; #ifdef __WXGTK__ - /* - * F10 is treated as a System menu under GTK. Hence, we will use Ctrl+F10 for - * "step over" operation under GTK, instead of F10. - * - * To make the behavior consitent, we will also use Ctrl+ for all the operations - * under GTK. (i.e. Step into, Step over, Continue, Toggle breakpoint, Stop - * debugging) - * - * Please follow this link for more details: - * http://trac.wxwidgets.org/ticket/2404 - */ - m_debugMenu->Append(MENU_ID_STEP_INTO, _( "Step into\tCtrl+F11" )); - m_debugMenu->Append(MENU_ID_STEP_OVER, _( "Step over\tCtrl+F10" )); - m_debugMenu->Append(MENU_ID_CONTINUE, _( "Continue\tCtrl+F5" )); + // + // F10 is treated as a System menu under GTK. Hence, we will use Ctrl+F10 for + // "step over" operation under GTK, instead of F10. + // + // To make the behavior consitent, we will also use Ctrl+ for all the operations + // under GTK. (i.e. Step into, Step over, Continue, Toggle breakpoint, Stop + // debugging) + // + // Please follow this link for more details: + // http://trac.wxwidgets.org/ticket/2404 + // + m_debugMenu->Append(MENU_ID_STEP_INTO, _("Step into\tCtrl+F11")); + m_debugMenu->Append(MENU_ID_STEP_OVER, _("Step over\tCtrl+F10")); + m_debugMenu->Append(MENU_ID_CONTINUE, _("Continue/Start\tCtrl+F5")); m_debugMenu->AppendSeparator(); - m_debugMenu->Append(MENU_ID_TOGGLE_BREAK, _( "Toggle breakpoint\tCtrl+F9" )); - m_debugMenu->Append(MENU_ID_CLEAR_ALL_BREAK, _( "Clear all breakpoints\tCtrl+Shift+F9" )); + m_debugMenu->Append(MENU_ID_TOGGLE_BREAK, _("Toggle breakpoint\tCtrl+F9")); + m_debugMenu->Append(MENU_ID_CLEAR_ALL_BREAK, _("Clear all breakpoints\tCtrl+Shift+F9")); m_debugMenu->AppendSeparator(); - m_debugMenu->Append(MENU_ID_STOP, _( "Stop debugging\tCtrl+F8" )); + m_debugMenu->Append(MENU_ID_STOP, _("Stop debugging\tCtrl+F8")); #else - m_debugMenu->Append(MENU_ID_STEP_INTO, _( "Step into\tF11" )); - m_debugMenu->Append(MENU_ID_STEP_OVER, _( "Step over\tF10" )); - m_debugMenu->Append(MENU_ID_CONTINUE, _( "Continue\tF5" )); + m_debugMenu->Append(MENU_ID_STEP_INTO, _("Step into\tF11")); + m_debugMenu->Append(MENU_ID_STEP_OVER, _("Step over\tF10")); + m_debugMenu->Append(MENU_ID_CONTINUE, _("Continue/Start\tF5")); m_debugMenu->AppendSeparator(); - m_debugMenu->Append(MENU_ID_TOGGLE_BREAK, _( "Toggle breakpoint\tF9" )); - m_debugMenu->Append(MENU_ID_CLEAR_ALL_BREAK, _( "Clear all breakpoints\tCtrl+Shift+F9" )); + m_debugMenu->Append(MENU_ID_TOGGLE_BREAK, _("Toggle breakpoint\tF9")); + m_debugMenu->Append(MENU_ID_CLEAR_ALL_BREAK, _("Clear all breakpoints\tCtrl+Shift+F9")); m_debugMenu->AppendSeparator(); - m_debugMenu->Append(MENU_ID_STOP, _( "Stop debugging\tF8" )); + m_debugMenu->Append(MENU_ID_STOP, _("Stop debugging\tF8")); #endif //__WXGTK__ m_debugMenu->Enable(MENU_ID_STEP_INTO, false); m_debugMenu->Enable(MENU_ID_STEP_OVER, false); @@ -365,9 +778,9 @@ wxMenuBar *frmDebugger::setupMenuBar(void) helpMenu->Append(MNU_HELP, _("&SQL Help\tF1"), _("Display help on SQL commands.")); m_menuBar->Append(helpMenu, _("&Help")); - SetMenuBar( m_menuBar ); + SetMenuBar(m_menuBar); - return( m_menuBar ); + return(m_menuBar); } //////////////////////////////////////////////////////////////////////////////// @@ -375,18 +788,32 @@ wxMenuBar *frmDebugger::setupMenuBar(void) // // wxWidgets invokes this event handler when the user closes the main window -void frmDebugger::OnClose( wxCloseEvent &event ) +void frmDebugger::OnClose(wxCloseEvent &_ev) { - if (m_standaloneDebugger) + bool wasTimerRunning = m_timer.IsRunning(); + m_timer.Stop(); + + if (!m_controller->CanRestart() && _ev.CanVeto()) { - m_standaloneDebugger->OnClose( event ); - if (event.GetVeto()) - return; + if (wxMessageBox( + _("Are you sure you wish to abort the debugging session?\nThis will abort the function currently being debugged."), + _("Close debugger"), wxICON_QUESTION | wxYES_NO) != wxYES) + { + _ev.Veto(); + if (wasTimerRunning) + { + m_timer.Start(500); + } - delete m_standaloneDebugger; - m_standaloneDebugger = NULL; + return; + } } - event.Skip(); + + m_timer.Stop(); + + m_controller->CloseDebugger(); + + _ev.Skip(); } //////////////////////////////////////////////////////////////////////////////// @@ -394,7 +821,7 @@ void frmDebugger::OnClose( wxCloseEvent &event ) // // Close the debugger -void frmDebugger::OnExit( wxCommandEvent &event ) +void frmDebugger::OnExit(wxCommandEvent &event) { Close(); } @@ -407,10 +834,11 @@ void frmDebugger::OnExit( wxCommandEvent &event ) void frmDebugger::OnToggleToolBar(wxCommandEvent &event) { if (m_viewMenu->IsChecked(MENU_ID_VIEW_TOOLBAR)) - manager.GetPane(wxT("toolBar")).Show(true); + m_manager.GetPane(wxT("toolBar")).Show(true); else - manager.GetPane(wxT("toolBar")).Show(false); - manager.Update(); + m_manager.GetPane(wxT("toolBar")).Show(false); + + m_manager.Update(); } //////////////////////////////////////////////////////////////////////////////// @@ -420,10 +848,11 @@ void frmDebugger::OnToggleToolBar(wxCommandEvent &event) void frmDebugger::OnToggleStackPane(wxCommandEvent &event) { if (m_viewMenu->IsChecked(MENU_ID_VIEW_STACKPANE)) - manager.GetPane(wxT("stackPane")).Show(true); + m_manager.GetPane(wxT("stackPane")).Show(true); else - manager.GetPane(wxT("stackPane")).Show(false); - manager.Update(); + m_manager.GetPane(wxT("stackPane")).Show(false); + + m_manager.Update(); } //////////////////////////////////////////////////////////////////////////////// @@ -433,10 +862,11 @@ void frmDebugger::OnToggleStackPane(wxCommandEvent &event) void frmDebugger::OnToggleOutputPane(wxCommandEvent &event) { if (m_viewMenu->IsChecked(MENU_ID_VIEW_OUTPUTPANE)) - manager.GetPane(wxT("outputPane")).Show(true); + m_manager.GetPane(wxT("outputPane")).Show(true); else - manager.GetPane(wxT("outputPane")).Show(false); - manager.Update(); + m_manager.GetPane(wxT("outputPane")).Show(false); + + m_manager.Update(); } //////////////////////////////////////////////////////////////////////////////// @@ -466,18 +896,157 @@ void frmDebugger::OnAuiUpdate(wxAuiManagerEvent &event) // Reset the AUI view to the default void frmDebugger::OnDefaultView(wxCommandEvent &event) { - manager.LoadPerspective(FRMDEBUGGER_DEFAULT_PERSPECTIVE, true); + m_manager.LoadPerspective(FRMDEBUGGER_DEFAULT_PERSPECTIVE, true); // Reset the captions for the current language - manager.GetPane(wxT("toolBar")).Caption(_("Toolbar")); - manager.GetPane(wxT("stackPane")).Caption(_("Stack pane")); - manager.GetPane(wxT("outputPane")).Caption(_("Output pane")); + m_manager.GetPane(wxT("toolBar")).Caption(_("Toolbar")); + m_manager.GetPane(wxT("stackPane")).Caption(_("Stack pane")); + m_manager.GetPane(wxT("outputPane")).Caption(_("Output pane")); - // tell the manager to "commit" all the changes just made - manager.Update(); + // tell the m_manager to "commit" all the changes just made + m_manager.Update(); // Sync the View menu options - m_viewMenu->Check(MENU_ID_VIEW_TOOLBAR, manager.GetPane(wxT("toolBar")).IsShown()); - m_viewMenu->Check(MENU_ID_VIEW_STACKPANE, manager.GetPane(wxT("stackPane")).IsShown()); - m_viewMenu->Check(MENU_ID_VIEW_OUTPUTPANE, manager.GetPane(wxT("outputPane")).IsShown()); + m_viewMenu->Check(MENU_ID_VIEW_TOOLBAR, m_manager.GetPane(wxT("toolBar")).IsShown()); + m_viewMenu->Check(MENU_ID_VIEW_STACKPANE, m_manager.GetPane(wxT("stackPane")).IsShown()); + m_viewMenu->Check(MENU_ID_VIEW_OUTPUTPANE, m_manager.GetPane(wxT("outputPane")).IsShown()); +} + + +void frmDebugger::HighlightLine(int _lineNo) +{ + int lineNo = m_codeViewer->MarkerNext( + 0, MARKERINDEX_TO_MARKERMASK( MARKER_CURRENT)); + + if (lineNo != -1) + { + m_codeViewer->MarkerDelete(lineNo, MARKER_CURRENT); + m_codeViewer->MarkerDelete(lineNo, MARKER_CURRENT_BG); + } + + // Add the current-line indicator to the current line of code + m_codeViewer->MarkerAdd(_lineNo, MARKER_CURRENT); + m_codeViewer->MarkerAdd(_lineNo, MARKER_CURRENT_BG); + + // Scroll the source code listing (if required) to make sure + // that this line of code is visible + // + // (NOTE: we set the anchor and the caret to the same position to avoid + // creating a selection region) + int pos = m_codeViewer->PositionFromLine(_lineNo); + + m_codeViewer->SetAnchor(pos); + m_codeViewer->SetCurrentPos(pos); + + m_codeViewer->EnsureCaretVisible(); +} + +void frmDebugger::DisplaySource(dbgCachedStack &_cached) +{ + dbgModel *model = m_controller->GetModel(); + + m_codeViewer->SetFocus(); + + if (model->RequireDisplayUpdate()) + { + ctlVarWindow *varWin = NULL; + if ((varWin = GetVarWindow(false)) != NULL) + { + varWin->DelVar(); + } + if ((varWin = GetParamWindow(false)) != NULL) + { + varWin->DelVar(); + } + if ((varWin = GetPkgVarWindow(false)) != NULL) + { + varWin->DelVar(); + } + + model->GetDisplayedFunction() = _cached.m_func; + model->GetDisplayedPackage() = _cached.m_pkg; + + // Now erase any old code and write out the new listing + m_codeViewer->SetReadOnly(false); + + m_codeViewer->SetText(_cached.m_source); + m_codeViewer->Colourise(0, _cached.m_source.Length()); + + m_codeViewer->SetReadOnly(true); + } + HighlightLine(model->GetCurrLineNo()); +} + + +void frmDebugger::SetStatusText(const wxString &_status) +{ + m_statusBar->SetStatusText(_status, 1); +} + + +void frmDebugger::CloseProgressBar() +{ + if (m_progressBar) + { + m_progressBar->Close(); + delete m_progressBar; + m_progressBar = NULL; + } +} + + +void frmDebugger::LaunchWaitingDialog(const wxString &msg) +{ + dbgModel *model = m_controller->GetModel(); + wxString strStatus, + strTargetPid = model->GetTargetPid(), + strTarget = model->GetTarget()->GetQualifiedName(); + + if (msg.IsEmpty()) + { + if (strTargetPid != wxT("NULL")) + { + strStatus = + wxString::Format( + _("Waiting for the session (pid:%s) to invoke the specified targets."), + strTargetPid.c_str()); + } + else + { + strStatus = + wxString::Format( + _("Waiting for another session to invoke the target - \"%s\""), strTarget.c_str()); + } + } + else + { + strStatus = msg; + } + + m_statusBar->SetStatusText(strStatus, 1); + + // NOTE: the waiting-dialog takes forever to appear running a remote X session so you can disable it by defining the following env. variable + if(getenv("SUPPRESS_WAIT_DIALOG")) + { + m_progressBar = NULL; + } + else + { + m_progressBar = + new wxProgressDialog(_("Waiting for target"), strStatus, 100, this, + wxPD_SMOOTH | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME ); + + m_timer.Start(500); // 2 clock ticks per second + } +} + +void frmDebugger::OnTimer(wxTimerEvent &_ev) +{ + if (m_progressBar) + { + if (m_progressBar->Pulse() == false) + { + Close(); + } + } } diff --git a/pgadmin/debugger/module.mk b/pgadmin/debugger/module.mk index 863ae8e..46fe479 100644 --- a/pgadmin/debugger/module.mk +++ b/pgadmin/debugger/module.mk @@ -10,17 +10,15 @@ ####################################################################### pgadmin3_SOURCES += \ - debugger/ctlCodeWindow.cpp \ debugger/ctlMessageWindow.cpp \ debugger/ctlResultGrid.cpp \ debugger/ctlStackWindow.cpp \ debugger/ctlTabWindow.cpp \ debugger/ctlVarWindow.cpp \ debugger/dbgBreakPoint.cpp \ - debugger/dbgDbResult.cpp \ - debugger/dbgPgConn.cpp \ - debugger/dbgPgThread.cpp \ - debugger/dbgResultset.cpp \ + debugger/dbgController.cpp \ + debugger/dbgEvents.cpp \ + debugger/dbgModel.cpp \ debugger/dbgTargetInfo.cpp \ debugger/debugger.cpp \ debugger/dlgDirectDbg.cpp \ diff --git a/pgadmin/dlg/dlgClasses.cpp b/pgadmin/dlg/dlgClasses.cpp index 2c764b1..aaeb626 100644 --- a/pgadmin/dlg/dlgClasses.cpp +++ b/pgadmin/dlg/dlgClasses.cpp @@ -491,7 +491,11 @@ void ExecutionDialog::Abort() if (thread) { if (thread->IsRunning()) - thread->Delete(); + { + thread->CancelExecution(); + thread->Wait(); + } + delete thread; thread = 0; } diff --git a/pgadmin/frm/frmEditGrid.cpp b/pgadmin/frm/frmEditGrid.cpp index 88efe55..8df535b 100644 --- a/pgadmin/frm/frmEditGrid.cpp +++ b/pgadmin/frm/frmEditGrid.cpp @@ -1524,7 +1524,10 @@ void frmEditGrid::Abort() { SetStatusText(_("aborting."), 0); if (thread->IsRunning()) - thread->Delete(); + { + thread->CancelExecution(); + thread->Wait(); + } delete thread; thread = 0; } diff --git a/pgadmin/frm/frmQuery.cpp b/pgadmin/frm/frmQuery.cpp index 4e30242..9c5e65b 100644 --- a/pgadmin/frm/frmQuery.cpp +++ b/pgadmin/frm/frmQuery.cpp @@ -159,7 +159,7 @@ BEGIN_EVENT_TABLE(frmQuery, pgFrame) EVT_TIMER(CTL_TIMERSIZES, frmQuery::OnAdjustSizesTimer) EVT_TIMER(CTL_TIMERFRM, frmQuery::OnTimer) // These fire when the queries complete - EVT_MENU(QUERY_COMPLETE, frmQuery::OnQueryComplete) + EVT_PGQUERYRESULT(QUERY_COMPLETE, frmQuery::OnQueryComplete) EVT_MENU(PGSCRIPT_COMPLETE, frmQuery::OnScriptComplete) EVT_AUINOTEBOOK_PAGE_CHANGED(CTL_NTBKCENTER, frmQuery::OnChangeNotebook) EVT_SPLITTER_SASH_POS_CHANGED(GQB_HORZ_SASH, frmQuery::OnResizeHorizontally) @@ -2415,7 +2415,7 @@ void frmQuery::execQuery(const wxString &query, int resultToRetrieve, bool singl // When the query completes, it raises an event which we process here. -void frmQuery::OnQueryComplete(wxCommandEvent &ev) +void frmQuery::OnQueryComplete(pgQueryResultEvent &ev) { QueryExecInfo *qi = (QueryExecInfo *)ev.GetClientData(); diff --git a/pgadmin/include/db/module.mk b/pgadmin/include/db/module.mk index ae79345..03fefa1 100644 --- a/pgadmin/include/db/module.mk +++ b/pgadmin/include/db/module.mk @@ -12,9 +12,9 @@ pgadmin3_SOURCES += \ include/db/pgConn.h \ include/db/pgQueryThread.h \ + include/db/pgQueryResultEvent.h \ include/db/pgSet.h - EXTRA_DIST += \ include/db/module.mk diff --git a/pgadmin/include/db/pgConn.h b/pgadmin/include/db/pgConn.h index f58cb81..c5d3e10 100644 --- a/pgadmin/include/db/pgConn.h +++ b/pgadmin/include/db/pgConn.h @@ -24,33 +24,33 @@ // status enums enum { - PGCONN_OK = CONNECTION_OK, - PGCONN_BAD = CONNECTION_BAD, - PGCONN_REFUSED, - PGCONN_DNSERR, - PGCONN_ABORTED, // connect user aborted - PGCONN_BROKEN // tcp/pipe broken + PGCONN_OK = CONNECTION_OK, + PGCONN_BAD = CONNECTION_BAD, + PGCONN_REFUSED, + PGCONN_DNSERR, + PGCONN_ABORTED, // connect user aborted + PGCONN_BROKEN // tcp/pipe broken }; enum { - PGCONN_EMPTY_QUERY = PGRES_EMPTY_QUERY, - PGCONN_COMMAND_OK = PGRES_COMMAND_OK, - PGCONN_TUPLES_OK = PGRES_TUPLES_OK, - PGCONN_COPY_OUT = PGRES_COPY_OUT, - PGCONN_COPY_IN = PGRES_COPY_IN, - PGCONN_BAD_RESPONSE = PGRES_BAD_RESPONSE, - PGCONN_NONFATAL_ERROR = PGRES_NONFATAL_ERROR, - PGCONN_FATAL_ERROR = PGRES_FATAL_ERROR + PGCONN_EMPTY_QUERY = PGRES_EMPTY_QUERY, + PGCONN_COMMAND_OK = PGRES_COMMAND_OK, + PGCONN_TUPLES_OK = PGRES_TUPLES_OK, + PGCONN_COPY_OUT = PGRES_COPY_OUT, + PGCONN_COPY_IN = PGRES_COPY_IN, + PGCONN_BAD_RESPONSE = PGRES_BAD_RESPONSE, + PGCONN_NONFATAL_ERROR = PGRES_NONFATAL_ERROR, + PGCONN_FATAL_ERROR = PGRES_FATAL_ERROR }; enum { - PGCONN_TXSTATUS_IDLE = PQTRANS_IDLE, - PGCONN_TXSTATUS_ACTIVE = PQTRANS_ACTIVE, - PGCONN_TXSTATUS_INTRANS = PQTRANS_INTRANS, - PGCONN_TXSTATUS_INERROR = PQTRANS_INERROR, - PGCONN_TXSTATUS_UNKNOWN = PQTRANS_UNKNOWN + PGCONN_TXSTATUS_IDLE = PQTRANS_IDLE, + PGCONN_TXSTATUS_ACTIVE = PQTRANS_ACTIVE, + PGCONN_TXSTATUS_INTRANS = PQTRANS_INTRANS, + PGCONN_TXSTATUS_INERROR = PQTRANS_INERROR, + PGCONN_TXSTATUS_UNKNOWN = PQTRANS_UNKNOWN }; // Our version of a pgNotify @@ -77,8 +77,9 @@ typedef struct pgError wxString source_line; wxString source_function; wxString formatted_msg; -} pgError; + void SetError(PGresult *_res = NULL, wxMBConv *_conv = NULL); +} pgError; class pgConn { @@ -110,7 +111,7 @@ public: bool GetIsGreenplum(); wxString EncryptPassword(const wxString &user, const wxString &password); wxString qtDbString(const wxString &value); - pgConn *Duplicate(); + pgConn *Duplicate(const wxString &_appName = wxT("")); static void ExamineLibpqVersion(); static double GetLibpqVersion() @@ -126,8 +127,10 @@ public: void Close(); bool Reconnect(); bool ExecuteVoid(const wxString &sql, bool reportError = true); - wxString ExecuteScalar(const wxString &sql); - pgSet *ExecuteSet(const wxString &sql); + wxString ExecuteScalar(const wxString &sql, bool reportError = true); + pgSet *ExecuteSet(const wxString &sql, bool reportError = true); + void CancelExecution(void); + wxString GetHostAddr() const { return save_hostaddr; @@ -257,12 +260,16 @@ public: bool TableHasColumn(wxString schemaname, wxString tblname, const wxString &colname); protected: - PGconn *conn; + PGconn *conn; + PGcancel *m_cancelConn; + wxMutex m_cancelConnMutex; int lastResultStatus; int connStatus; void SetLastResultError(PGresult *res, const wxString &msg = wxEmptyString); + void SetConnCancel(void); + void ResetConnCancel(void); pgError lastResultError; wxMBConv *conv; diff --git a/pgadmin/include/db/pgQueryResultEvent.h b/pgadmin/include/db/pgQueryResultEvent.h new file mode 100644 index 0000000..7d2fb3b --- /dev/null +++ b/pgadmin/include/db/pgQueryResultEvent.h @@ -0,0 +1,79 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2013, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// pgQueryResultEvent.h - Query Result Event from the pgQueryThread +// +////////////////////////////////////////////////////////////////////////// + +#ifndef PGQUERYRESULTEVENT_H +#define PGQUERYRESULTEVENT_H + +#include "wx/wx.h" +#include "wx/event.h" + +class pgBatchQuery; + +extern const wxEventType PGQueryResultEvent; + + +class pgQueryResultEvent : public wxCommandEvent +{ +public: + pgQueryResultEvent(unsigned long _thrdId, pgBatchQuery *_qry, int _id = 0); + pgQueryResultEvent(const pgQueryResultEvent &_ev); + + // Required for sending with wxPostEvent() + wxEvent *Clone() const + { + return new pgQueryResultEvent(*this); + } + + pgBatchQuery *GetQuery() + { + return m_query; + } + unsigned long GetThreadID() + { + return m_thrdId; + } + + enum + { + PGQ_RESULT_ERROR = -8, + PGQ_EXECUTION_CANCELLED = -7, + PGQ_ERROR_CONSUME_INPUT = -6, + PGQ_ERROR_SEND_QUERY = -5, + PGQ_ERROR_EXECUTE_CALLABLE = -4, + PGQ_ERROR_PREPARE_CALLABLE = -3, + PGQ_STRING_INVALID = -2, + PGQ_CONN_LOST = -1, + }; + +private: + pgBatchQuery *m_query; + // Thread Id (pgQueryThread) + unsigned long m_thrdId; +}; + +typedef void (wxEvtHandler::*pgQueryResultEventFunc)(pgQueryResultEvent &); + +// This #define simplifies the one below, and makes the syntax less +// ugly if you want to use Connect() instead of an event table. +#define pgQueryResultEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) \ + wxStaticCastEvent(pgQueryResultEventFunc, &func) + +// Define the event table entry. Yes, it really *does* end in a comma. +#define EVT_PGQUERYRESULT(id, fn) \ + DECLARE_EVENT_TABLE_ENTRY(PGQueryResultEvent, id, wxID_ANY, \ + pgQueryResultEventHandler(fn), (wxObject*) NULL), + +#define EVT_PGQUERYRESULT_RANGE(id1, id2, fn) \ + DECLARE_EVENT_TABLE_ENTRY(PGQueryResultEvent, id1, id2, \ + pgQueryResultEventHandler(fn), (wxObject*) NULL), + +#endif // PGQUERYRESULTEVENT_H diff --git a/pgadmin/include/db/pgQueryThread.h b/pgadmin/include/db/pgQueryThread.h index a03a32a..59bebf7 100644 --- a/pgadmin/include/db/pgQueryThread.h +++ b/pgadmin/include/db/pgQueryThread.h @@ -12,59 +12,271 @@ #ifndef PGQUERYTHREAD_H #define PGQUERYTHREAD_H +#include "wx/wx.h" +#include "wx/event.h" +#include "db/pgConn.h" + +// Forward declaration class pgSet; +class pgQueryThread; +class pgBatchQuery; + +// Support for the IN & INOUT parameters type +class pgParam : public wxObject +{ +public: + enum + { + PG_PARAM_IN = 1, + PG_PARAM_OUT = 2, + PG_PARAM_INOUT = 3, + PG_PARAM_VARIADIC = 4, + PG_PARAM_TABLE = 5 + }; + + // Any data + // + // This constructor won't call the functions - 'htonl' or 'htons' + // It will be the responsibility of the caller to take care + // to call 'htonl' or 'htons' depending on requirements. + // + // NOTE: + // This data will be owned by pgParam object and will be released. + pgParam(Oid _type, void *_val, int _len, short mode = PG_PARAM_IN); + + // wxString data + pgParam(Oid _type, wxString *_val, wxMBConv *_conv = NULL, + short mode = PG_PARAM_IN); + + ~pgParam(); + + // Returns 0 for text type and 1 for otherwise + int GetFormat(); + + Oid GetType() + { + return m_type; + } + short GetMode() + { + return m_mode; + } + +protected: + Oid m_type; + void *m_val; + int m_len; + short m_format; + // Modes are required by EnterpriseDB's callable statement + short m_mode; + + // Do not allow copy construction and shadow-copy + // to avoid ownership + // Force to use an pointer + pgParam(const pgParam &) + { + wxASSERT(0); + } + pgParam &operator= (const pgParam &) + { + wxASSERT(0); + } + + friend class pgConn; + friend class pgQueryThread; +}; + +WX_DEFINE_ARRAY_PTR(pgParam *, pgParamsArray); + +class pgBatchQuery : public wxObject +{ +public: + pgBatchQuery(const wxString &_query, pgParamsArray *_params = NULL, + long _eventId = -1, void *_data = NULL, bool _useCallable = false, + int _resultToRetrieve = 0) + : m_query(_query), m_params(_params), m_eventID(_eventId), m_data(_data), + m_useCallable(_useCallable), m_resToRetrieve(_resultToRetrieve), + m_returnCode(-1), m_resultSet(NULL), m_rowsInserted(-1), m_insertedOid(-1) + { + // Do not honour the empty query string + wxASSERT(!_query.IsEmpty()); + } + ~pgBatchQuery(); + + bool Release(); + + pgSet *ResultSet() + { + return m_resultSet; + } + + int ReturnCode() + { + return m_returnCode; + } + + const wxString &GetMessage() + { + return m_message; + } + + long RowInserted() + { + return m_rowsInserted; + } + + const wxString &GetErrorMessage(); + +protected: + wxString m_query; // Query + pgParamsArray *m_params; // parameters + long m_eventID; // Event ID + void *m_data; // Data to be send with event + bool m_useCallable; // Use EnterpriseDB callable statement if possible + int m_resToRetrieve; // Which result to be retrieved + int m_returnCode; // Return code + pgSet *m_resultSet; // Result-Set + long m_rowsInserted; // No of rows inserted + Oid m_insertedOid; // Inserted Oid + wxString m_message; // Message generated during query execution + pgError m_err; // Error + +private: + // Do not allow copy construction and '=' operator (shadow copying) + // to avoid ownership of parameters and result-set + // + // This will force this class to be used as an pointer only. + pgBatchQuery(const pgBatchQuery &) + { + wxASSERT(0); + } + pgBatchQuery &operator= (const pgBatchQuery &) + { + wxASSERT(0); + } + + friend class pgQueryThread; +}; +WX_DEFINE_ARRAY_PTR(pgBatchQuery *, pgBatchQueryArray); class pgQueryThread : public wxThread { public: - pgQueryThread(pgConn *_conn, const wxString &qry, int resultToRetrieve = -1, wxWindow *_caller = 0, long eventId = 0, void *_data = 0); + // For running a single query (Used by few components) + pgQueryThread(pgConn *_conn, const wxString &qry, int resultToRetrieve = -1, + wxWindow *_caller = 0, long eventId = 0, void *_data = 0); + + // Support for multiple queries support + pgQueryThread(pgConn *_conn, wxEvtHandler *_caller = NULL, + PQnoticeProcessor _processor = NULL, void *_noticeHandler = NULL); + ~pgQueryThread(); + bool HasMultipleQueriesSupport() + { + return m_multiQueries; + } + + bool SupportCallableStatement() + { + return m_useCallable; + } + + void AddQuery( + const wxString &_qry, pgParamsArray *_params = NULL, + long _eventId = 0, void *_data = NULL, bool _useCallable = false, + int _resultToRetrieve = -1); + virtual void *Entry(); - bool DataValid() const + bool DataValid(int _idx = -1) const { - return dataSet != NULL; + if (_idx == -1) + _idx = m_currIndex; + return (_idx >= 0 && _idx > m_currIndex ? false : (m_queries[_idx]->m_resultSet != NULL)); } - pgSet *DataSet() + + pgConn *GetConn() + { + return m_conn; + } + + pgSet *DataSet(int _idx = -1) + { + if (_idx == -1) + _idx = m_currIndex; + return (_idx >= 0 && _idx > m_currIndex ? NULL : m_queries[_idx]->m_resultSet); + } + + int ReturnCode(int _idx = -1) const + { + if (_idx == -1) + _idx = m_currIndex; + return (_idx >= 0 && _idx > m_currIndex ? -1 : m_queries[_idx]->m_returnCode); + } + + long RowsInserted(int _idx = -1) const { - return dataSet; + if (_idx == -1) + _idx = m_currIndex; + return (_idx >= 0 && _idx > m_currIndex ? -1L : m_queries[_idx]->m_rowsInserted); } - int ReturnCode() const + + Oid InsertedOid(int _idx = -1) const { - return rc; + if (_idx == -1) + _idx = m_currIndex; + return (_idx >= 0 && _idx > m_currIndex ? -1L : m_queries[_idx]->m_insertedOid); } - long RowsInserted() const + + inline void CancelExecution() + { + m_cancelled = true; + } + + inline size_t GetNumberQueries() { - return rowsInserted; + return m_queries.GetCount(); } - OID InsertedOid() const + + size_t QueriesExecuted() { - return insertedOid; + return m_currIndex + 1; } - wxString GetMessagesAndClear(); - void appendMessage(const wxString &str); + + wxString GetMessagesAndClear(int _idx = -1); + void AppendMessage(const wxString &_str); + + int DeleteReleasedQueries(); private: - int rc; - int resultToRetrieve; - long rowsInserted; - OID insertedOid; - - wxString query; - pgConn *conn; - PGresult *result; - wxString messages; - pgSet *dataSet; - wxCriticalSection criticalSection; - - void *data; - wxWindow *caller; - long eventId; - - int execute(); - int raiseEvent(int retval = 0); - - void appendMessageRaw(const wxString &str); + int Execute(); + int RaiseEvent(int _retval = 0); + + // Queries to be exectued + pgBatchQueryArray m_queries; + // Current running query index + int m_currIndex; + // Connection object + pgConn *m_conn; + // Execution cancelled? + bool m_cancelled; + // Does this thread support multiple queries + bool m_multiQueries; + // Use EDB callable statement (if available and require) + bool m_useCallable; + // Is executing a query + bool m_executing; + // Queries are being accessed at this time + wxMutex m_queriesLock; + // When one thread is accesing messages, other should not be able to access it + wxCriticalSection m_criticalSection; + // Event Handler + wxEvtHandler *m_caller; + // Database server notice-processor + PQnoticeProcessor m_processor; + // Notice Handler + void *m_noticeHandler; + }; #endif diff --git a/pgadmin/include/db/pgSet.h b/pgadmin/include/db/pgSet.h index f6543b1..9a5c7b5 100644 --- a/pgadmin/include/db/pgSet.h +++ b/pgadmin/include/db/pgSet.h @@ -131,6 +131,14 @@ public: return conv; } + wxString GetCommandStatus() const + { + if (res) + { + return wxString(PQcmdStatus(res), conv); + } + return wxEmptyString; + } protected: pgConn *conn; diff --git a/pgadmin/include/debugger/ctlCodeWindow.h b/pgadmin/include/debugger/ctlCodeWindow.h deleted file mode 100644 index 6267ad9..0000000 --- a/pgadmin/include/debugger/ctlCodeWindow.h +++ /dev/null @@ -1,250 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// ctlCodeWindow.h - debugger -// -////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// class ctlCodeWindow -// -// This class implements the debugger window. The constructor expects a string -// that contains a TCP port number - the constructor connects to the debugger -// server waiting at that port. -// -// A ctlCodeWindow object creates (and manages) a toolbar and handles toolbar -// and keystroke messages. The input messages are treated as debugger commands. -// -// The m_view member is a ctlSQLBox that displays the code for the PL -// function that you're debugging. The m_hilite member tracks the current -// line (that is, the line about to execute). We use hilight the current line. -// If m_hilite is -1, there is no current line. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef CTLCODEWINDOW_H -#define CTLCODEWINDOW_H - -#include - -#include "ctl/ctlSQLBox.h" -#include "debugger/frmDebugger.h" -#include "debugger/dbgBreakPoint.h" -#include "debugger/ctlTabWindow.h" - -class dbgPgConn; -class dbgResultset; -class dbgConnProp; -class wsWaitingDialog; -class ctlStackWindow; -class ctlMessageWindow; -class ctlVarWindow; -class ctlResultGrid; - - -#define MARKERINDEX_TO_MARKERMASK( MI ) ( 1 << MI ) - -class wsCodeCache -{ -public: - wsCodeCache() {} - wsCodeCache(const wxString &packageOID, const wxString &funcOID, const wxString &source, const wxString &signature); - - const wxString &getSource() - { - return( m_sourceCode ); - } - const wxString &getSignature() - { - return( m_signature ); - } - -private: - wxString m_packageOID; // Package OID - wxString m_funcOID; // Function OID - wxString m_sourceCode; // Source code for this function - wxString m_signature; // Function sig - -}; - -class ctlCodeWindow : public pgFrame -{ - DECLARE_CLASS( ctlCodeWindow ) - -public: - ctlCodeWindow( frmDebugger *parent, wxWindowID id, const dbgConnProp &connProps ); - - void OnClose(wxCloseEvent &event); - void startLocalDebugging(); // Start debugging - void resumeLocalDebugging(); // Start debugging, already attached to the proxy - void startGlobalDebugging(); // Start debugging - void OnCommand( wxCommandEvent &event ); // Handle menu/toolbar commands - void OnSelectFrame( wxCommandEvent &event ); // Select a different stack frame - void OnMarginClick( wxStyledTextEvent &event ); // Set/clear breakpoint on margin click - void OnPositionStc( wxStyledTextEvent &event ); // update the status bar text - void OnVarChange( wxGridEvent &event ); // User changed a variable - void processResult( wxString &result ); // Handle a message from the debugger server - void OnNoticeReceived( wxCommandEvent &event ); // NOTICE received from server - void OnResultSet( PGresult *result ); // Result set received from server - void disableTools(); // Disable toolbar tools - void enableTools(); // Enable toolbar tools - - frmDebugger *GetFrame() - { - return m_parent; - } - - bool m_targetAborted; // Have we aborted the target? (true) or are we waiting for a breakpoint? (false) - bool m_targetComplete; // Is the target complete? (true) or is it still running (or aborted)? (false) - dbgBreakPointList &getBreakpointList(); - - WX_DECLARE_STRING_HASH_MAP( wsCodeCache, sourceHash ); - -private: - - bool isBreakpoint(int lineNumber) - { - return (m_view->MarkerGet( lineNumber ) & MARKERINDEX_TO_MARKERMASK( MARKER_BREAKPOINT ) ? true : false); - } - void clearBreakpoint( int lineNumber, bool requestUpdate ); - void setBreakpoint( int lineNumber ); - - ctlStackWindow *getStackWindow() - { - return( m_stackWindow ); - } - ctlMessageWindow *getMessageWindow() - { - return( m_tabWindow->getMessageWindow()); - } - - ctlVarWindow *getVarWindow( bool create ) - { - return( m_tabWindow->getVarWindow( create )); - } - ctlVarWindow *getParamWindow( bool create ) - { - return( m_tabWindow->getParamWindow( create )); - } - ctlVarWindow *getPkgVarWindow( bool create ) - { - return( m_tabWindow->getPkgVarWindow( create )); - } - ctlResultGrid *getResultWindow() - { - return( m_tabWindow->getResultWindow()); - } - - void setTools(bool enable); // Enable/disable debugger options - void OnIdle( wxIdleEvent &event ); // Idle processor - void OnTimer( wxTimerEvent &event ); // Clock tick - - int getLineNo( ); // Compute line number for current cursor position - void closeConnection(); // Closes proxy connection - void updateUI( dbgResultset &breakpoint ); // Update the lazy parts of the UI - void updateSourceCode( dbgResultset &breakpoint ); // Update the source code window - bool connectionLost( dbgResultset &resultSet ); // Returns true if proxy lost it's connection - bool gotFatalError( dbgResultset &resultSet ); // Returns true if result set indicates a fatal error has occurred - void popupError(dbgResultset &resultSet); - void addBreakpoint( dbgBreakPoint *breakpoint, wxEventType nextStep ); - - void ResultPortAttach( wxCommandEvent &event ); // Attach to debugger port complete - void ResultBreakpoint( wxCommandEvent &event ); // Breakpoint encountered - void ResultVarList( wxCommandEvent &event ); // Variable list complete - void ResultStack( wxCommandEvent &event ); // Stack trace retrieval complete - void ResultSource( wxCommandEvent &event ); // Source code retrieval complete - void ResultBreakpoints( wxCommandEvent &event ); // Breakpoint list retrieval complete - void ResultNewBreakpoint( wxCommandEvent &event ); // Set Breakpoint command complete - void ResultNewBreakpointWait( wxCommandEvent &event ); // Set Breakpoint command complete, wait for a target process - void ResultDeletedBreakpoint( wxCommandEvent &event ); // Drop Breakpoint command complete - void ResultDepositValue( wxCommandEvent &event ); // Deposit Value command complete - void ResultAbortTarget( wxCommandEvent &event ); // Abort target command complete - void ResultAddBreakpoint( wxCommandEvent &event ); // getTargetInfo() complete, add a breakpoint - void ResultListenerCreated( wxCommandEvent &event ); // Global listener created, ready to wait for a target - void ResultTargetReady( wxCommandEvent &event ); // Target session attached, ready to wait for a breakpoint - void ResultLastBreakpoint( wxCommandEvent &event ); // Adding last breakpoint - - dbgPgConn *m_dbgConn; // Network connection to debugger server - wxString m_debugPort; // Port at which debugger server is listening - - frmDebugger *m_parent; // Parent window - int m_currentLineNumber; // Current line number - - ctlSQLBox *m_view; // Window that displays function source code - ctlStackWindow *m_stackWindow; // Stack Window - ctlTabWindow *m_tabWindow; // Tab Window - - typedef enum - { - SESSION_TYPE_UNKNOWN, // Session could be in-context or direct - SESSION_TYPE_INCONTEXT, // Session is configured for in-context debugging - SESSION_TYPE_DIRECT // Session is configured for direct debugging - } eSessionType; - - eSessionType m_sessionType; // Debugging mode is in-context or direct? - bool m_updateVars; // Update variable window in next idle period? - bool m_updateStack; // Update stack window in next idle period? - bool m_updateBreakpoints; // Update breakpoints in next idle period? - dbgBreakPointList m_breakpoints; // List of initial breakpoints to create - - enum - { - MARKER_CURRENT = 0x02, // Current line marker - MARKER_CURRENT_BG = 0x04, // Current line marker - background hilight - MARKER_BREAKPOINT = 0x01, // Breakpoint marker - }; - - sourceHash m_sourceCodeMap; - - wxString m_focusPackageOid; // Which package has the debug focus? - wxString m_focusFuncOid; // Which function has the debug focus? - wxString m_displayedFuncOid; // Which function are we currently displaying? (function OID component) - wxString m_displayedPackageOid; // Which function are we currently displaying? (package OID component) - wxString m_sessionHandle; // Handle to proxy's server session - wxString m_targetName; // User-friendly target name - - wxProgressDialog *m_progressBar; // "Waiting for target" dialog - wxTimer m_timer; - bool findSourceInCache( const wxString &packageOID, const wxString &funcOID); - void clearSourceCache(); - void getSource(const wxString &packageOID, const wxString &funcOID); - void cacheSource(const wxString &packageOID, const wxString &funcOID, const wxString &sourceCode, const wxString &signature); - void displaySource(const wxString &packageOID, const wxString &funcID); - void unhilightCurrentLine(); - void launchWaitingDialog(); - - void clearAllBreakpoints(); - void clearBreakpointMarkers(); - void stopDebugging(); - - static wxString m_commandAttach; - static wxString m_commandWaitForBreakpoint; - static wxString m_commandGetVars; - static wxString m_commandGetStack; - static wxString m_commandGetBreakpoints; - static wxString m_commandGetSourceV1; - static wxString m_commandGetSourceV2; - static wxString m_commandStepOver; - static wxString m_commandStepInto; - static wxString m_commandContinue; - static wxString m_commandSetBreakpointV1; - static wxString m_commandSetBreakpointV2; - static wxString m_commandClearBreakpointV1; - static wxString m_commandClearBreakpointV2; - static wxString m_commandSelectFrame; - static wxString m_commandDepositValue; - static wxString m_commandAbortTarget; - static wxString m_commandGetTargetInfo; - static wxString m_commandAddBreakpointEDB; - static wxString m_commandAddBreakpointPG; - static wxString m_commandCreateListener; - static wxString m_commandWaitForTarget; - - DECLARE_EVENT_TABLE() -}; - -#endif diff --git a/pgadmin/include/debugger/ctlMessageWindow.h b/pgadmin/include/debugger/ctlMessageWindow.h index aaab315..f588880 100644 --- a/pgadmin/include/debugger/ctlMessageWindow.h +++ b/pgadmin/include/debugger/ctlMessageWindow.h @@ -23,21 +23,14 @@ class ctlMessageWindow : public wxTextCtrl { - DECLARE_CLASS( ctlMessageWindow ) + DECLARE_CLASS(ctlMessageWindow) public: - ctlMessageWindow( wxWindow *parent, wxWindowID id ); + ctlMessageWindow(wxWindow *parent, wxWindowID id); - void addMessage( wxString message ); // Add a message to the window - void delMessage( const char *name = NULL ); // Remove a message from the window - wxString getMessage( int row ); - -private: - - typedef struct - { - int m_row; // Row number for this variable/grid cell - } gridCell; + void AddMessage(wxString message); // Add a message to the window + void DelMessage(const char *name = NULL); // Remove a message from the window + wxString GetMessage(int row); }; #endif diff --git a/pgadmin/include/debugger/ctlResultGrid.h b/pgadmin/include/debugger/ctlResultGrid.h index fa7590e..76fdc28 100644 --- a/pgadmin/include/debugger/ctlResultGrid.h +++ b/pgadmin/include/debugger/ctlResultGrid.h @@ -25,12 +25,13 @@ class ctlResultGrid : public wxGrid { - DECLARE_CLASS( ctlResultGrid ) + DECLARE_CLASS(ctlResultGrid) public: - ctlResultGrid( wxWindow *parent, wxWindowID id ); + ctlResultGrid(wxWindow *parent, wxWindowID id); - void fillGrid( PGresult *result ); // Copy a result set into the grid + // Copy a result set into the grid + void FillResult(pgSet *set); }; #endif diff --git a/pgadmin/include/debugger/ctlStackWindow.h b/pgadmin/include/debugger/ctlStackWindow.h index b6d94c0..cf64f81 100644 --- a/pgadmin/include/debugger/ctlStackWindow.h +++ b/pgadmin/include/debugger/ctlStackWindow.h @@ -36,8 +36,8 @@ class ctlStackWindow : public wxListBox public: ctlStackWindow(wxWindow *parent, wxWindowID id, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxCLIP_CHILDREN | wxSW_3D, const wxString &name = wxT( "stackWindow" )); - void clear(); // Remove all frames from the stack trace - void setStack(const wxArrayString &stack); // Add an array of frames to the stack trace + void ClearStack(); // Remove all frames from the stack trace + void SetStack(const wxArrayString &stack); // Add an array of frames to the stack trace }; #endif diff --git a/pgadmin/include/debugger/ctlTabWindow.h b/pgadmin/include/debugger/ctlTabWindow.h index 7d09f9b..85acd0b 100644 --- a/pgadmin/include/debugger/ctlTabWindow.h +++ b/pgadmin/include/debugger/ctlTabWindow.h @@ -32,34 +32,37 @@ #include "debugger/ctlStackWindow.h" #include "debugger/ctlResultGrid.h" -WX_DECLARE_HASH_MAP( int, int, wxIntegerHash, wxIntegerEqual, wsTabHash ); +WX_DECLARE_HASH_MAP(int, int, wxIntegerHash, wxIntegerEqual, wsTabHash); class ctlTabWindow : public ctlAuiNotebook { - DECLARE_CLASS( ctlTabWindow ) + DECLARE_CLASS(ctlTabWindow) public: - ctlTabWindow(wxWindow *parent, wxWindowID id, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxCLIP_CHILDREN | wxSW_3D, const wxString &name = wxT( "layoutWindow" )); + ctlTabWindow(wxWindow *parent, wxWindowID id, + const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, + long style = wxCLIP_CHILDREN | wxSW_3D, + const wxString &name = wxT("layoutWindow")); - ctlVarWindow *getVarWindow( bool create = true ); // Returns a pointer to the local-variables window (creates it if requested) - ctlVarWindow *getPkgVarWindow( bool create = true ); // Returns a pointer to the package-variables window (creates it if requested) - ctlVarWindow *getParamWindow( bool create = true ); // Returns a pointer to the parameters window (creates it if requested) + ctlVarWindow *GetVarWindow(bool create = true); // Returns a pointer to the local-variables window (creates it if requested) + ctlVarWindow *GetPkgVarWindow(bool create = true); // Returns a pointer to the package-variables window (creates it if requested) + ctlVarWindow *GetParamWindow(bool create = true); // Returns a pointer to the parameters window (creates it if requested) - ctlResultGrid *getResultWindow( void ); // Returns a pointer to the result window (creates it if necessary) - ctlStackWindow *getStackWindow( void ); // Returns a pointer to the stack-trace window (creates it if necessary) - ctlMessageWindow *getMessageWindow( void ); // Returns a pointer to the DBMS messages window (creates it if necessary) - void selectTab( wxWindowID id ); + ctlResultGrid *GetResultWindow(); // Returns a pointer to the result window (creates it if necessary) + ctlStackWindow *GetStackWindow(); // Returns a pointer to the stack-trace window (creates it if necessary) + ctlMessageWindow *GetMessageWindow(); // Returns a pointer to the DBMS messages window (creates it if necessary) + void SelectTab(wxWindowID id); private: - ctlResultGrid *m_resultWindow; // Displays the result set from a query - ctlVarWindow *m_varWindow; // Displays the local variables when debugging a PL function - ctlVarWindow *m_pkgVarWindow; // Displays the package variables when debugging a PL function - ctlStackWindow *m_stackWindow; // Displays the current call stack - ctlVarWindow *m_paramWindow; // Displays the parameters when debugging a PL function - ctlMessageWindow *m_messageWindow; // Displays the DBMS messages when debugging a PL function + ctlResultGrid *m_resultWindow; // Displays the result set from a query + ctlVarWindow *m_varWindow; // Displays the local variables when debugging a PL function + ctlVarWindow *m_pkgVarWindow; // Displays the package variables when debugging a PL function + ctlStackWindow *m_stackWindow; // Displays the current call stack + ctlVarWindow *m_paramWindow; // Displays the parameters when debugging a PL function + ctlMessageWindow *m_messageWindow; // Displays the DBMS messages when debugging a PL function - wsTabHash *m_tabMap; // Map window ID's to tab numbers; + wsTabHash *m_tabMap; // Map window ID's to tab numbers; }; #endif diff --git a/pgadmin/include/debugger/ctlVarWindow.h b/pgadmin/include/debugger/ctlVarWindow.h index 0d2a4a2..665531f 100644 --- a/pgadmin/include/debugger/ctlVarWindow.h +++ b/pgadmin/include/debugger/ctlVarWindow.h @@ -35,38 +35,40 @@ class ctlVarWindow : public wxGrid { - DECLARE_CLASS( ctlVarWindow ) + DECLARE_CLASS(ctlVarWindow) public: - ctlVarWindow( wxWindow *parent, wxWindowID id ); + ctlVarWindow(wxWindow *parent, wxWindowID id); - void addVar( wxString name, wxString value, wxString type, bool readOnly ); // Add a variable to the window - void delVar( wxString name = wxEmptyString); // Remove a variable from the window - wxString getVarName( int row ); - wxString getVarValue( int row ); + // Add a variable to the window + void AddVar(wxString name, wxString value, wxString type, bool readOnly); + // Remove a variable from the window + void DelVar(wxString name = wxEmptyString); + + wxString GetVarName(int row); + wxString GetVarValue(int row); private: // The content of a grid cell is defined by the gridCell structure - typedef struct { - int m_row; // Row number for this variable/grid cell - wxString m_value; // Variable value - wxString m_type; // Variable type + int m_row; // Row number for this variable/grid cell + wxString m_value; // Variable value + wxString m_type; // Variable type } gridCell; enum { - COL_NAME = 0, // Column 0 contains the variable name - COL_TYPE, // This column contains the variable type - COL_VALUE // This column contains the variable value + COL_NAME = 0, // Column 0 contains the variable name + COL_TYPE, // This column contains the variable type + COL_VALUE // This column contains the variable value }; // The m_cells hash translates variable names into gridCell references public: - WX_DECLARE_STRING_HASH_MAP( gridCell, wsCellHash ); - WX_DECLARE_HASH_SET( wxString, wxStringHash, wxStringEqual, wsStringSet ); + WX_DECLARE_STRING_HASH_MAP(gridCell, wsCellHash); + WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, wsStringSet); private: wsStringSet m_hiddenNames; // List of hidden variable names diff --git a/pgadmin/include/debugger/dbgBreakPoint.h b/pgadmin/include/debugger/dbgBreakPoint.h index b327504..486038a 100644 --- a/pgadmin/include/debugger/dbgBreakPoint.h +++ b/pgadmin/include/debugger/dbgBreakPoint.h @@ -21,36 +21,29 @@ class dbgBreakPoint { public: + dbgBreakPoint(const wxString &_funcOid, const wxString &_pkgOid = wxT("0"), + const wxString &_lineNo = wxT("NULL")) + : m_func(_funcOid), m_pkg(_pkgOid), m_lineNo(_lineNo) {} - enum eTargetType + wxString &GetFunctionOid() { - TRIGGER, - FUNCTION, - PROCEDURE, - OID - }; - - dbgBreakPoint(eTargetType targetType, const wxString &target, const wxString &process ): m_targetType(targetType), m_target(target), m_targetProcess(process) {} - - eTargetType getTargetType() - { - return( m_targetType ); + return m_func; } - wxString &getTarget() + wxString &GetPackageOid() { - return( m_target ); + return m_pkg; } - wxString &getTargetProcess() + wxString &GetLineNo() { - return( m_targetProcess ); + return m_lineNo; } -private: - eTargetType m_targetType; - wxString m_target; - wxString m_targetProcess; +private: + wxString m_func; + wxString m_pkg; + wxString m_lineNo; }; -WX_DECLARE_LIST( dbgBreakPoint, dbgBreakPointList ); +WX_DECLARE_LIST(dbgBreakPoint, dbgBreakPointList); #endif diff --git a/pgadmin/include/debugger/dbgConnProp.h b/pgadmin/include/debugger/dbgConnProp.h deleted file mode 100644 index be003de..0000000 --- a/pgadmin/include/debugger/dbgConnProp.h +++ /dev/null @@ -1,38 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgConnProp.h - debugger -// -////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// class dbgConnProp -// -// dbgConnProp object is used to hold a set of connection properties, that is. -// it's a collection of all of the information that we need in order to connect -// to a server. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DBGCONNPROP_H -#define DBGCONNPROP_H - -class dbgConnProp -{ - -public: - wxString m_host; // Host name (or IP-address) - wxString m_database; // Database name - wxString m_userName; // User name - wxString m_password; // Password - wxString m_port; // Port number - wxString m_debugPort; // Port number for debugger connection - int m_sslMode; // SSL Mode - wxString m_applicationName; // Application name, only on 9.0 -}; - -#endif diff --git a/pgadmin/include/debugger/dbgConst.h b/pgadmin/include/debugger/dbgConst.h index d8b6e9e..5075616 100644 --- a/pgadmin/include/debugger/dbgConst.h +++ b/pgadmin/include/debugger/dbgConst.h @@ -18,16 +18,63 @@ #ifndef DBGCONST_H #define DBGCONST_H -const int ID_BTNNEXT = 1800; -const int ID_GRDFUNCARGS = 1810; -const int ID_TXTMESSAGE = 1820; -const int ID_TIMER = 1830; -const int ID_BTNCANCEL = 1840; +const int ID_BTNNEXT = 1800; +const int ID_GRDFUNCARGS = 1810; +const int ID_TXTMESSAGE = 1820; +const int ID_TIMER = 1830; +const int ID_BTNCANCEL = 1840; -const int ID_PARAMGRID = 1000; -const int ID_VARGRID = 1001; -const int ID_MSG_PAGE = 1002; -const int ID_PKGVARGRID = 1003; +const int ID_PARAMGRID = 1000; +const int ID_VARGRID = 1001; +const int ID_MSG_PAGE = 1002; +const int ID_PKGVARGRID = 1003; -#endif +enum +{ + MENU_ID_EXECUTE = 10001, // Execute command entered by user + + MENU_ID_TOGGLE_BREAK, // Set/Unset breakpoint + MENU_ID_CLEAR_ALL_BREAK, // Clear all breakpoints + MENU_ID_CONTINUE, // Continue + MENU_ID_STEP_OVER, // Step over + MENU_ID_STEP_INTO, // Step into + MENU_ID_STOP, // Stop debugging + + MENU_ID_START_DEBUGGING, // Spawn a separate debugger process + MENU_ID_NOTICE_RECEIVED, // NOTICE received from server + WINDOW_ID_STACK, // Tree-control window + WINDOW_ID_CONSOLE, // Console window + WINDOW_ID_TABS, // Tab window + WINDOW_ID_BREAKPOINTS, // Breakpoints window + WINDOW_ID_RESULT_GRID, // Results window + WINDOW_ID_COMMAND, // Command window + SOCKET_ID_DEBUG, // Debugger Socket ID + + MENU_ID_VIEW_TOOLBAR, // View menu options + MENU_ID_VIEW_STACKPANE, + MENU_ID_VIEW_OUTPUTPANE, + MENU_ID_VIEW_DEFAULTVIEW, + + RESULT_ID_ATTACH_TO_PORT, // Debugger - attach to port completed + RESULT_ID_BREAKPOINT, // Debugger - breakpoint reached + RESULT_ID_GET_VARS, // Debugger - variable list complete + RESULT_ID_GET_STACK, // Debugger - stack trace complete + RESULT_ID_GET_BREAKPOINTS, // Debugger - breakpoint list complete + RESULT_ID_NEW_BREAKPOINT, // Debugger - set breakpoint complete + RESULT_ID_NEW_BREAKPOINT_WAIT, // Debugger - set breakpoint complete, wait for target progress + RESULT_ID_DEL_BREAKPOINT, // Debugger - drop breakpoint complete + RESULT_ID_DEPOSIT_VALUE, // Debugger - deposit value complete + RESULT_ID_ADD_BREAKPOINT, // Debugger - target info received, now set a breakpoint + RESULT_ID_LISTENER_CREATED, // Debugger - global listener created + RESULT_ID_TARGET_READY, // Debugger - target session attached + RESULT_ID_LAST_BREAKPOINT, // Debugger - last breakpoint created + RESULT_ID_ARGS_UPDATED, // Debugger - Values are evaluated and been updated in the arguments + RESULT_ID_ARGS_UPDATE_ERROR, // Debugger - Error while evaluateling the value for the arguments + RESULT_ID_DIRECT_TARGET_COMPLETE, // DirectDebug - target function complete + + ID_DEBUG_INITIALIZER // Debugger - debug package initializer? checkbox + +}; + +#endif diff --git a/pgadmin/include/debugger/dbgController.h b/pgadmin/include/debugger/dbgController.h new file mode 100644 index 0000000..6068b87 --- /dev/null +++ b/pgadmin/include/debugger/dbgController.h @@ -0,0 +1,173 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2013, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// dbgController.h - Debugger controller +// +////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// class dbgController +// +// A dbgController object controls the behaviour of the debugger. It stays +// in the central of the whole debugger mechanism. It controls the flow of +// execution and also, asks the view to update the user presentation (when +// needed), also execute commands based on the user inputs. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DBGCONTROLLER_H +#define DBGCONTROLLER_H + +#include +#include + +#include "db/pgQueryResultEvent.h" +#include "debugger/dbgTargetInfo.h" +#include "debugger/dbgModel.h" + +class frmMain; +class frmDebugger; + +typedef enum +{ + DBG_SESSION_TYPE_UNKNOWN, // Session could be in-context or direct + DBG_SESSION_TYPE_INCONTEXT, // Session is configured for in-context debugging + DBG_SESSION_TYPE_DIRECT // Session is configured for direct debugging +} DebuggerSessionType; + + +typedef enum +{ + DEBUGGER_UNKNOWN_API = 0, + DEBUGGER_V1_API = 1, + DEBUGGER_V2_API = 2, + DEBUGGER_V3_API = 3 +} DebuggerApiVersion; + + +class dbgController : public wxEvtHandler +{ +public: + dbgController(frmMain *_main, pgObject *_obj, bool _directDebugging = false); + ~dbgController(); + + dbgTargetInfo *GetTargetInfo(); + dbgModel *GetModel() + { + return m_model; + } + + bool CanRestart() + { + return (m_dbgConn && (m_dbgConn->GetStatus() != PGCONN_OK)); + } + + // Debugging actions (called from the frmDebugger) + bool Start(); + void ClearBreakpoint(int _lineNo); + void SetBreakpoint(int _lineNo); + void Countinue(); + void StepOver(); + void StepInto(); + bool Stop(); + void DepositValue(const wxString &_name, const wxString &_val); + void SelectFrame(int _frameNo); + void UpdateBreakpoints(); + + bool HandleQuery(pgBatchQuery *_qry, const wxString &_err); + + // Closing Debugger + void CloseDebugger(); + bool ExecuteTarget(); + bool IsTerminated() + { + return m_terminated; + } + + // Event functions + void OnNoticeReceived(wxCommandEvent &); + void OnStartDebugging(wxCommandEvent &); + + void ResultTargetComplete(pgQueryResultEvent &); + void ResultPortAttach(pgQueryResultEvent &); + void ResultBreakpoint(pgQueryResultEvent &); + void ResultVarList(pgQueryResultEvent &); + void ResultStack(pgQueryResultEvent &); + void ResultBreakpoints(pgQueryResultEvent &); + void ResultNewBreakpoint(pgQueryResultEvent &); + void ResultNewBreakpointWait(pgQueryResultEvent &); + void ResultDeletedBreakpoint(pgQueryResultEvent &); + void ResultDepositValue(pgQueryResultEvent &); + void ResultListenerCreated(pgQueryResultEvent &); + void ResultTargetReady(pgQueryResultEvent &); + +private: + static void NoticeHandler(void *arg, const char *message); + +private: + const static wxString ms_cmdDebugSPLV1; + const static wxString ms_cmdDebugSPLV2; + const static wxString ms_cmdDebugPLPGSQLV1; + const static wxString ms_cmdDebugPLPGSQLV2; + const static wxString ms_cmdAttachToPort; + const static wxString ms_cmdWaitForBreakpointV1; + const static wxString ms_cmdWaitForBreakpointV2; + const static wxString ms_cmdGetVars; + const static wxString ms_cmdGetStack; + const static wxString ms_cmdGetBreakpoints; + const static wxString ms_cmdStepOverV1; + const static wxString ms_cmdStepOverV2; + const static wxString ms_cmdStepIntoV1; + const static wxString ms_cmdStepIntoV2; + const static wxString ms_cmdContinueV1; + const static wxString ms_cmdContinueV2; + const static wxString ms_cmdSetBreakpointV1; + const static wxString ms_cmdSetBreakpointV2; + const static wxString ms_cmdClearBreakpointV1; + const static wxString ms_cmdClearBreakpointV2; + const static wxString ms_cmdSelectFrameV1; + const static wxString ms_cmdSelectFrameV2; + const static wxString ms_cmdDepositValue; + const static wxString ms_cmdAbortTarget; + const static wxString ms_cmdAddBreakpointEDB; + const static wxString ms_cmdAddBreakpointPG; + const static wxString ms_cmdGetTargetInfo; + const static wxString ms_cmdCreateListener; + const static wxString ms_cmdWaitForTarget; + +private: + // Debugger Version for the current server + DebuggerApiVersion m_ver; + + // Session type + DebuggerSessionType m_sessionType; + + // Debugging Terminated + bool m_terminated; + + // Main Window for the debugger + frmDebugger *m_frm; + + // Connection for collecting the current debugging information + pgConn *m_dbgConn; + // Connetion Thread for fetching the debugging information in background + pgQueryThread *m_dbgThread; + // Connection to execute the function/procedure for the direct debugging + pgConn *m_execConn; + // Connection-thread to run the function in background for debugging + pgQueryThread *m_execConnThread; + + // Debugger Data Model + dbgModel *m_model; + + DECLARE_EVENT_TABLE() + +}; + +#define MARKERINDEX_TO_MARKERMASK( MI ) ( 1 << MI ) + +#endif diff --git a/pgadmin/include/debugger/dbgDbResult.h b/pgadmin/include/debugger/dbgDbResult.h deleted file mode 100644 index 77e2296..0000000 --- a/pgadmin/include/debugger/dbgDbResult.h +++ /dev/null @@ -1,53 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgDbResult.h - debugger -// -////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// class dbgDbResult -// -// This class is used to convert a PGresult (a query result set) into a wxEvent -// We create a dbgDbResult object whenever a result set arrives from the server -// and then we send that object (which is really a wxEvent) through the normal -// wxWidgets event handler mechanism. -// -// The arrival of a result set thus becomes a wxEvent. We create dbgDbResult -// objects in dbgPgThread. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DBGDBRESULT_H -#define DBGDBRESULT_H - -#include - -BEGIN_DECLARE_EVENT_TYPES() -DECLARE_EVENT_TYPE( dbgDBRESULT, wxID_HIGHEST + 1 ) -END_DECLARE_EVENT_TYPES() - -class dbgDbResult : public wxEvent -{ - -public: - dbgDbResult( PGresult *result ) : wxEvent( 0, dbgDBRESULT ), m_result( result ) { } - - wxEvent *Clone( void ) const - { - return( new dbgDbResult( *this )); - } - PGresult *getResult( void ) - { - return( m_result ); - } - -private: - PGresult *m_result; -}; - -#endif diff --git a/pgadmin/include/debugger/dbgModel.h b/pgadmin/include/debugger/dbgModel.h new file mode 100644 index 0000000..6bcf332 --- /dev/null +++ b/pgadmin/include/debugger/dbgModel.h @@ -0,0 +1,143 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin III - PostgreSQL Tools +// +// Copyright (C) 2002 - 2013, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +// dbgModel.h - Debugger Model +// +////////////////////////////////////////////////////////////////////////// + +#ifndef DBGMODEL_H +#define DBGMODEL_H + +#include + +#include "debugger/dbgBreakPoint.h" +#include "debugger/dbgTargetInfo.h" + + +class dbgCachedStack +{ +public: + dbgCachedStack() {} + dbgCachedStack(const wxString &_pkg, const wxString &_func, + const wxString &_target, const wxString &_arg, const wxString &_src) + : m_func(_func), m_pkg(_pkg), m_source(_src), + m_target(_target), m_arg(_arg) {} + + dbgCachedStack(const dbgCachedStack &_src) + : m_func(_src.m_func), m_pkg(_src.m_pkg), m_source(_src.m_source), + m_target(_src.m_target), m_arg(_src.m_arg) {} + + dbgCachedStack &operator =(const dbgCachedStack &_src) + { + m_func = _src.m_func; + m_pkg = _src.m_pkg; + m_source = _src.m_source; + m_target = _src.m_target; + m_arg = _src.m_arg; + + return *this; + } + +private: + wxString m_pkg; // Package OID + wxString m_func; // Function OID + wxString m_target; // Target Name + wxString m_arg; // Argument passed to the target + wxString m_source; // Source code for this function + + friend class frmDebugger; +}; + +WX_DECLARE_STRING_HASH_MAP(dbgCachedStack, dbgSourceHash); + +class dbgModel +{ +public: + dbgModel(Oid _target, pgConn *_conn); + + dbgTargetInfo *GetTarget() + { + return m_target; + } + dbgBreakPointList &GetBreakPoints() + { + return m_breakpoints; + } + + wxString &GetPort() + { + return m_port; + } + wxString &GetSession() + { + return m_session; + } + wxString &GetTargetPid() + { + return m_targetPid; + } + + bool GetSource(const wxString &_funcOid, dbgCachedStack *_cached = NULL); + void ClearCachedSource(); + void AddSource(const wxString &_funcOid, const dbgCachedStack &cached); + + bool RequireDisplayUpdate() + { + return (m_focusedFuncOid != m_displayedFuncOid || + m_displayedPkgOid != m_focusedPkgOid); + } + + wxString &GetFocusedPackage() + { + return m_focusedPkgOid; + } + wxString &GetDisplayedPackage() + { + return m_displayedPkgOid; + } + wxString &GetFocusedFunction() + { + return m_focusedFuncOid; + } + wxString &GetDisplayedFunction() + { + return m_displayedFuncOid; + } + + int &GetCurrLineNo() + { + return m_currLineNo; + } + +private: + // Target Information + dbgTargetInfo *m_target; + + // Break-Points + dbgBreakPointList m_breakpoints; + + // Debugging Port, session-handle & target-backend pid + wxString m_port; + wxString m_session; + wxString m_targetPid; + + // Cached source-code for the stacked functions + dbgSourceHash m_sourceMap; + + // Current focused function-information + wxString m_focusedFuncOid; + wxString m_focusedPkgOid; + + // Current displayed function-information + wxString m_displayedFuncOid; + wxString m_displayedPkgOid; + + // Current Line number + int m_currLineNo; +}; + +#endif diff --git a/pgadmin/include/debugger/dbgPgConn.h b/pgadmin/include/debugger/dbgPgConn.h deleted file mode 100644 index 31d5ce3..0000000 --- a/pgadmin/include/debugger/dbgPgConn.h +++ /dev/null @@ -1,99 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgPgConn.h - debugger -// -////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// class dbgPgConn -// -// A dbgPgConn object encapsulates the connection to a PostgreSQL server. This -// class is a wrapper around a Pgconn that provides a convenient constructor -// and a few member functions. -// -// This class doesn't do much - instead, the real work happens in a dbgPgThread -// (a separate thread) that initiates commands and processes result sets. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DBGPGCONN_H -#define DBGPGCONN_H - -#include - -#include "debugger/frmDebugger.h" -#include "debugger/dbgPgThread.h" -#include "debugger/dbgConnProp.h" - -enum DebuggerApiVersions -{ - DEBUGGER_UNKNOWN_API = 0, - DEBUGGER_V1_API = 1, - DEBUGGER_V2_API = 2, - DEBUGGER_V3_API = 3 -}; - -class dbgPgParams -{ -public: - int nParams; - Oid *paramTypes; - char **paramValues; - int *paramModes; -}; - -class dbgPgConn -{ -public: - - dbgPgConn( frmDebugger *frame, - const wxString &server = wxT( "" ), - const wxString &database = wxT( "" ), - const wxString &username = wxT( "" ), - const wxString &password = wxT( "" ), - const wxString &port = wxT( "5432" ), - int sslmode = 0, - const wxString &applicationname = wxT( "" )); - - dbgPgConn( frmDebugger *frame, const dbgConnProp &props, bool startThread = true ); - - ~dbgPgConn(); - - bool BackendMinimumVersion(int major, int minor); - bool EdbMinimumVersion(int major, int minor); - bool GetIsEdb(); - bool GetIsGreenplum(); - wxString qtDbString(const wxString &value); - DebuggerApiVersions DebuggerApiVersion(); - wxString GetVersionString(); - bool isConnected() const; // Returns true if the connection attempt succeeded - const wxString getName() const; // Returns human-friendly name for this connection - const wxString getHost() const; // Returns the host-name (or IP address) for this connection - const wxString getDatabase() const; // Returns the name of the database that we're connected to - PGconn *getConnection(); // Returns the libpq connection handle - void Close(); // Close this connection - void Cancel(); // Cancel any ongoing queries - - void startCommand( const wxString &command, wxEvtHandler *caller, wxEventType eventType = wxEVT_NULL, dbgPgParams *params = NULL ); // Starts executing a command - void setNoticeHandler( PQnoticeProcessor handler, void *arg ); // Registers a NOTICE handler - PGresult *waitForCommand( const wxString &command ); // Starts a command and waits for completion - -private: - - void Init( const wxString &server, const wxString &database, const wxString &userName, const wxString &password, const wxString &port, int sslmode, const wxString &applicationname, bool startThread ); - - PGconn *m_pgConn; // libpq connection handler - dbgPgThread *m_workerThread; // Worker thread (this thread interacts with the server) - frmDebugger *m_frame; - int m_minorVersion, m_majorVersion; - bool m_isEdb; - bool m_isGreenplum; - DebuggerApiVersions m_debuggerApiVersion; -}; - -#endif diff --git a/pgadmin/include/debugger/dbgPgThread.h b/pgadmin/include/debugger/dbgPgThread.h deleted file mode 100644 index e100133..0000000 --- a/pgadmin/include/debugger/dbgPgThread.h +++ /dev/null @@ -1,95 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgPgThread.h - debugger -// -////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// class dbgPgThread -// -// A dbgPgThread object encapsulates a separate execution thread that interacts -// with a PostgreSQL server. We use a separate thread to keep the main thread -// (and thus the user interface) responsive while we're waiting for the server. -// -// The startCommand() member function initiates a new command - this function -// is called by the UI thread. It stores the command text (and the caller) in -// the dbgPgThread object and then awakens the thread. The dbgPgThread then sends -// the command string to the server and waits for a reply. When the reply (a -// result set) arrives from the server, the Entry() member function converts -// the result set (a Pgresult) into a wxEvent (specifically, a dbgDbResult) and -// posts that event to the UI thread's event queue. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DBGPGTHREAD_H -#define DBGPGTHREAD_H - -// #include "debugger/dbgPgConn.h" -class dbgPgConn; -class dbgPgParams; - -class dbgPgThreadCommand -{ -public: - dbgPgThreadCommand( const wxString &command, wxEvtHandler *caller, wxEventType eventType = wxEVT_NULL, dbgPgParams *params = NULL ) : m_command( command ), m_caller( caller ), m_eventType( eventType ), m_params( params ) {}; - - wxString &getCommand() - { - return m_command; - } - dbgPgParams *getParams() - { - return m_params; - } - - wxEvtHandler *getCaller() - { - return( m_caller ); - } - wxEventType getEventType() - { - return( m_eventType ); - } - -private: - wxString m_command; // Text of the command we're supposed to execute - wxEvtHandler *m_caller; // Event handler that we post results to - wxEventType m_eventType; // Event to report when result set arrives - dbgPgParams *m_params; -}; - -WX_DECLARE_LIST( dbgPgThreadCommand, ThreadCommandList ); - -class dbgPgThread : public wxThread -{ - -public: - dbgPgThread(dbgPgConn &owner); - - virtual void *Entry(); - void startCommand( const wxString &command, wxEvtHandler *caller, wxEventType eventType = wxEVT_NULL, dbgPgParams *params = NULL ); - void Die(); - -private: - - static void noticeHandler( void *arg, const char *message ); - - dbgPgThreadCommand *getNextCommand(); // Grab next command from queue - - dbgPgConn &m_owner; // Connection to the PostgreSQL server - wxSemaphore m_queueCounter; // Number of entries in queue (thread synchronizer) - wxMutex m_queueMutex; // Mutex to serialize access to m_commandQueue - ThreadCommandList m_commandQueue; // Queue of pending commands - - dbgPgThreadCommand *m_currentCommand; // Currently executing command - wxMBConv *conv; - long run; - bool die; -}; - -#endif diff --git a/pgadmin/include/debugger/dbgResultset.h b/pgadmin/include/debugger/dbgResultset.h deleted file mode 100644 index 4c8c392..0000000 --- a/pgadmin/include/debugger/dbgResultset.h +++ /dev/null @@ -1,52 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// pgAdmin III - PostgreSQL Tools -// -// Copyright (C) 2002 - 2013, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -// dbgResultset.h - debugger -// -////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// class dbgResultset -// -// A dbgResultset object encapsulates a result set produced by executing a -// database command. This class is a wrapper around a PGresult handle that -// provides a few convenient member functions. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DBGRESULTSET_H -#define DBGRESULTSET_H - -#include - -class dbgResultset -{ -public: - dbgResultset( PGresult *handle ); - - const char *getRawErrorMessage(); // Return error message as a char * - const wxString getErrorMessage(); // Return error message as a wxString - const wxString getString(int column, int row = 0); - const wxString getString(const wxString &columnName, int row = 0); - long getLong(int column, int row = 0); - long getLong(const wxString &columnName, int row = 0); - bool getBool(int column, int row = 0); - bool getBool(const wxString &columnName, int row = 0); - int getRowCount() - { - return(PQntuples( m_handle)); - } - bool columnExists(const wxString &columnname); - - ExecStatusType getCommandStatus(); - -private: - - PGresult *m_handle; - -}; -#endif diff --git a/pgadmin/include/debugger/dbgTargetInfo.h b/pgadmin/include/debugger/dbgTargetInfo.h index 5649513..ece98a4 100644 --- a/pgadmin/include/debugger/dbgTargetInfo.h +++ b/pgadmin/include/debugger/dbgTargetInfo.h @@ -11,57 +11,103 @@ #ifndef DBGTARGETINFO_H #define DBGTARGETINFO_H +#include +#include "db/pgConn.h" +#include "db/pgQueryThread.h" + + //////////////////////////////////////////////////////////////////////////////// -// class wsArgInfo +// class dbgArgInfo // -// A wsArgInfo object contains information about a function (or procedure) +// A dbgArgInfo object contains information about a function (or procedure) // argument. Inside of each wsArgInfo object, we store the name of the argument, // the argument type, and the argument mode (IN (i), OUT (o), or INOUT (b)). // // Once the user has had a chance to enter values for each of the IN and INOUT // arguments, we store those values inside of the corresponding wsArgInfo objects - -class wsArgInfo +class dbgArgInfo { public: - wsArgInfo( const wxString &argName, const wxString &argType, const wxString &argMode, const wxString &argTypeOid ); + dbgArgInfo(const wxString &_name, const wxString &_type, Oid _typeOid, + short _mode = pgParam::PG_PARAM_IN); - const wxString &getName() + Oid GetType() { - return m_name; + return m_typeOid; } - const wxString &getType() + const wxString &GetTypeName() { return m_type; } - const wxString &getMode() + wxString &GetName() + { + return m_name; + } + short GetMode() { return m_mode; } - const Oid getTypeOid() + bool IsArray() { - return m_typeOid; + return !(m_baseType.IsEmpty()); + } + const wxString &GetBaseType() + { + return m_baseType; + } + bool &Null() + { + return m_null; + } + wxString &Value() + { + return m_val; + } + + void SetDefault(const wxString &val) + { + m_hasDefault = true; + m_defValue = val; } - wxString &getValue() + wxString &Default() { - return m_value; // NOTE: non-const, caller may modifiy value + return m_defValue; } - const wxString quoteValue(); - bool isValidDefVal( const wxString defvalue ); - void setValue( const wxString &newValue ) + bool HasDefault() { - m_value = newValue; + return m_hasDefault; } + bool &UseDefault() + { + return m_useDefault; + } + + pgParam *GetParam(wxMBConv *_conv = NULL); private: - wxString m_name; - wxString m_type; - wxString m_mode; - wxString m_value; - Oid m_typeOid; + dbgArgInfo(const dbgArgInfo &_arg) + { + wxASSERT(0); + } + + dbgArgInfo &operator= (const dbgArgInfo &) + { + wxASSERT(0); + } + + wxString m_name; /* Name of the argument */ + wxString m_type; /* Type of the argument */ + wxString m_baseType; /* Base type of an array type */ + wxString m_defValue; /* Default Value */ + Oid m_typeOid; /* OID of Type */ + short m_mode; /* IN, IN OUT, OUT, or VARAIDIC */ + bool m_hasDefault; /* Has the default value? */ + bool m_useDefault; /* Use the default value? */ + bool m_null; /* Is Value NULL */ + wxString m_val; /* Value */ }; -WX_DECLARE_OBJARRAY(wsArgInfo, wsArgInfoArray); +WX_DEFINE_ARRAY_PTR(dbgArgInfo *, pgDbgArgs); //////////////////////////////////////////////////////////////////////////////// // class dbgTargetInfo @@ -82,95 +128,120 @@ WX_DECLARE_OBJARRAY(wsArgInfo, wsArgInfoArray); // // This class offers a number of (inline) member functions that you can call // to extract the above information after it's been queried from the server. - -class dbgPgConn; - +// class dbgTargetInfo { public: - dbgTargetInfo( const wxString &target, dbgPgConn *conn, char targetType ); + dbgTargetInfo(Oid _target, pgConn *_conn); - int getArgInCount() + const wxString &GetQualifiedName() { - return( m_argInCount ); + return m_fqName; } - int getArgOutCount() + + const wxString &GetLanguage() { - return( m_argOutCount ); + return m_language; } - int getArgInOutCount() + + const wxString &GetPackageName() { - return( m_argInOutCount ); + return m_package; } - int getArgCount() + const wxString &GetSchemaName() { - return( m_argInCount + m_argOutCount + m_argInOutCount ); + return m_schema; } - const wxString &getLanguage() + const wxString &GetName() { - return( m_language ); + return m_name; } - const wxString &getSchema() + + const wxString &GetReturnType() { - return( m_schema ); + return m_returnType; } - const wxString &getName() + + long GetOid() { - return( m_name ); + return m_oid; } - const wxString &getFQName() + + long GetPkgOid() { - return( m_fqName ); + return m_pkgOid; } - const wxString &getReturnType() + + long GetSchemaOid() { - return( m_returnType ); + return m_schemaOid; } - long getOid() + + long GetPkgInitOid() { - return( m_oid ); + return m_pkgInitOid; } - long getPkgOid() + + bool GetIsFunction() { - return( m_pkgOid ); + return m_isFunction; } - long getPkgInitOid() + + bool GetReturnsSet() { - return( m_pkgInitOid ); + return m_returnsSet; } - bool getIsFunction() + + bool RequireUserInput() { - return( m_isFunction ); + return ((m_pkgOid != 0 && m_pkgInitOid != 0) || m_inputParamCnt != 0); } - bool getReturnsSet() + + bool &DebugPackageConstructor() { - return( m_returnsSet ); + return m_debugPkgCon; } - wsArgInfo &operator[]( int index ); + dbgArgInfo *operator[](int index); + + pgDbgArgs *GetArgs() + { + return m_args; + } + + bool HasVariadic() + { + return m_hasVariadic; + } + + bool AddForExecution(pgQueryThread *_thread); private: - wxString m_name; // Target name (function or procedure) - wxString m_schema; // Schema in which target resides - wxString m_language; // Language in which target is defined - wxString m_argNames; // Argument names - wxString m_argModes; // Argument modes - wxString m_argTypes; // Argument types - wxString m_argTypeOids; // Argument type OIDs - wxString m_argDefVals; // Argument default values - wxString m_fqName; // Fully-qualified name (schema.package.func or package.func) - wxString m_returnType;// Return type - bool m_isFunction; // true->target is a function, false->target is a procedure - bool m_returnsSet; // Returns a set? - int m_argInCount; // Number of IN arguments - int m_argOutCount; // Number of OUT arguments - int m_argInOutCount; // Number of INOUT arguments - long m_oid; // Target function/procedure OID - long m_pkgOid; // Package in which target defined (if non-zero) - long m_pkgInitOid; // OID of the package initializer function. - wsArgInfoArray m_argInfo; + wxString m_name; // Target name (function or procedure) + wxString m_schema; // Schema in which target resides + wxString m_package; // Package in which target resides + wxString m_language; // Language in which target is defined + + wxString m_returnType; // Return type + wxString m_funcSignature; // Function Signature + + wxString m_fqName; // Function qualified name + + bool m_isFunction; // true->target is a function, false->target is a procedure + bool m_returnsSet; // Returns a set? + bool m_hasVariadic; // Has the variadic argument + + bool m_debugPkgCon; // Debug Package Constructor + + long m_oid; // Target function/procedure OID + long m_pkgOid; // Package in which target defined (if non-zero) + long m_pkgInitOid; // OID of the package initializer function. + long m_schemaOid; // OID of the schema + + size_t m_inputParamCnt; // IN/IN OUT parameter count + pgDbgArgs *m_args; // Function arguments }; #endif diff --git a/pgadmin/include/debugger/dlgDirectDbg.h b/pgadmin/include/debugger/dlgDirectDbg.h index d44ff2d..e1ff0b6 100644 --- a/pgadmin/include/debugger/dlgDirectDbg.h +++ b/pgadmin/include/debugger/dlgDirectDbg.h @@ -27,71 +27,97 @@ // //////////////////////////////////////////////////////////////////////////////// -#ifndef DBGDIRECT_H -#define DBGDIRECT_H +#ifndef DLGDIRECTDBG_H +#define DLGDIRECTDBG_H + +#include +#include +#include #include "dlg/dlgClasses.h" -#include "debugger/dbgBreakPoint.h" -#include "debugger/dbgConnProp.h" -#include "debugger/frmDebugger.h" -class dbgTargetInfo; -class dbgPgConn; -class ctlCodeWindow; +class dbgArgInfo; +class frmDebugger; +class dbgController; -class dlgDirectDbg : public pgDialog +class ctlGridCellBoolEditor : public wxGridCellBoolEditor { - DECLARE_CLASS( dlgDirectDbg ) - public: + ctlGridCellBoolEditor(dbgArgInfo *_arg = NULL); + void BeginEdit (int _row, int _col, wxGrid *_grid); + wxGridCellEditor *Clone() const; - dlgDirectDbg( frmDebugger *parent, wxWindowID id, const dbgConnProp &connProp ); - dbgBreakPointList &getBreakpointList(); - void setupParamWindow(); - bool startDebugging(); - bool GetCancelled() + dbgArgInfo *GetArg() { - return m_cancelled; - }; + return m_arg; + } private: + dbgArgInfo *m_arg; +}; + +class dlgDirectDbg; + +class dbgArgValueEvaluator : public wxThread +{ +public: + dbgArgValueEvaluator(pgConn *, dlgDirectDbg *); + + void *Entry(); + void CancelEval(); + static void NoticeHandler(void *, const char *); + +private: + pgConn *m_conn; + dlgDirectDbg *m_dlg; + + bool m_cancelled; +}; + + +class dlgDirectDbg : public pgDialog +{ + DECLARE_CLASS(dlgDirectDbg) + +public: + dlgDirectDbg(frmDebugger *_parent, dbgController *_controller, + pgConn *_conn); enum { - COL_NAME = 0, // Column 0 contains the variable name - COL_TYPE, // This column contains the variable type - COL_VALUE // This column contains the variable value + COL_NAME = 0, // Column 0 contains the variable name + COL_TYPE, // Type of column + COL_NULL, // Value Set to NULL (yes/no) + COL_EXPR, // Value is an expression (yes/no) + COL_VALUE, // Value (constant ,or an expression) + COL_USE_DEF, // Use the default value + COL_DEF_VAL // Default value for the column }; - wxString m_target; // Target name (function/procedure signature or OID) - bool m_isFunc; - const dbgConnProp &m_connProp; // Connection properties (used to connect to the server) - dbgTargetInfo *m_targetInfo; // Detailed information about the target (like argument types, name, ...) - dbgPgConn *m_conn; // The connection to the server - ctlCodeWindow *m_codeWindow; // A pointer to the debugger window that we'll create - dbgBreakPointList m_breakpoints; // List of initial breakpoints to create - frmDebugger *m_parent; - bool m_cancelled; - - bool loadTargetInfo( const wxString &target, const dbgConnProp &connProp, char targetType ); - void populateParamGrid(); - void OnOk( wxCommandEvent &event ); - void OnCancel( wxCommandEvent &event ); - void OnClose( wxCloseEvent &event ); - void OnTargetComplete( wxCommandEvent &event ); - void OnDebug( wxCommandEvent &event ); - void OnNoticeReceived( wxCommandEvent &event ); - bool activateDebugger( ); - - void saveSettings(); - void loadSettings(); - void setBreakpoint( long pkgOid, long funcOid ); - void invokeTarget(); - void invokeTargetCallable(); - void invokeTargetStatement(); +private: - DECLARE_EVENT_TABLE() + void PopulateParamGrid(); + void OnOk(wxCommandEvent &event); + void OnCancel(wxCommandEvent &event); + void OnClickGridLabel(wxGridEvent &event); + + void ResultArgsUpdated(wxCommandEvent &); + void ResultArgsUpdateError(wxCommandEvent &); + + void SaveSettings(); + void LoadSettings(); + + wxGrid *GetParamsGrid(); + bool DebugPkgConstructor(); + + dbgController *m_controller; + pgConn *m_conn; + dbgArgValueEvaluator *m_thread; + + friend class dbgArgValueEvaluator; + + DECLARE_EVENT_TABLE() }; #endif diff --git a/pgadmin/include/debugger/frmDebugger.h b/pgadmin/include/debugger/frmDebugger.h index 11c444f..0c7801a 100644 --- a/pgadmin/include/debugger/frmDebugger.h +++ b/pgadmin/include/debugger/frmDebugger.h @@ -24,11 +24,7 @@ #define FRMDEBUGGER_H #include - -#include "ctl/ctlSQLBox.h" -#include "frm/frmMain.h" -#include "debugger/dbgConnProp.h" -#include "debugger/ctlTabWindow.h" +#include // // This number MUST be incremented if changing any of the default perspectives @@ -45,113 +41,139 @@ #endif #endif -enum -{ - MENU_ID_EXECUTE = 10001, // Execute command entered by user - - MENU_ID_TOGGLE_BREAK, // Set/Unset breakpoint - MENU_ID_CLEAR_ALL_BREAK, // Clear all breakpoints - MENU_ID_CONTINUE, // Continue - MENU_ID_STEP_OVER, // Step over - MENU_ID_STEP_INTO, // Step into - MENU_ID_STOP, // Stop debugging - - MENU_ID_SPAWN_DEBUGGER, // Spawn a separate debugger process - MENU_ID_NOTICE_RECEIVED, // NOTICE received from server - WINDOW_ID_STACK, // Tree-control window - WINDOW_ID_CONSOLE, // Console window - WINDOW_ID_TABS, // Tab window - WINDOW_ID_BREAKPOINTS, // Breakpoints window - WINDOW_ID_RESULT_GRID, // Results window - WINDOW_ID_COMMAND, // Command window - SOCKET_ID_DEBUG, // Debugger Socket ID - - MENU_ID_VIEW_TOOLBAR, // View menu options - MENU_ID_VIEW_STACKPANE, - MENU_ID_VIEW_OUTPUTPANE, - MENU_ID_VIEW_DEFAULTVIEW, - - RESULT_ID_ATTACH_TO_PORT, // Debugger - attach to port completed - RESULT_ID_BREAKPOINT, // Debugger - breakpoint reached - RESULT_ID_GET_VARS, // Debugger - variable list complete - RESULT_ID_GET_STACK, // Debugger - stack trace complete - RESULT_ID_GET_BREAKPOINTS, // Debugger - breakpoint list complete - RESULT_ID_GET_SOURCE, // Debugger - source code listing complete - RESULT_ID_NEW_BREAKPOINT, // Debugger - set breakpoint complete - RESULT_ID_NEW_BREAKPOINT_WAIT, // Debugger - set breakpoint complete, wait for target progress - RESULT_ID_DEL_BREAKPOINT, // Debugger - drop breakpoint complete - RESULT_ID_DEPOSIT_VALUE, // Debugger - deposit value complete - RESULT_ID_ABORT_TARGET, // Debugger - abort target (cancel function) - RESULT_ID_ADD_BREAKPOINT, // Debugger - target info received, now set a breakpoint - RESULT_ID_LISTENER_CREATED, // Debugger - global listener created - RESULT_ID_TARGET_READY, // Debugger - target session attached - RESULT_ID_LAST_BREAKPOINT, // Debugger - last breakpoint created - - RESULT_ID_DIRECT_TARGET_COMPLETE, // DirectDebug - target function complete - - ID_DEBUG_INITIALIZER, // Debugger - debug package initializer? checkbox -}; - +// Debugger Controller +class dbgController; +class frmMain; +class ctlTabWindow; class ctlResultGrid; class ctlVarWindow; -class ctlCodeWindow; -class dlgDirectDbg; -class wxSizeReportCtrl; +class ctlSQLBox; +class dbgCachedStack; class frmDebugger : public pgFrame { - DECLARE_CLASS( frmDebugger ) + DECLARE_CLASS(frmDebugger) public: - frmDebugger(frmMain *parent, const wxString &title); + frmDebugger(frmMain *_parent, dbgController *_controller, + const wxString &_title); virtual ~frmDebugger(); - dlgDirectDbg *addDirectDbg( const dbgConnProp &connProp ); // Create a new direct-debugging window - ctlCodeWindow *addDebug( const dbgConnProp &props ); // Create a new debugger window - wxStatusBar *getStatusBar() + void SetupDebugger(); + + void SetStatusText(const wxString &_status); + + ctlTabWindow *GetTabWindow() { - return( m_statusBar ); // Returns pointer to the status bar + return m_tabWindow; + } + ctlStackWindow *GetStackWindow() + { + return m_stackWindow; + } + ctlMessageWindow *GetMessageWindow() + { + return m_tabWindow->GetMessageWindow(); } - wxMenuBar *m_menuBar; // Menu bar - ctlMenuToolbar *m_toolBar; // Frames' toolbar - wxMenu *m_viewMenu; // View menu (can be modified by wxCodeWindow) - wxMenu *m_debugMenu; // Debug menu (can be modified by wxCodeWindow) + ctlVarWindow *GetVarWindow(bool create) + { + return m_tabWindow->GetVarWindow(create); + } + ctlVarWindow *GetParamWindow(bool create) + { + return m_tabWindow->GetParamWindow(create); + } + ctlVarWindow *GetPkgVarWindow(bool create) + { + return m_tabWindow->GetPkgVarWindow(create); + } + ctlResultGrid *GetResultWindow() + { + return m_tabWindow->GetResultWindow(); + } + + void DisplaySource(dbgCachedStack &); - wxAuiManager manager; - ctlCodeWindow *m_standaloneDebugger; // Standalone debugger window - dlgDirectDbg *m_standaloneDirectDbg; // Standalone direct debugger + void EnableToolsAndMenus(bool enable = true); + void UnhilightCurrentLine(); + void HighlightLine(int _lineNo); + void ClearBreakpointMarkers(); + void MarkBreakpoint(int lineNo); + void CloseProgressBar(); + void LaunchWaitingDialog(const wxString &msg = wxEmptyString); private: - wxStatusBar *m_statusBar; // Frame's status bar - wxMenuBar *setupMenuBar( void ); - ctlMenuToolbar *setupToolBar( void ); - wxStatusBar *setupStatusBar( void ); + int GetLineNo(); + bool IsBreakpoint(int _lineNo); + void ClearAllBreakpoints(); - frmMain *m_parent; + wxMenuBar *SetupMenuBar( void ); + ctlMenuToolbar *SetupToolBar( void ); + wxStatusBar *SetupStatusBar( void ); + + enum + { + MARKER_CURRENT = 0x02, // Current line marker + MARKER_CURRENT_BG = 0x04, // Current line marker - background hilight + MARKER_BREAKPOINT = 0x01, // Breakpoint marker + }; - DECLARE_EVENT_TABLE() - void OnExecute( wxCommandEvent &event ); - void OnDebugCommand( wxCommandEvent &event ); - void OnSelectFrame( wxCommandEvent &event ); - void OnMarginClick( wxStyledTextEvent &event ); // Set/clear breakpoint on margin click - void OnPositionStc( wxStyledTextEvent &event ); - void OnVarChange( wxGridEvent &event ); - void OnClose( wxCloseEvent &event ); - void OnExit( wxCommandEvent &event ); - void OnSize( wxSizeEvent &event ); - void OnEraseBackground(wxEraseEvent &event); - void OnHelp(wxCommandEvent &event); - void OnContents(wxCommandEvent &event); - - void OnToggleToolBar(wxCommandEvent &event); - void OnToggleStackPane(wxCommandEvent &event); - void OnToggleOutputPane(wxCommandEvent &event); - void OnAuiUpdate(wxAuiManagerEvent &event); - void OnDefaultView(wxCommandEvent &event); +private: + // Menu bar + wxMenuBar *m_menuBar; + // Frames' toolbar + ctlMenuToolbar *m_toolBar; + // View menu (can be modified by wxCodeWindow) + wxMenu *m_viewMenu; + // Debug menu (can be modified by wxCodeWindow) + wxMenu *m_debugMenu; + + wxAuiManager m_manager; + // Frame's status bar + wxStatusBar *m_statusBar; + // Main Frame + frmMain *m_parent; + // Debugger Controller + dbgController *m_controller; + + // Stack Window + ctlStackWindow *m_stackWindow; + // Tab Window + ctlTabWindow *m_tabWindow; + // Function Code Viewer + ctlSQLBox *m_codeViewer; + // Timer + wxTimer m_timer; + // "Waiting for target" dialog + wxProgressDialog *m_progressBar; + + DECLARE_EVENT_TABLE() + // Idle processor + void OnTimer(wxTimerEvent &_ev); + + void OnExecute(wxCommandEvent &_ev); + void OnDebugCommand(wxCommandEvent &_ev); + void OnSelectFrame(wxCommandEvent &_ev); + // Toggle break-point on margin click + void OnMarginClick(wxStyledTextEvent &_ev); + void OnPositionStc(wxStyledTextEvent &_ev); + void OnVarChange(wxGridEvent &_ev); + void OnClose(wxCloseEvent &_ev); + void OnExit(wxCommandEvent &_ev); + void OnSize(wxSizeEvent &_ev); + void OnEraseBackground(wxEraseEvent &_ev); + void OnHelp(wxCommandEvent &_ev); + void OnContents(wxCommandEvent &_ev); + + void OnToggleToolBar(wxCommandEvent &_ev); + void OnToggleStackPane(wxCommandEvent &_ev); + void OnToggleOutputPane(wxCommandEvent &_ev); + void OnAuiUpdate(wxAuiManagerEvent &_ev); + void OnDefaultView(wxCommandEvent &_ev); }; #endif diff --git a/pgadmin/include/debugger/module.mk b/pgadmin/include/debugger/module.mk index 1ce5bce..9dc8ad6 100644 --- a/pgadmin/include/debugger/module.mk +++ b/pgadmin/include/debugger/module.mk @@ -10,19 +10,15 @@ ####################################################################### pgadmin3_SOURCES += \ - include/debugger/ctlCodeWindow.h \ + include/debugger/dbgController.h \ + include/debugger/dbgModel.h \ include/debugger/ctlMessageWindow.h \ include/debugger/ctlResultGrid.h \ include/debugger/ctlStackWindow.h \ include/debugger/ctlTabWindow.h \ include/debugger/ctlVarWindow.h \ include/debugger/dbgBreakPoint.h \ - include/debugger/dbgConnProp.h \ include/debugger/dbgConst.h \ - include/debugger/dbgDbResult.h \ - include/debugger/dbgPgConn.h \ - include/debugger/dbgPgThread.h \ - include/debugger/dbgResultset.h \ include/debugger/dbgTargetInfo.h \ include/debugger/debugger.h \ include/debugger/dlgDirectDbg.h \ diff --git a/pgadmin/include/frm/frmQuery.h b/pgadmin/include/frm/frmQuery.h index 1f35a78..a5cd06a 100644 --- a/pgadmin/include/frm/frmQuery.h +++ b/pgadmin/include/frm/frmQuery.h @@ -13,6 +13,7 @@ #define __FRM_QUERY_H #include "ctl/ctlAuiNotebook.h" +#include "db/pgQueryResultEvent.h" #include "dlg/dlgClasses.h" #include "gqb/gqbViewController.h" #include "gqb/gqbModel.h" @@ -222,7 +223,7 @@ private: void OpenLastFile(); void updateMenu(bool allowUpdateModelSize = true); void execQuery(const wxString &query, int resultToRetrieve = 0, bool singleResult = false, const int queryOffset = 0, bool toFile = false, bool explain = false, bool verbose = false); - void OnQueryComplete(wxCommandEvent &ev); + void OnQueryComplete(pgQueryResultEvent &ev); void completeQuery(bool done, bool explain, bool verbose); void OnScriptComplete(wxCommandEvent &ev); void setTools(const bool running); diff --git a/pgadmin/include/precomp.h b/pgadmin/include/precomp.h index 3040ebd..3defe6c 100644 --- a/pgadmin/include/precomp.h +++ b/pgadmin/include/precomp.h @@ -46,21 +46,18 @@ #include "db/pgConn.h" #include "db/pgQueryThread.h" +#include "db/pgQueryResultEvent.h" #include "db/pgSet.h" -#include "debugger/ctlCodeWindow.h" #include "debugger/ctlMessageWindow.h" #include "debugger/ctlResultGrid.h" #include "debugger/ctlStackWindow.h" #include "debugger/ctlTabWindow.h" #include "debugger/ctlVarWindow.h" #include "debugger/dbgBreakPoint.h" -#include "debugger/dbgConnProp.h" #include "debugger/dbgConst.h" -#include "debugger/dbgDbResult.h" -#include "debugger/dbgPgConn.h" -#include "debugger/dbgPgThread.h" -#include "debugger/dbgResultset.h" +#include "debugger/dbgController.h" +#include "debugger/dbgModel.h" #include "debugger/dbgTargetInfo.h" #include "debugger/debugger.h" #include "debugger/dlgDirectDbg.h" diff --git a/pgadmin/pgAdmin3.vcxproj b/pgadmin/pgAdmin3.vcxproj index 521b1e1..5a1800c 100644 --- a/pgadmin/pgAdmin3.vcxproj +++ b/pgadmin/pgAdmin3.vcxproj @@ -1007,17 +1007,15 @@ - - - - - + + + @@ -1689,20 +1687,17 @@ + - - - - - - + + @@ -2239,4 +2234,4 @@ - \ No newline at end of file + diff --git a/pgadmin/pgAdmin3.vcxproj.filters b/pgadmin/pgAdmin3.vcxproj.filters index 252589b..66f021b 100644 --- a/pgadmin/pgAdmin3.vcxproj.filters +++ b/pgadmin/pgAdmin3.vcxproj.filters @@ -823,7 +823,13 @@ utils - + + debugger + + + debugger + + debugger @@ -844,18 +850,6 @@ debugger - - debugger - - - debugger - - - debugger - - - debugger - debugger @@ -2702,9 +2696,6 @@ include\db - - include\debugger - include\debugger @@ -2723,22 +2714,13 @@ include\debugger - - include\debugger - include\debugger - - include\debugger - - - include\debugger - - + include\debugger - + include\debugger @@ -4307,4 +4289,4 @@ - \ No newline at end of file + diff --git a/pgadmin/ui/dlgDirectDbg.xrc b/pgadmin/ui/dlgDirectDbg.xrc index b314fd0..bc3d991 100644 --- a/pgadmin/ui/dlgDirectDbg.xrc +++ b/pgadmin/ui/dlgDirectDbg.xrc @@ -2,7 +2,7 @@ View Data Options - 300,265d + 450,210d 1 @@ -10,7 +10,7 @@ 0 - 296,240d + 400,180d 0 @@ -58,7 +58,7 @@ - + Accept the current options and close the dialogue. 1 diff --git a/pgadmin/ui/xrcDialogs.cpp b/pgadmin/ui/xrcDialogs.cpp index eb5eca0..13f9b63 100644 --- a/pgadmin/ui/xrcDialogs.cpp +++ b/pgadmin/ui/xrcDialogs.cpp @@ -3982,7 +3982,7 @@ static unsigned char xml_res_file_10[] = { 62,10,32,32,60,47,111,98,106,101,99,116,62,10,60,47,114,101,115,111,117, 114,99,101,62,10}; -static size_t xml_res_size_11 = 3331; +static size_t xml_res_size_11 = 3334; static unsigned char xml_res_file_11[] = { 60,63,120,109,108,32,118,101,114,115,105,111,110,61,34,49,46,48,34,32,101, 110,99,111,100,105,110,103,61,34,73,83,79,45,56,56,53,57,45,49,34,63,62, @@ -3991,7 +3991,7 @@ static unsigned char xml_res_file_11[] = { 101,61,34,100,108,103,68,105,114,101,99,116,68,98,103,34,62,10,32,32,32, 32,60,116,105,116,108,101,62,86,105,101,119,32,68,97,116,97,32,79,112,116, 105,111,110,115,60,47,116,105,116,108,101,62,10,32,32,32,32,60,115,105, -122,101,62,51,48,48,44,50,54,53,100,60,47,115,105,122,101,62,10,32,32,32, +122,101,62,52,53,48,44,50,49,48,100,60,47,115,105,122,101,62,10,32,32,32, 32,60,115,116,121,108,101,62,119,120,68,69,70,65,85,76,84,95,68,73,65,76, 79,71,95,83,84,89,76,69,124,119,120,67,65,80,84,73,79,78,124,119,120,83, 89,83,84,69,77,95,77,69,78,85,124,119,120,82,69,83,73,90,69,95,66,79,82, @@ -4006,8 +4006,8 @@ static unsigned char xml_res_file_11[] = { 122,101,114,105,116,101,109,34,62,10,32,32,32,32,32,32,32,32,60,111,98, 106,101,99,116,32,99,108,97,115,115,61,34,119,120,78,111,116,101,98,111, 111,107,34,32,110,97,109,101,61,34,110,98,78,111,116,101,98,111,111,107, -34,62,10,32,32,32,32,32,32,32,32,32,32,60,115,105,122,101,62,50,57,54,44, -50,52,48,100,60,47,115,105,122,101,62,10,32,32,32,32,32,32,32,32,32,32, +34,62,10,32,32,32,32,32,32,32,32,32,32,60,115,105,122,101,62,52,48,48,44, +49,56,48,100,60,47,115,105,122,101,62,10,32,32,32,32,32,32,32,32,32,32, 60,115,101,108,101,99,116,101,100,62,48,60,47,115,101,108,101,99,116,101, 100,62,10,32,32,32,32,32,32,32,32,32,32,60,111,98,106,101,99,116,32,99, 108,97,115,115,61,34,110,111,116,101,98,111,111,107,112,97,103,101,34,62, @@ -4098,47 +4098,47 @@ static unsigned char xml_res_file_11[] = { 34,62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,111,98,106,101,99,116,32, 99,108,97,115,115,61,34,119,120,66,117,116,116,111,110,34,32,110,97,109, 101,61,34,119,120,73,68,95,79,75,34,62,10,32,32,32,32,32,32,32,32,32,32, -32,32,32,32,60,108,97,98,101,108,62,38,97,109,112,59,79,75,60,47,108,97, -98,101,108,62,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,111,111, -108,116,105,112,62,65,99,99,101,112,116,32,116,104,101,32,99,117,114,114, -101,110,116,32,111,112,116,105,111,110,115,32,97,110,100,32,99,108,111, -115,101,32,116,104,101,32,100,105,97,108,111,103,117,101,46,60,47,116,111, -111,108,116,105,112,62,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60, -100,101,102,97,117,108,116,62,49,60,47,100,101,102,97,117,108,116,62,10, -32,32,32,32,32,32,32,32,32,32,32,32,60,47,111,98,106,101,99,116,62,10,32, -32,32,32,32,32,32,32,32,32,32,32,60,102,108,97,103,62,119,120,69,88,80, -65,78,68,124,119,120,65,76,76,60,47,102,108,97,103,62,10,32,32,32,32,32, -32,32,32,32,32,32,32,60,98,111,114,100,101,114,62,51,60,47,98,111,114,100, -101,114,62,10,32,32,32,32,32,32,32,32,32,32,60,47,111,98,106,101,99,116, -62,10,32,32,32,32,32,32,32,32,32,32,60,111,98,106,101,99,116,32,99,108, +32,32,32,32,60,108,97,98,101,108,62,38,97,109,112,59,68,101,98,117,103, +60,47,108,97,98,101,108,62,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32, +60,116,111,111,108,116,105,112,62,65,99,99,101,112,116,32,116,104,101,32, +99,117,114,114,101,110,116,32,111,112,116,105,111,110,115,32,97,110,100, +32,99,108,111,115,101,32,116,104,101,32,100,105,97,108,111,103,117,101, +46,60,47,116,111,111,108,116,105,112,62,10,32,32,32,32,32,32,32,32,32,32, +32,32,32,32,60,100,101,102,97,117,108,116,62,49,60,47,100,101,102,97,117, +108,116,62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,47,111,98,106,101, +99,116,62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,102,108,97,103,62,119, +120,69,88,80,65,78,68,124,119,120,65,76,76,60,47,102,108,97,103,62,10,32, +32,32,32,32,32,32,32,32,32,32,32,60,98,111,114,100,101,114,62,51,60,47, +98,111,114,100,101,114,62,10,32,32,32,32,32,32,32,32,32,32,60,47,111,98, +106,101,99,116,62,10,32,32,32,32,32,32,32,32,32,32,60,111,98,106,101,99, +116,32,99,108,97,115,115,61,34,115,105,122,101,114,105,116,101,109,34,62, +10,32,32,32,32,32,32,32,32,32,32,32,32,60,111,98,106,101,99,116,32,99,108, +97,115,115,61,34,119,120,66,117,116,116,111,110,34,32,110,97,109,101,61, +34,119,120,73,68,95,67,65,78,67,69,76,34,62,10,32,32,32,32,32,32,32,32, +32,32,32,32,32,32,60,108,97,98,101,108,62,38,97,109,112,59,67,97,110,99, +101,108,60,47,108,97,98,101,108,62,10,32,32,32,32,32,32,32,32,32,32,32, +32,32,32,60,116,111,111,108,116,105,112,62,67,97,110,99,101,108,32,97,110, +121,32,99,104,97,110,103,101,115,32,97,110,100,32,99,108,111,115,101,32, +116,104,101,32,100,105,97,108,111,103,117,101,46,60,47,116,111,111,108, +116,105,112,62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,47,111,98,106, +101,99,116,62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,102,108,97,103, +62,119,120,69,88,80,65,78,68,124,119,120,65,76,76,60,47,102,108,97,103, +62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,98,111,114,100,101,114,62, +51,60,47,98,111,114,100,101,114,62,10,32,32,32,32,32,32,32,32,32,32,60, +47,111,98,106,101,99,116,62,10,32,32,32,32,32,32,32,32,60,47,111,98,106, +101,99,116,62,10,32,32,32,32,32,32,32,32,60,102,108,97,103,62,119,120,69, +88,80,65,78,68,124,119,120,84,79,80,124,119,120,76,69,70,84,124,119,120, +82,73,71,72,84,60,47,102,108,97,103,62,10,32,32,32,32,32,32,60,47,111,98, +106,101,99,116,62,10,32,32,32,32,32,32,60,111,98,106,101,99,116,32,99,108, 97,115,115,61,34,115,105,122,101,114,105,116,101,109,34,62,10,32,32,32, -32,32,32,32,32,32,32,32,32,60,111,98,106,101,99,116,32,99,108,97,115,115, -61,34,119,120,66,117,116,116,111,110,34,32,110,97,109,101,61,34,119,120, -73,68,95,67,65,78,67,69,76,34,62,10,32,32,32,32,32,32,32,32,32,32,32,32, -32,32,60,108,97,98,101,108,62,38,97,109,112,59,67,97,110,99,101,108,60, -47,108,97,98,101,108,62,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60, -116,111,111,108,116,105,112,62,67,97,110,99,101,108,32,97,110,121,32,99, -104,97,110,103,101,115,32,97,110,100,32,99,108,111,115,101,32,116,104,101, -32,100,105,97,108,111,103,117,101,46,60,47,116,111,111,108,116,105,112, -62,10,32,32,32,32,32,32,32,32,32,32,32,32,60,47,111,98,106,101,99,116,62, -10,32,32,32,32,32,32,32,32,32,32,32,32,60,102,108,97,103,62,119,120,69, -88,80,65,78,68,124,119,120,65,76,76,60,47,102,108,97,103,62,10,32,32,32, -32,32,32,32,32,32,32,32,32,60,98,111,114,100,101,114,62,51,60,47,98,111, -114,100,101,114,62,10,32,32,32,32,32,32,32,32,32,32,60,47,111,98,106,101, -99,116,62,10,32,32,32,32,32,32,32,32,60,47,111,98,106,101,99,116,62,10, -32,32,32,32,32,32,32,32,60,102,108,97,103,62,119,120,69,88,80,65,78,68, -124,119,120,84,79,80,124,119,120,76,69,70,84,124,119,120,82,73,71,72,84, -60,47,102,108,97,103,62,10,32,32,32,32,32,32,60,47,111,98,106,101,99,116, -62,10,32,32,32,32,32,32,60,111,98,106,101,99,116,32,99,108,97,115,115,61, -34,115,105,122,101,114,105,116,101,109,34,62,10,32,32,32,32,32,32,32,32, -60,111,98,106,101,99,116,32,99,108,97,115,115,61,34,119,120,83,116,97,116, -117,115,66,97,114,34,32,110,97,109,101,61,34,117,110,107,83,116,97,116, -117,115,66,97,114,34,62,10,32,32,32,32,32,32,32,32,32,32,60,115,116,121, -108,101,62,119,120,83,84,95,83,73,90,69,71,82,73,80,60,47,115,116,121,108, -101,62,10,32,32,32,32,32,32,32,32,60,47,111,98,106,101,99,116,62,10,32, -32,32,32,32,32,60,47,111,98,106,101,99,116,62,10,32,32,32,32,60,47,111, -98,106,101,99,116,62,10,32,32,60,47,111,98,106,101,99,116,62,10,60,47,114, -101,115,111,117,114,99,101,62,10}; +32,32,32,32,32,60,111,98,106,101,99,116,32,99,108,97,115,115,61,34,119, +120,83,116,97,116,117,115,66,97,114,34,32,110,97,109,101,61,34,117,110, +107,83,116,97,116,117,115,66,97,114,34,62,10,32,32,32,32,32,32,32,32,32, +32,60,115,116,121,108,101,62,119,120,83,84,95,83,73,90,69,71,82,73,80,60, +47,115,116,121,108,101,62,10,32,32,32,32,32,32,32,32,60,47,111,98,106,101, +99,116,62,10,32,32,32,32,32,32,60,47,111,98,106,101,99,116,62,10,32,32, +32,32,60,47,111,98,106,101,99,116,62,10,32,32,60,47,111,98,106,101,99,116, +62,10,60,47,114,101,115,111,117,114,99,101,62,10}; static size_t xml_res_size_12 = 12541; static unsigned char xml_res_file_12[] = {