mirror of
https://github.com/levinsv/pgadmin3.git
synced 2026-05-15 06:05:49 -06:00
499 lines
15 KiB
C++
499 lines
15 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin III - PostgreSQL Tools
|
|
//
|
|
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
// slNode.cpp PostgreSQL Slony-I node
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// wxWindows headers
|
|
#include <wx/wx.h>
|
|
|
|
// App headers
|
|
#include "pgAdmin3.h"
|
|
#include "utils/misc.h"
|
|
#include "schema/pgObject.h"
|
|
#include "slony/slNode.h"
|
|
#include "slony/slCluster.h"
|
|
#include "slony/slPath.h"
|
|
#include "slony/slListen.h"
|
|
#include "slony/slSet.h"
|
|
#include "frm/frmMain.h"
|
|
|
|
|
|
slNode::slNode(slCluster *cl, const wxString &newName)
|
|
: slObject(cl, nodeFactory, newName)
|
|
{
|
|
pid = -1;
|
|
}
|
|
|
|
int slNode::GetIconId()
|
|
{
|
|
if (!GetActive())
|
|
return nodeFactory.GetDisabledIconId();
|
|
else if (GetSlId() == GetCluster()->GetLocalNodeID())
|
|
return nodeFactory.GetLocalIconId();
|
|
else
|
|
return nodeFactory.GetIconId();
|
|
}
|
|
|
|
|
|
wxMenu *slNode::GetNewMenu()
|
|
{
|
|
wxMenu *menu = pgObject::GetNewMenu();
|
|
|
|
// if (GetCreatePrivilege())
|
|
pathFactory.AppendMenu(menu);
|
|
listenFactory.AppendMenu(menu);
|
|
|
|
return menu;
|
|
}
|
|
|
|
|
|
bool slNode::CanDrop()
|
|
{
|
|
return (GetSlId() != GetCluster()->GetLocalNodeID());
|
|
}
|
|
|
|
|
|
bool slNode::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded)
|
|
{
|
|
return GetDatabase()->ExecuteVoid(
|
|
wxT("SELECT ") + GetCluster()->GetSchemaPrefix()
|
|
+ wxT("dropnode(") + NumToStr(GetSlId()) + wxT(");\n"));
|
|
}
|
|
|
|
|
|
wxString slNode::GetTranslatedMessage(int kindOfMessage) const
|
|
{
|
|
wxString message = wxEmptyString;
|
|
|
|
switch (kindOfMessage)
|
|
{
|
|
case RETRIEVINGDETAILS:
|
|
message = _("Retrieving details on Slony node");
|
|
message += wxT(" ") + GetName();
|
|
break;
|
|
case REFRESHINGDETAILS:
|
|
message = _("Refreshing Slony node");
|
|
message += wxT(" ") + GetName();
|
|
break;
|
|
case DROPINCLUDINGDEPS:
|
|
message = wxString::Format(_("Are you sure you wish to drop Slony node \"%s\" including all objects that depend on it?"),
|
|
GetFullIdentifier().c_str());
|
|
break;
|
|
case DROPEXCLUDINGDEPS:
|
|
message = wxString::Format(_("Are you sure you wish to drop Slony node \"%s\"?"),
|
|
GetFullIdentifier().c_str());
|
|
break;
|
|
case DROPCASCADETITLE:
|
|
message = _("Drop Slony node cascaded?");
|
|
break;
|
|
case DROPTITLE:
|
|
message = _("Drop Slony node?");
|
|
break;
|
|
case PROPERTIESREPORT:
|
|
message = _("Slony node properties report");
|
|
message += wxT(" - ") + GetName();
|
|
break;
|
|
case PROPERTIES:
|
|
message = _("Slony node properties");
|
|
break;
|
|
case DDLREPORT:
|
|
message = _("Slony node DDL report");
|
|
message += wxT(" - ") + GetName();
|
|
break;
|
|
case DDL:
|
|
message = _("Slony node DDL");
|
|
break;
|
|
case DEPENDENCIESREPORT:
|
|
message = _("Slony node dependencies report");
|
|
message += wxT(" - ") + GetName();
|
|
break;
|
|
case DEPENDENCIES:
|
|
message = _("Slony node dependencies");
|
|
break;
|
|
case DEPENDENTSREPORT:
|
|
message = _("Slony node dependents report");
|
|
message += wxT(" - ") + GetName();
|
|
break;
|
|
case DEPENDENTS:
|
|
message = _("Slony node dependents");
|
|
break;
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
bool slNode::WaitForEvent(long evNode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
wxString slNode::GetSql(ctlTree *browser)
|
|
{
|
|
if (sql.IsNull())
|
|
{
|
|
sql = wxT("-- Create replication node ") + GetName() + wxT(".\n\n")
|
|
wxT("SELECT ") + GetCluster()->GetSchemaPrefix() + wxT("storenode(")
|
|
+ NumToStr(GetSlId()) + wxT(", ")
|
|
+ qtDbString(GetComment());
|
|
|
|
if (GetCluster()->ClusterMinimumVersion(1, 1) && !GetCluster()->ClusterMinimumVersion(2, 0))
|
|
sql += wxT(", ") + BoolToStr(GetSpool());
|
|
sql += wxT(");\n");
|
|
}
|
|
return sql;
|
|
}
|
|
|
|
|
|
long slNode::GetOutstandingAcks()
|
|
{
|
|
long l = StrToLong(GetDatabase()->ExecuteScalar(
|
|
wxT("SELECT SUM(st_lag_num_events) AS sumlagevents\n")
|
|
wxT(" FROM ") + GetCluster()->GetSchemaPrefix() + wxT("sl_status\n")
|
|
wxT(" WHERE st_origin = ") + NumToStr(GetCluster()->GetLocalNodeID()) + wxT("\n")
|
|
wxT(" GROUP BY st_last_event")));
|
|
|
|
return l;
|
|
}
|
|
|
|
|
|
bool slNode::CheckAcksAndContinue(wxFrame *frame)
|
|
{
|
|
long l = GetOutstandingAcks();
|
|
if (!l)
|
|
return true;
|
|
|
|
wxMessageDialog dlg(frame, wxString::Format(wxPLURAL("There are %ld event acknowledgement outstanding.\nContinue anyway?",
|
|
"There are %ld event acknowledgements outstanding.\nContinue anyway?", l), l),
|
|
_("Events pending"), wxYES_NO | wxNO_DEFAULT);
|
|
|
|
return dlg.ShowModal() == wxID_YES;
|
|
}
|
|
|
|
|
|
void slNode::ShowStatistics(frmMain *form, ctlListView *statistics)
|
|
{
|
|
CreateListColumns(statistics, _("Statistic"), _("Value"));
|
|
|
|
if (GetActive())
|
|
{
|
|
if (GetCluster()->GetLocalNodeID() == GetSlId())
|
|
{
|
|
pgSet *stats = GetDatabase()->ExecuteSet(
|
|
wxT("SELECT st_last_event,\n")
|
|
wxT(" MAX(st_last_received_ts - st_last_received_event_ts) AS roundtrip,\n")
|
|
wxT(" SUM(st_lag_num_events) AS sumlagevents, st_last_event - MAX(st_lag_num_events) as oldestlagevent,\n")
|
|
wxT(" MAX(st_last_event_ts - st_last_received_ts) AS maxeventlag\n")
|
|
wxT(" FROM ") + GetCluster()->GetSchemaPrefix() + wxT("sl_status\n")
|
|
wxT(" WHERE st_origin = ") + NumToStr(GetCluster()->GetLocalNodeID()) + wxT("\n")
|
|
wxT(" GROUP BY st_last_event"));
|
|
|
|
if (stats)
|
|
{
|
|
wxString roundtrip = stats->GetVal(wxT("roundtrip"));
|
|
long lags = stats->GetLong(wxT("sumlagevents"));
|
|
if (roundtrip.Left(6) == wxT("00:00:"))
|
|
roundtrip = NumToStr(StrToLong(roundtrip.Mid(6))) + roundtrip.Mid(8) + wxT(" s");
|
|
else if (roundtrip.Left(3) == wxT("00:"))
|
|
roundtrip = NumToStr(StrToLong(roundtrip.Mid(3))) + roundtrip.Mid(5);
|
|
|
|
statistics->AppendItem(_("Last event"), stats->GetLong(wxT("st_last_event")));
|
|
statistics->AppendItem(_("Max response time"), roundtrip);
|
|
statistics->AppendItem(_("Acks outstanding"), lags);
|
|
|
|
|
|
if (lags > 0)
|
|
{
|
|
long lagEvent = stats->GetLong(wxT("oldestlagevent"));
|
|
statistics->AppendItem(_("Oldest outstanding"), lagEvent);
|
|
statistics->AppendItem(_("Outstanding for"), stats->GetVal(wxT("maxeventlag")));
|
|
}
|
|
|
|
delete stats;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pgSet *stats = GetDatabase()->ExecuteSet(
|
|
wxT("SELECT st_last_event, st_last_event_ts, st_last_received, st_last_received_ts,\n")
|
|
wxT(" st_last_received_ts - st_last_received_event_ts AS roundtrip,\n")
|
|
wxT(" st_last_event_ts - st_last_received_ts AS eventlag,")
|
|
wxT(" ev_seqno, ev_type || ' ' || COALESCE(ev_data1, '') AS hanging\n")
|
|
wxT(" FROM ") + GetCluster()->GetSchemaPrefix() + wxT("sl_status\n")
|
|
wxT(" LEFT JOIN ") + GetCluster()->GetSchemaPrefix() + wxT("sl_event ON ev_origin=st_origin AND ev_seqno=\n")
|
|
wxT(" (SELECT MIN(ev_seqno) FROM ") + GetCluster()->GetSchemaPrefix() + wxT("sl_event WHERE ev_seqno > st_last_received)\n")
|
|
wxT(" WHERE st_origin = ") + NumToStr(GetCluster()->GetLocalNodeID()) + wxT("\n")
|
|
wxT(" AND st_received = ") + NumToStr(GetSlId()));
|
|
|
|
if (stats)
|
|
{
|
|
long evno = stats->GetLong(wxT("st_last_event"));
|
|
long ack = stats->GetLong(wxT("st_last_received"));
|
|
wxString roundtrip = stats->GetVal(wxT("roundtrip"));
|
|
if (roundtrip.Left(6) == wxT("00:00:"))
|
|
roundtrip = NumToStr(StrToLong(roundtrip.Mid(6))) + roundtrip.Mid(8) + wxT(" s");
|
|
else if (roundtrip.Left(3) == wxT("00:"))
|
|
roundtrip = NumToStr(StrToLong(roundtrip.Mid(3))) + roundtrip.Mid(5);
|
|
|
|
statistics->AppendItem(_("Last event"), evno);
|
|
statistics->AppendItem(_("Last event timestamp"), stats->GetDateTime(wxT("st_last_event_ts")));
|
|
statistics->AppendItem(_("Last acknowledged"), ack);
|
|
statistics->AppendItem(_("Last ack timestamp"), stats->GetDateTime(wxT("st_last_received_ts")));
|
|
statistics->AppendItem(_("Last response time"), roundtrip);
|
|
|
|
if (evno > ack)
|
|
{
|
|
statistics->AppendItem(_("Outstanding acks"), evno - ack);
|
|
statistics->AppendItem(_("No ack for"), stats->GetVal(wxT("eventlag")));
|
|
statistics->AppendItem(_("Hanging event"), stats->GetVal(wxT("ev_seqno")));
|
|
statistics->AppendItem(_("Command"), stats->GetVal(wxT("hanging")));
|
|
}
|
|
|
|
delete stats;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
statistics->AppendItem(_("not active"), wxEmptyString);
|
|
}
|
|
|
|
|
|
void slNode::ShowTreeDetail(ctlTree *browser, frmMain *form, ctlListView *properties, ctlSQLBox *sqlPane)
|
|
{
|
|
wxString pidcol = wxEmptyString;
|
|
pgConn *conn = GetCluster()->GetNodeConn(form, GetSlId(), pid < 0);
|
|
|
|
if (!expandedKids)
|
|
{
|
|
expandedKids = true;
|
|
|
|
browser->RemoveDummyChild(this);
|
|
// Log
|
|
wxLogInfo(wxT("Adding child object to node %s"), GetIdentifier().c_str());
|
|
|
|
browser->AppendCollection(this, pathFactory);
|
|
browser->AppendCollection(this, listenFactory);
|
|
}
|
|
|
|
|
|
if (properties)
|
|
{
|
|
CreateListColumns(properties);
|
|
|
|
properties->AppendItem(_("Name"), GetName());
|
|
properties->AppendItem(_("ID"), GetSlId());
|
|
if (GetCluster()->GetLocalNodeID() == GetSlId())
|
|
properties->AppendYesNoItem(_("Local node"), true);
|
|
properties->AppendYesNoItem(_("Active"), GetActive());
|
|
if (GetCluster()->ClusterMinimumVersion(1, 1))
|
|
properties->AppendYesNoItem(_("Log spooler"), GetSpool());
|
|
properties->AppendYesNoItem(_("Connected"), conn != NULL);
|
|
properties->AppendItem(_("Comment"), firstLineOnly(GetComment()));
|
|
|
|
if (conn && pid < 0)
|
|
{
|
|
if (conn->BackendMinimumVersion(9, 0))
|
|
{
|
|
if (conn->BackendMinimumVersion(9, 2))
|
|
pidcol = wxT("pid");
|
|
else
|
|
pidcol = wxT("procpid");
|
|
|
|
pid = StrToLong(conn->ExecuteScalar(
|
|
wxT("SELECT nl_backendpid FROM ") + qtIdent(wxT("_") + GetCluster()->GetName()) + wxT(".sl_nodelock nl, ")
|
|
wxT("pg_stat_activity sa WHERE nl.nl_backendpid = sa.") + pidcol + wxT(" AND nl_nodeid = ")
|
|
+ NumToStr(GetCluster()->GetLocalNodeID())));
|
|
}
|
|
else
|
|
{
|
|
pid = StrToLong(conn->ExecuteScalar(
|
|
wxT("SELECT listenerpid FROM pg_listener WHERE relname=") + qtDbString(wxT("_") + GetCluster()->GetName() + wxT("_Restart"))));
|
|
}
|
|
}
|
|
|
|
if (conn)
|
|
{
|
|
if (GetPid())
|
|
properties->AppendItem(_("Running PID"), GetPid());
|
|
else
|
|
properties->AppendItem(_("Running PID"), _("not running"));
|
|
}
|
|
else
|
|
{
|
|
if (GetSlId() != GetCluster()->GetAdminNodeID())
|
|
properties->AppendItem(_("Running PID"), _("administrative node"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
pgObject *slNode::Refresh(ctlTree *browser, const wxTreeItemId item)
|
|
{
|
|
pgObject *node = 0;
|
|
pgCollection *coll = browser->GetParentCollection(item);
|
|
if (coll)
|
|
node = nodeFactory.CreateObjects(coll, 0, wxT(" WHERE no_id=") + NumToStr(GetSlId()) + wxT("\n"));
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
|
|
pgObject *slNodeFactory::CreateObjects(pgCollection *coll, ctlTree *browser, const wxString &restriction)
|
|
{
|
|
slObjCollection *collection = (slObjCollection *)coll;
|
|
slNode *node = 0;
|
|
|
|
pgSet *nodes = collection->GetDatabase()->ExecuteSet(
|
|
wxT("SELECT * FROM ") + collection->GetCluster()->GetSchemaPrefix() + wxT("sl_node\n")
|
|
+ restriction +
|
|
wxT(" ORDER BY no_id"));
|
|
|
|
if (nodes)
|
|
{
|
|
while (!nodes->Eof())
|
|
{
|
|
node = new slNode(collection->GetCluster(), nodes->GetVal(wxT("no_comment")).BeforeFirst('\n'));
|
|
node->iSetSlId(nodes->GetLong(wxT("no_id")));
|
|
node->iSetActive(nodes->GetBool(wxT("no_active")));
|
|
node->iSetComment(nodes->GetVal(wxT("no_comment")));
|
|
|
|
if (collection->GetCluster()->ClusterMinimumVersion(1, 1))
|
|
{
|
|
if (nodes->HasColumn(wxT("no_spool")))
|
|
node->iSetSpool(nodes->GetBool(wxT("no_spool")));
|
|
}
|
|
|
|
if (browser)
|
|
{
|
|
browser->AppendObject(collection, node);
|
|
nodes->MoveNext();
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
delete nodes;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
|
|
wxString slNodeCollection::GetTranslatedMessage(int kindOfMessage) const
|
|
{
|
|
wxString message = wxEmptyString;
|
|
|
|
switch (kindOfMessage)
|
|
{
|
|
case RETRIEVINGDETAILS:
|
|
message = _("Retrieving details on Slony nodes");
|
|
break;
|
|
case REFRESHINGDETAILS:
|
|
message = _("Refreshing Slony nodes");
|
|
break;
|
|
case OBJECTSLISTREPORT:
|
|
message = _("Slony nodes list report");
|
|
break;
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
|
|
void slNodeCollection::ShowStatistics(frmMain *form, ctlListView *statistics)
|
|
{
|
|
wxLogInfo(wxT("Displaying statistics for nodes on Cluster %s"), GetCluster()->GetIdentifier().c_str());
|
|
|
|
// Add the statistics view columns
|
|
statistics->ClearAll();
|
|
statistics->AddColumn(_("Node"), 50);
|
|
statistics->AddColumn(_("Roundtrip"), 50);
|
|
statistics->AddColumn(_("Acks outstanding"), 50);
|
|
statistics->AddColumn(_("Outstanding time"), 50);
|
|
statistics->AddColumn(_("Event No"), 50);
|
|
statistics->AddColumn(_("Command"), 250);
|
|
|
|
pgSet *stats = GetDatabase()->ExecuteSet(
|
|
wxT("SELECT st_received, st_last_event, st_lag_num_events, st_last_event_ts, st_last_received, st_last_received_ts,\n")
|
|
wxT(" st_last_received_ts - st_last_received_event_ts AS roundtrip,\n")
|
|
wxT(" CASE WHEN st_lag_num_events > 0 THEN st_last_event_ts - st_last_received_ts END AS eventlag,")
|
|
wxT(" ev_seqno, ev_type || ' ' || COALESCE(ev_data1, '') AS hanging\n")
|
|
wxT(" FROM ") + GetCluster()->GetSchemaPrefix() + wxT("sl_status\n")
|
|
wxT(" LEFT JOIN ") + GetCluster()->GetSchemaPrefix() + wxT("sl_event ON ev_origin=st_origin AND ev_seqno=\n")
|
|
wxT(" (SELECT MIN(ev_seqno) FROM ") + GetCluster()->GetSchemaPrefix() + wxT("sl_event WHERE ev_seqno > st_last_received)\n")
|
|
wxT(" WHERE st_origin = ") + NumToStr(GetCluster()->GetLocalNodeID()));
|
|
|
|
if (stats)
|
|
{
|
|
long pos = 0;
|
|
while (!stats->Eof())
|
|
{
|
|
long lagEvents = stats->GetLong(wxT("st_lag_num_events"));
|
|
statistics->InsertItem(pos, NumToStr(stats->GetLong(wxT("st_received"))), nodeFactory.GetIconId());
|
|
statistics->SetItem(pos, 1, stats->GetVal(wxT("roundtrip")));
|
|
statistics->SetItem(pos, 2, NumToStr(lagEvents));
|
|
statistics->SetItem(pos, 3, stats->GetVal(wxT("eventlag")));
|
|
if (lagEvents)
|
|
{
|
|
statistics->SetItem(pos, 4, stats->GetVal(wxT("ev_seqno")));
|
|
statistics->SetItem(pos, 5, stats->GetVal(wxT("hanging")));
|
|
}
|
|
stats->MoveNext();
|
|
pos++;
|
|
}
|
|
|
|
delete stats;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////
|
|
|
|
#include "images/slnode.pngc"
|
|
#include "images/slnode-local.pngc"
|
|
#include "images/slnode-disabled.pngc"
|
|
#include "images/slnodes.pngc"
|
|
|
|
slNodeFactory::slNodeFactory()
|
|
: slObjFactory(__("Node"), __("New Node"), __("Create a new Node."), slnode_png_img)
|
|
{
|
|
localIconId = addIcon(slnode_local_png_img);
|
|
disabledIconId = addIcon(slnode_disabled_png_img);
|
|
metaType = SLM_NODE;
|
|
}
|
|
|
|
pgCollection *slNodeFactory::CreateCollection(pgObject *obj)
|
|
{
|
|
return new slNodeCollection(GetCollectionFactory(), (slCluster *)obj);
|
|
}
|
|
|
|
|
|
slNodeObject::slNodeObject(slNode *n, pgaFactory &factory, const wxString &newName)
|
|
: slObject(n->GetCluster(), factory, newName)
|
|
{
|
|
node = n;
|
|
}
|
|
|
|
slNodeObjCollection::slNodeObjCollection(pgaFactory *factory, slNode *n)
|
|
: slObjCollection(factory, n->GetCluster())
|
|
{
|
|
node = n;
|
|
iSetSlId(n->GetSlId());
|
|
}
|
|
|
|
|
|
pgCollection *slNodeObjFactory::CreateCollection(pgObject *obj)
|
|
{
|
|
return new slNodeObjCollection(GetCollectionFactory(), (slNode *)obj);
|
|
}
|
|
|
|
|
|
slNodeFactory nodeFactory;
|
|
static pgaCollectionFactory cf(&nodeFactory, __("Nodes"), slnodes_png_img);
|