////////////////////////////////////////////////////////////////////////// // // pgAdmin III - PostgreSQL Tools // // Copyright (C) 2002 - 2016, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // pgDatabase.cpp - PostgreSQL Database // ////////////////////////////////////////////////////////////////////////// // wxWindows headers #include // App headers #include "pgAdmin3.h" #include "utils/misc.h" #include "utils/pgfeatures.h" #include "frm/frmMain.h" #include "schema/edbSynonym.h" #include "schema/pgCast.h" #include "schema/pgExtension.h" #include "schema/pgPublication.h" #include "schema/pgSubscription.h" #include "schema/pgForeignDataWrapper.h" #include "schema/pgLanguage.h" #include "schema/pgSchema.h" #include "schema/pgEventTrigger.h" #include "slony/slCluster.h" #include "frm/frmHint.h" #include "frm/frmReport.h" #include "pro_scheduler/pgproJob.h" pgDatabase::pgDatabase(const wxString &newName) : pgServerObject(databaseFactory, newName) { useServerConnection = true; allowConnections = true; connected = false; conn = NULL; missingFKs = 0; canDebugPlpgsql = 0; canDebugEdbspl = 0; } pgDatabase::~pgDatabase() { Disconnect(); } wxString pgDatabase::GetTranslatedMessage(int kindOfMessage) const { wxString message = wxEmptyString; switch (kindOfMessage) { case RETRIEVINGDETAILS: message = _("Retrieving details on database"); message += wxT(" ") + GetName(); break; case REFRESHINGDETAILS: message = _("Refreshing database"); message += wxT(" ") + GetName(); break; case DROPINCLUDINGDEPS: message = wxString::Format(_("Are you sure you wish to drop database \"%s\" including all objects that depend on it?"), GetFullIdentifier().c_str()); break; case DROPEXCLUDINGDEPS: message = wxString::Format(_("Are you sure you wish to drop database \"%s\"?"), GetFullIdentifier().c_str()); break; case DROPCASCADETITLE: message = _("Drop database cascaded?"); break; case DROPTITLE: message = _("Drop database?"); break; case PROPERTIESREPORT: message = _("Database properties report"); message += wxT(" - ") + GetName(); break; case PROPERTIES: message = _("Database properties"); break; case DDLREPORT: message = _("Database DDL report"); message += wxT(" - ") + GetName(); break; case DDL: message = _("Database DDL"); break; case STATISTICSREPORT: message = _("Database statistics report"); message += wxT(" - ") + GetName(); break; case OBJSTATISTICS: message = _("Database statistics"); break; case DEPENDENCIESREPORT: message = _("Database dependencies report"); message += wxT(" - ") + GetName(); break; case DEPENDENCIES: message = _("Database dependencies"); break; case DEPENDENTSREPORT: message = _("Database dependents report"); message += wxT(" - ") + GetName(); break; case DEPENDENTS: message = _("Database dependents"); break; case BACKUPTITLE: message = wxString::Format(_("Backup database \"%s\""), GetFullIdentifier().c_str()); break; case RESTORETITLE: message = wxString::Format(_("Restore database \"%s\""), GetFullIdentifier().c_str()); break; } return message; } int pgDatabase::GetIconId() { if (GetConnected()) return databaseFactory.GetIconId(); else return databaseFactory.GetClosedIconId(); } wxMenu *pgDatabase::GetNewMenu() { wxMenu *menu = pgObject::GetNewMenu(); if (GetConnection() && GetCreatePrivilege()) { if (settings->GetDisplayOption(_("Casts"))) castFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Extensions")) && GetConnection()->BackendMinimumVersion(9, 1)) extensionFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Publications")) && GetConnection()->BackendMinimumVersion(10, 0)) publicationFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Subscriptions")) && GetConnection()->BackendMinimumVersion(10, 0)) subscriptionFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Foreign Data Wrappers")) && GetConnection()->BackendMinimumVersion(8, 4)) foreignDataWrapperFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Languages"))) languageFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Synonyms")) && GetConnection()->EdbMinimumVersion(8, 0)) if (!GetConnection()->BackendMinimumVersion(8, 4)) synonymFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Schemas"))) schemaFactory.AppendMenu(menu); if (settings->GetDisplayOption(_("Slony"))) slClusterFactory.AppendMenu(menu); } return menu; } int pgDatabase::Connect() { if (!allowConnections) return PGCONN_REFUSED; if (!connected) { if (GetName() == server->GetDatabaseName() && server->connection()->GetStatus() == PGCONN_OK) { useServerConnection = true; conn = 0; } else { useServerConnection = false; wxString applicationname = appearanceFactory->GetLongAppName() + wxT(" - Browser"); conn = CreateConn(applicationname); if (!conn) { connected = false; return PGCONN_BAD; } } // Now we're connected. // check for extended ruleutils with pretty-print option wxString exprname = connection()->ExecuteScalar(wxT("SELECT proname FROM pg_proc WHERE proname='pg_get_viewdef' AND proargtypes[1]=16")); if (!exprname.IsEmpty()) prettyOption = wxT(", true"); UpdateDefaultSchema(); if (connection()->BackendMinimumVersion(9, 0)) { m_defPrivsOnTables = connection()->ExecuteScalar(wxT("SELECT array_agg(array_to_string(defaclacl,',')) FROM pg_catalog.pg_default_acl dacl WHERE dacl.defaclnamespace = 0::OID AND defaclobjtype='r'")); m_defPrivsOnSeqs = connection()->ExecuteScalar(wxT("SELECT array_agg(array_to_string(defaclacl,',')) FROM pg_catalog.pg_default_acl dacl WHERE dacl.defaclnamespace = 0::OID AND defaclobjtype='S'")); m_defPrivsOnFuncs = connection()->ExecuteScalar(wxT("SELECT array_agg(array_to_string(defaclacl,',')) FROM pg_catalog.pg_default_acl dacl WHERE dacl.defaclnamespace = 0::OID AND defaclobjtype='f'")); } if (connection()->BackendMinimumVersion(9, 2)) { m_defPrivsOnTypes = connection()->ExecuteScalar(wxT("SELECT array_agg(array_to_string(defaclacl,',')) FROM pg_catalog.pg_default_acl dacl WHERE dacl.defaclnamespace = 0::OID AND defaclobjtype='T'")); } connected = true; } return connection()->GetStatus(); } pgConn *pgDatabase::connection() { if (useServerConnection) return server->connection(); return conn; } void pgDatabase::CheckAlive() { if (connected) connected = connection()->IsAlive(); } void pgDatabase::Disconnect() { connected = false; if (conn) delete conn; conn = 0; } bool pgDatabase::GetCanHint() { if (encoding == wxT("SQL_ASCII")) return true; if (encoding == wxT("UNICODE")) { wxString ver = GetServer()->GetVersionString(); if (ver.Find(wxT("mingw32")) > 0 && ver.Find(wxT("SQL 8.0.")) > 0) return true; } if (GetServer()->GetConnection() == GetConnection() && GetConnection()->BackendMinimumVersion(8, 0) && !GetConnection()->HasFeature(FEATURE_FILEREAD)) return true; return false; } void pgDatabase::ShowHint(frmMain *form, bool force) { wxArrayString hints; int rc = -1; if (encoding == wxT("SQL_ASCII")) hints.Add(HINT_ENCODING_ASCII); else if (encoding == wxT("UNICODE")) { wxString ver = GetServer()->GetVersionString(); if (ver.Find(wxT("mingw32")) > 0 && ver.Find(wxT("SQL 8.0.")) > 0) hints.Add(HINT_ENCODING_UNICODE); } if (GetServer()->GetConnection() == GetConnection() && GetConnection()->BackendMinimumVersion(8, 0) && !GetConnection()->HasFeature(FEATURE_FILEREAD)) { // if the server release is 9.1 or more and the server has no adminpack if (GetConnection()->BackendMinimumVersion(9, 1)) { // Search the adminpack extension pgSet *set = GetConnection()->ExecuteSet(wxT("SELECT 1 FROM pg_available_extensions WHERE name='adminpack'")); if (set->NumRows() == 1) hints.Add(HINT_INSTRUMENTATION_91_WITH); else hints.Add(HINT_INSTRUMENTATION_91_WITHOUT); delete set; } else hints.Add(HINT_INSTRUMENTATION); } if (force || !hintShown) { rc = frmHint::ShowHint(form, hints, GetFullIdentifier(), force); if (rc == HINT_RC_FIX) GetConnection()->ExecuteVoid(wxT("CREATE EXTENSION adminpack"), true); } hintShown = true; } void pgDatabase::ShowStatistics(frmMain *form, ctlListView *statistics) { if (GetConnection()) { bool hasSize = connection()->HasFeature(FEATURE_SIZE); wxString sql = wxT("SELECT numbackends AS ") + qtIdent(_("Backends")) + wxT(", xact_commit AS ") + qtIdent(_("Xact Committed")) + wxT(", xact_rollback AS ") + qtIdent(_("Xact Rolled Back")) + wxT(", blks_read AS ") + qtIdent(_("Blocks Read")) + wxT(", blks_hit AS ") + qtIdent(_("Blocks Hit")); if (connection()->BackendMinimumVersion(8, 3)) sql += wxT(", tup_returned AS ") + qtIdent(_("Tuples Returned")) + wxT(", tup_fetched AS ") + qtIdent(_("Tuples Fetched")) + wxT(", tup_inserted AS ") + qtIdent(_("Tuples Inserted")) + wxT(", tup_updated AS ") + qtIdent(_("Tuples Updated")) + wxT(", tup_deleted AS ") + qtIdent(_("Tuples Deleted")); if (connection()->BackendMinimumVersion(9, 1)) sql += wxT(", stats_reset AS ") + qtIdent(_("Last statistics reset")) + wxT(", slave.confl_tablespace AS ") + qtIdent(_("Tablespace conflicts")) + wxT(", slave.confl_lock AS ") + qtIdent(_("Lock conflicts")) + wxT(", slave.confl_snapshot AS ") + qtIdent(_("Snapshot conflicts")) + wxT(", slave.confl_bufferpin AS ") + qtIdent(_("Bufferpin conflicts")) + wxT(", slave.confl_deadlock AS ") + qtIdent(_("Deadlock conflicts")); if (connection()->BackendMinimumVersion(9, 2)) sql += wxT(", temp_files AS ") + qtIdent(_("Temporary files")) + wxT(", pg_size_pretty(temp_bytes) AS ") + qtIdent(_("Size of temporary files")) + wxT(", deadlocks AS ") + qtIdent(_("Deadlocks")) + wxT(", blk_read_time AS ") + qtIdent(_("Block read time")) + wxT(", blk_write_time AS ") + qtIdent(_("Block write time")); if (hasSize) sql += wxT(", pg_size_pretty(pg_database_size(db.datid)) AS ") + qtIdent(_("Size")); sql += wxT("\n FROM pg_stat_database db"); if (connection()->BackendMinimumVersion(9, 1)) sql += wxT(" JOIN pg_stat_database_conflicts slave ON db.datid=slave.datid"); sql += wxT(" WHERE db.datname=") + qtDbString(GetName()); // DisplayStatistics is not available for this object CreateListColumns(statistics, _("Statistic"), _("Value")); pgSet *stats = connection()->ExecuteSet(sql); if (stats) { int col; for (col = 0 ; col < stats->NumCols() ; col++) { if (!stats->ColName(col).IsEmpty()) statistics->AppendItem(stats->ColName(col), stats->GetVal(col)); } delete stats; } } } pgSet *pgDatabase::ExecuteSet(const wxString &sql) { pgSet *set = 0; if (connection()) { set = connection()->ExecuteSet(sql); if (!set) CheckAlive(); } return set; } wxString pgDatabase::ExecuteScalar(const wxString &sql) { wxString str; if (connection()) { str = connection()->ExecuteScalar(sql); if (str.IsEmpty() && connection()->GetLastResultStatus() != PGRES_TUPLES_OK) CheckAlive(); } return str; } bool pgDatabase::ExecuteVoid(const wxString &sql, bool reportError) { bool rc = 0; if (connection()) { rc = connection()->ExecuteVoid(sql, reportError); if (!rc) CheckAlive(); } return rc; } void pgDatabase::UpdateDefaultSchema() { searchPath = connection()->ExecuteScalar(wxT("SHOW search_path")); if (!searchPath.IsEmpty()) { wxStringTokenizer tk(searchPath, wxT(",")); pgSet *set = ExecuteSet(wxT("SELECT nspname, session_user=nspname AS isuser FROM pg_namespace")); if (set) { while (tk.HasMoreTokens()) { wxString str = tk.GetNextToken(); str.Strip(wxString::both); if (str.IsEmpty()) continue; long row; for (row = 1 ; row <= set->NumRows() ; row++) { set->Locate(row); defaultSchema = set->GetVal(wxT("nspname")); if (str == defaultSchema || ((str == wxT("$user") || str == wxT("\"$user\"")) && set->GetBool(wxT("isuser")))) { delete set; return; } } } delete set; } } defaultSchema = wxEmptyString; } wxString pgDatabase::GetSchemaPrefix(const wxString &name) const { if (name.IsEmpty()) return name; if (name == wxT("pg_catalog") || name == defaultSchema) return wxEmptyString; return name + wxT("."); } wxString pgDatabase::GetQuotedSchemaPrefix(const wxString &name) const { wxString str = GetSchemaPrefix(name); if (!str.IsEmpty()) return qtIdent(str.Left(str.Length() - 1)) + wxT("."); return str; } bool pgDatabase::GetSystemObject() const { if (server) { if (this->GetName() == wxT("template0")) return true; return (this->GetOid() <= server->GetLastSystemOID()); } else { return false; } } wxArrayString pgDatabase::GetSlonyClusters(ctlTree *browser) { wxArrayString clusters; pgCollection *collection = browser->FindCollection(slClusterFactory, GetId()); if (collection) { treeObjectIterator clusterIterator(browser, collection); slCluster *cluster; while ((cluster = (slCluster *)clusterIterator.GetNextObject()) != 0) clusters.Add(cluster->GetName()); } return clusters; } void pgDatabase::AppendSchemaChange(const wxString &sql) { wxDateTime dt; dt.Now(); schemaChanges.Append(wxT("-- ") + DateToStr(dt) + wxT("\n")); schemaChanges.Append(sql); schemaChanges.Append(wxT("\n\n")); } bool pgDatabase::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded) { if (useServerConnection) { wxMessageDialog(frame, _("Maintenance database can't be dropped."), _("Dropping database not allowed"), wxICON_EXCLAMATION | wxOK).ShowModal(); return false; } Disconnect(); bool done = server->ExecuteVoid(wxT("DROP DATABASE ") + GetQuotedIdentifier() + wxT(";")); if (!done) Connect(); return done; } wxString pgDatabase::GetSql(ctlTree *browser) { if (sql.IsEmpty()) { // If we can't connect to this database, use the maintenance DB pgConn *myConn = GetConnection(); if (!myConn) myConn = GetServer()->GetConnection(); sql = wxT("-- Database: ") + GetQuotedFullIdentifier() + wxT("\n\n") + wxT("-- DROP DATABASE ") + GetQuotedIdentifier() + wxT(";") + wxT("\n\nCREATE DATABASE ") + GetQuotedIdentifier() + wxT("\n WITH OWNER = ") + qtIdent(GetOwner()) + wxT("\n ENCODING = ") + qtDbString(GetEncoding()); if (!GetTablespace().IsEmpty()) sql += wxT("\n TABLESPACE = ") + qtIdent(GetTablespace()); if (myConn && myConn->BackendMinimumVersion(8, 4)) { sql += wxT("\n LC_COLLATE = ") + qtDbString(GetCollate()); sql += wxT("\n LC_CTYPE = ") + qtDbString(GetCType()); } if (myConn && myConn->BackendMinimumVersion(8, 1)) { sql += wxT("\n CONNECTION LIMIT = "); sql << GetConnectionLimit(); } sql += wxT(";\n"); if (variables.GetCount() > 0) { size_t i; wxString username = wxEmptyString; wxString parameter = wxEmptyString; wxString value = wxEmptyString; sql += wxT("\n"); for (i = 0 ; i < variables.GetCount() ; i += 3) { username = variables.Item(i); parameter = variables.Item(i + 1); value = variables.Item(i + 2); if (username.Length() == 0) { sql += wxT("ALTER DATABASE ") + GetQuotedFullIdentifier(); } else { sql += wxT("ALTER ROLE ") + username + wxT(" IN DATABASE ") + GetQuotedFullIdentifier(); } if (parameter != wxT("search_path") && parameter != wxT("temp_tablespaces")) { sql += wxT("\n SET ") + parameter + wxT(" = '") + value + wxT("';\n"); } else { sql += wxT("\n SET ") + parameter + wxT(" = ") + value + wxT(";\n"); } } } if (myConn) { if (!myConn->BackendMinimumVersion(8, 2)) sql += GetGrant(wxT("CT")); else sql += GetGrant(wxT("CTc")); } sql += wxT("\n") + pgDatabase::GetDefaultPrivileges('r', m_defPrivsOnTables, wxT("")); sql += pgDatabase::GetDefaultPrivileges('S', m_defPrivsOnSeqs, wxT("")); sql += pgDatabase::GetDefaultPrivileges('f', m_defPrivsOnFuncs, wxT("")); sql += pgDatabase::GetDefaultPrivileges('T', m_defPrivsOnTypes, wxT("")); sql += GetCommentSql(); if (myConn->BackendMinimumVersion(9, 2)) sql += GetSeqLabelsSql(); } return sql; } void pgDatabase::ShowTreeDetail(ctlTree *browser, frmMain *form, ctlListView *properties, ctlSQLBox *sqlPane) { if (Connect() == PGCONN_OK) { // Set the icon if required UpdateIcon(browser); // Add child nodes if necessary if (browser->GetChildrenCount(GetId(), false) == 0) { wxLogInfo(wxT("Adding child object to database %s"), GetIdentifier().c_str()); if (settings->GetDisplayOption(_("Catalogs"))) browser->AppendCollection(this, catalogFactory); if (settings->GetDisplayOption(_("Casts"))) browser->AppendCollection(this, castFactory); if (connection()->BackendMinimumVersion(9, 3) && settings->GetDisplayOption(_("Event Triggers"))) browser->AppendCollection(this, eventTriggerFactory); if (settings->GetDisplayOption(_("Extensions")) && GetConnection()->BackendMinimumVersion(9, 1)) browser->AppendCollection(this, extensionFactory); if (settings->GetDisplayOption(_("Publications")) && GetConnection()->BackendMinimumVersion(10, 0)) browser->AppendCollection(this, publicationFactory); if (settings->GetDisplayOption(_("Subscriptions")) && GetConnection()->BackendMinimumVersion(10, 0)) browser->AppendCollection(this, subscriptionFactory); if (settings->GetDisplayOption(_("Foreign Data Wrappers")) && GetConnection()->BackendMinimumVersion(8, 4)) browser->AppendCollection(this, foreignDataWrapperFactory); if (settings->GetDisplayOption(_("Languages"))) browser->AppendCollection(this, languageFactory); if (settings->GetDisplayOption(_("Synonyms")) && connection()->EdbMinimumVersion(8, 0)) if (!GetConnection()->BackendMinimumVersion(8, 4)) browser->AppendCollection(this, synonymFactory); if (settings->GetDisplayOption(_("Schemas"))) browser->AppendCollection(this, schemaFactory); if (settings->GetDisplayOption(_("pgpro_scheduler"))) { wxString exists = connection()->ExecuteScalar( wxT("select true ok from pg_extension where extname='pgpro_scheduler'\n")); if (!exists.IsNull()) { browser->AppendCollection(this, projobFactory); } } if (settings->GetDisplayOption(_("Slony-I Clusters"))) browser->AppendCollection(this, slClusterFactory); wxString missingFKsql = wxT("SELECT COUNT(*) FROM\n") wxT(" (SELECT tgargs from pg_trigger tr\n") wxT(" LEFT JOIN pg_depend dep ON dep.objid=tr.oid AND deptype = 'i'\n") wxT(" LEFT JOIN pg_constraint co ON refobjid = co.oid AND contype = 'f'\n") wxT(" WHERE \n"); if (connection()->BackendMinimumVersion(9, 0)) missingFKsql += wxT("tgisinternal\n"); else missingFKsql += wxT("tgisconstraint\n"); missingFKsql += wxT(" AND co.oid IS NULL\n") wxT(" GROUP BY tgargs\n") wxT(" HAVING count(1) = 3) AS foo"); missingFKs = StrToLong(connection()->ExecuteScalar(missingFKsql)); // Get configuration wxString query; if (connection()->BackendMinimumVersion(9, 0)) { query = wxT("WITH configs AS ") wxT("(SELECT rolname, unnest(setconfig) AS config") wxT(" FROM pg_db_role_setting s") wxT(" LEFT JOIN pg_roles r ON r.oid=s.setrole") wxT(" WHERE s.setdatabase=") + NumToStr(GetOid()) + wxT(")\n") wxT("SELECT rolname, split_part(config, '=', 1) AS variable, ") wxT(" replace(config, split_part(config, '=', 1) || '=', '') AS value\n") wxT("FROM configs"); } else { wxString query_mydb = wxT("SELECT datconfig FROM pg_database WHERE oid=") + NumToStr(GetOid()); query = wxT("SELECT '' AS rolname, split_part(config, '=', 1) AS variable,\n") wxT(" replace(config,split_part(config, '=', 1) || '=', '') AS value\n") wxT("FROM (\n") wxT(" SELECT\n") wxT(" (\n") wxT(" SELECT datconfig[i]\n") wxT(" FROM pg_database\n") wxT(" WHERE oid=") + NumToStr(GetOid()) + wxT("\n") wxT(" ) AS config\n") wxT(" FROM generate_series(array_lower((") + query_mydb + wxT("),1), array_upper((") + query_mydb + wxT("),1)) AS i\n") wxT(" ) configs"); } pgSet *configs = connection()->ExecuteSet(query); if (configs) { while (!configs->Eof()) { variables.Add(configs->GetVal(wxT("rolname"))); variables.Add(configs->GetVal(wxT("variable"))); variables.Add(configs->GetVal(wxT("value"))); configs->MoveNext(); } delete configs; } } } GetServer()->iSetLastDatabase(GetName()); if (properties) { // Setup listview CreateListColumns(properties); properties->AppendItem(_("Name"), GetName()); properties->AppendItem(_("OID"), NumToStr(GetOid())); properties->AppendItem(_("Owner"), GetOwner()); properties->AppendItem(_("ACL"), GetAcl()); if (!GetPath().IsEmpty()) properties->AppendItem(_("Path"), GetPath()); // We may not actually be connected... if (GetConnection() && GetConnection()->BackendMinimumVersion(8, 0)) { properties->AppendItem(_("Tablespace"), GetTablespace()); properties->AppendItem(_("Default tablespace"), GetDefaultTablespace()); } properties->AppendItem(_("Encoding"), GetEncoding()); if (GetConnection() && GetConnection()->BackendMinimumVersion(8, 4)) { properties->AppendItem(_("Collation"), GetCollate()); properties->AppendItem(_("Character type"), GetCType()); } properties->AppendItem(_("Default schema"), defaultSchema); if (GetConnection() && GetConnection()->BackendMinimumVersion(9, 0)) { properties->AppendItem(_("Default table ACL"), m_defPrivsOnTables); properties->AppendItem(_("Default sequence ACL"), m_defPrivsOnSeqs); properties->AppendItem(_("Default function ACL"), m_defPrivsOnFuncs); } if (GetConnection() && GetConnection()->BackendMinimumVersion(9, 2)) properties->AppendItem(_("Default type ACL"), m_defPrivsOnTypes); size_t i; wxString username; wxString parameter; wxString value; for (i = 0 ; i < variables.GetCount() ; i += 3) { username = variables.Item(i); parameter = variables.Item(i + 1); value = variables.Item(i + 2); if (username.Length() == 0) { properties->AppendItem(parameter, value); } else { properties->AppendItem(parameter + wxT(" (role ") + username + wxT(")"), value); } } properties->AppendYesNoItem(_("Allow connections?"), GetAllowConnections()); properties->AppendYesNoItem(_("Connected?"), GetConnected()); if (GetConnection() && GetConnection()->BackendMinimumVersion(8, 1)) { wxString strConnLimit; strConnLimit.Printf(wxT("%ld"), GetConnectionLimit()); properties->AppendItem(_("Connection limit"), strConnLimit); } properties->AppendYesNoItem(_("System database?"), GetSystemObject()); if (GetMissingFKs()) properties->AppendItem(_("Old style FKs"), GetMissingFKs()); if (!GetSchemaRestriction().IsEmpty()) properties->AppendItem(_("Schema restriction"), GetSchemaRestriction()); properties->AppendItem(_("Comment"), firstLineOnly(GetComment())); if (!GetLabels().IsEmpty()) { wxArrayString seclabels = GetProviderLabelArray(); if (seclabels.GetCount() > 0) { for (unsigned int index = 0 ; index < seclabels.GetCount() - 1 ; index += 2) { properties->AppendItem(seclabels.Item(index), seclabels.Item(index + 1)); } } } } if (form && GetCanHint() && !hintShown) { ShowHint(form, false); } } pgObject *pgDatabase::Refresh(ctlTree *browser, const wxTreeItemId item) { pgDatabase *database = 0; pgCollection *coll = browser->GetParentCollection(item); if (coll) database = (pgDatabase *)databaseFactory.CreateObjects(coll, 0, wxT(" WHERE db.oid=") + GetOidStr() + wxT("\n")); return database; } pgObject *pgDatabaseFactory::CreateObjects(pgCollection *collection, ctlTree *browser, const wxString &restriction) { pgDatabase *database = 0; pgSet *databases; wxString datcollate, datctype, datconnlimit, seclabelsql; if (collection->GetConnection()->BackendMinimumVersion(8, 1)) { datconnlimit = wxT(", db.datconnlimit as connectionlimit"); } if (collection->GetConnection()->BackendMinimumVersion(8, 4)) { datctype = wxT(", db.datctype as ctype"); datcollate = wxT(", db.datcollate as collate"); } if (collection->GetConnection()->BackendMinimumVersion(9, 2)) { seclabelsql = wxT(",\n(SELECT array_agg(label) FROM pg_shseclabel sl1 WHERE sl1.objoid=db.oid) AS labels") wxT(",\n(SELECT array_agg(provider) FROM pg_shseclabel sl2 WHERE sl2.objoid=db.oid) AS providers"); } wxString restr = restriction; if (!collection->GetServer()->GetDbRestriction().IsEmpty()) { if (restr.IsEmpty()) restr = wxT(" WHERE datname IN ("); else restr += wxT(" AND datname IN ("); restr += collection->GetServer()->GetDbRestriction() + wxT(")\n"); } // In 9.0+, database config options are in pg_db_role_setting if (collection->GetConnection()->BackendMinimumVersion(9, 0)) { databases = collection->GetServer()->ExecuteSet( wxT("SELECT db.oid, datname, db.dattablespace AS spcoid, spcname, datallowconn, datacl, ") wxT("pg_encoding_to_char(encoding) AS serverencoding, pg_get_userbyid(datdba) AS datowner,") wxT("has_database_privilege(db.oid, 'CREATE') as cancreate, \n") wxT("current_setting('default_tablespace') AS default_tablespace, \n") wxT("descr.description\n") + datconnlimit + datcollate + datctype + seclabelsql + wxT(" FROM pg_database db\n") wxT(" LEFT OUTER JOIN pg_tablespace ta ON db.dattablespace=ta.OID\n") wxT(" LEFT OUTER JOIN pg_shdescription descr ON (db.oid=descr.objoid AND descr.classoid='pg_database'::regclass)\n") + restr + wxT(" ORDER BY datname")); } else if (collection->GetConnection()->BackendMinimumVersion(8, 0)) databases = collection->GetServer()->ExecuteSet( wxT("SELECT db.oid, datname, db.dattablespace AS spcoid, spcname, datallowconn, datacl, ") wxT("pg_encoding_to_char(encoding) AS serverencoding, pg_get_userbyid(datdba) AS datowner,") wxT("has_database_privilege(db.oid, 'CREATE') as cancreate, \n") wxT("current_setting('default_tablespace') AS default_tablespace, \n") wxT("descr.description\n") + datconnlimit + datcollate + datctype + wxT(" FROM pg_database db\n") wxT(" LEFT OUTER JOIN pg_tablespace ta ON db.dattablespace=ta.OID\n") wxT(" LEFT OUTER JOIN ") + wxString(collection->GetConnection()->BackendMinimumVersion(8, 2) ? wxT("pg_shdescription") : wxT("pg_description")) + wxT(" descr ON (db.oid=descr.objoid AND descr.classoid='pg_database'::regclass)\n") + restr + wxT(" ORDER BY datname")); else databases = collection->GetServer()->ExecuteSet( wxT("SELECT db.oid, datname, datpath, datallowconn, datacl, ") wxT("pg_encoding_to_char(encoding) AS serverencoding, pg_get_userbyid(datdba) AS datowner,") wxT("has_database_privilege(db.oid, 'CREATE') as cancreate,\n") wxT("descr.description\n") wxT(" FROM pg_database db\n") wxT(" LEFT OUTER JOIN pg_description descr ON (db.oid=descr.objoid AND descr.classoid='pg_database'::regclass)\n") + restr + wxT(" ORDER BY datname")); if (databases) { while (!databases->Eof()) { wxString name = databases->GetVal(wxT("datname")); database = new pgDatabase(name); database->iSetServer(collection->GetServer()); database->iSetOid(databases->GetOid(wxT("oid"))); database->iSetOwner(databases->GetVal(wxT("datowner"))); database->iSetAcl(databases->GetVal(wxT("datacl"))); database->iSetEncoding(databases->GetVal(wxT("serverencoding"))); database->iSetCreatePrivilege(databases->GetBool(wxT("cancreate"))); database->iSetComment(databases->GetVal(wxT("description"))); database->iSetAllowConnections(databases->GetBool(wxT("datallowconn"))); if (collection->GetConnection()->BackendMinimumVersion(8, 0)) { database->iSetTablespace(databases->GetVal(wxT("spcname"))); database->iSetTablespaceOid(databases->GetOid(wxT("spcoid"))); if (databases->GetVal(wxT("default_tablespace")) == wxEmptyString || databases->GetVal(wxT("default_tablespace")) == wxT("unset")) database->iSetDefaultTablespace(databases->GetVal(wxT("spcname"))); else database->iSetDefaultTablespace(databases->GetVal(wxT("default_tablespace"))); } else database->iSetPath(databases->GetVal(wxT("datpath"))); if (collection->GetConnection()->BackendMinimumVersion(8, 1)) { database->iSetConnectionLimit(databases->GetLong(wxT("connectionlimit"))); } if (collection->GetConnection()->BackendMinimumVersion(8, 4)) { database->iSetCollate(databases->GetVal(wxT("collate"))); database->iSetCType(databases->GetVal(wxT("ctype"))); } if (collection->GetServer()->GetServerIndex()) { wxString value; settings->Read(wxT("Servers/") + NumToStr(collection->GetServer()->GetServerIndex()) + wxT("/Databases/") + name + wxT("/SchemaRestriction"), &value, wxEmptyString); database->iSetSchemaRestriction(value); } if (collection->GetConnection()->BackendMinimumVersion(9, 2)) { database->iSetProviders(databases->GetVal(wxT("providers"))); database->iSetLabels(databases->GetVal(wxT("labels"))); } // Add the treeview node if required if (settings->GetShowSystemObjects() || !database->GetSystemObject()) { if (browser) { int icon; if (database->GetName() == database->GetServer()->GetDatabaseName()) icon = databaseFactory.GetIconId(); else icon = databaseFactory.GetClosedIconId(); browser->AppendItem(collection->GetId(), database->GetIdentifier(), icon, -1, database); } else break; } else delete database; databases->MoveNext(); } delete databases; } return database; } wxString pgDatabase::GetDefaultPrivileges(const wxChar &cType, wxString strDefPrivs, const wxString &strSchema) { wxString strDefPrivsSql; if (!strDefPrivs.IsEmpty()) { wxString strRole, strPriv, strSupportedPrivs, strType; strDefPrivs.Replace(wxT("\\\""), wxT("\""), true); strDefPrivs.Replace(wxT("\\\\"), wxT("\\"), true); switch(cType) { case 'r': strType = wxT("TABLES"); strSupportedPrivs = wxT("arwdDxtm"); break; case 'S': strType = wxT("SEQUENCES"); strSupportedPrivs = wxT("rwU"); break; case 'f': strType = wxT("FUNCTIONS"); strSupportedPrivs = wxT("X"); break; case 'T': strType = wxT("TYPES"); strSupportedPrivs = wxT("U"); break; default: return wxT(""); } // Removing starting brace '{' and ending brace '}' strDefPrivs = strDefPrivs.SubString(1, strDefPrivs.Length() - 1); while (pgObject::findUserPrivs(strDefPrivs, strRole, strPriv)) { strDefPrivsSql += pgObject::GetDefaultPrivileges(strType, strSupportedPrivs, strSchema, wxT(""), strPriv, qtIdent(strRole)); strRole = wxT(""); strPriv = wxT(""); } } return strDefPrivsSql; } bool pgDatabase::CanCreate() { return GetDatabase()->GetCreatePrivilege(); } bool pgDatabase::CanDebugPlpgsql() { wxString preload_option; // Result cache - 0 = not tested, 1 = false, 2 = true. if (canDebugPlpgsql == 1) return false; else if (canDebugPlpgsql == 2) return true; // "show shared_preload_libraries" does not work for other than // the super users. if (GetServer()->GetSuperUser()) { // Parameter's name depends of the backend's version if (server->GetConnection()->BackendMinimumVersion(8, 2)) { preload_option = wxT("shared_preload_libraries"); } else { preload_option = wxT("preload_libraries"); } // Check the appropriate plugin is loaded if (!ExecuteScalar(wxT("SHOW ") + preload_option).Contains(wxT("plugin_debugger"))) { canDebugPlpgsql = 1; return false; } } if (ExecuteScalar(wxT("SELECT count(*) FROM pg_proc WHERE proname = 'pldbg_get_target_info';")) == wxT("0")) { canDebugPlpgsql = 1; return false; } // If this is EDBAS81, the debuggers will be built into the PLs // so we don't need to check any further. if (server->GetConnection()->EdbMinimumVersion(8, 1)) { canDebugPlpgsql = 2; return true; } // On EDBAS82 and PostgreSQL, we need to check to make sure that // the debugger library is also available. if (ExecuteScalar(wxT("SELECT count(*) FROM pg_proc WHERE proname = 'plpgsql_oid_debug';")) == wxT("0")) { canDebugPlpgsql = 1; return false; } else { canDebugPlpgsql = 2; return true; } return true; } bool pgDatabase::CanDebugEdbspl() { // Result cache - 0 = not tested, 1 = false, 2 = true. if (canDebugEdbspl == 1) return false; else if (canDebugEdbspl == 2) return true; // "show shared_preload_libraries" does not work for other than // the super users. if (GetServer()->GetSuperUser()) { // Check the appropriate plugin is loaded // Before EDBAS92, there was a separate library for SPL and PL/pgSQL. // Starting with 9.2, EDB uses the community version of pldebugger, // and support for both languages is built into plugin_debugger.so wxString library_name; if (server->GetConnection()->EdbMinimumVersion(9, 2)) library_name = wxT("plugin_debugger"); else library_name = wxT("plugin_spl_debugger"); if (!ExecuteScalar(wxT("SHOW shared_preload_libraries;")).Contains(library_name)) { canDebugEdbspl = 1; return false; } } if (ExecuteScalar(wxT("SELECT count(*) FROM pg_proc WHERE proname = 'pldbg_get_target_info';")) == wxT("0")) { canDebugEdbspl = 1; return false; } // If this is EDBAS81, the debuggers will be built into the PLs // so we don't need to check any further. if (server->GetConnection()->EdbMinimumVersion(8, 1)) { canDebugEdbspl = 2; return true; } // On EDBAS82 and PostgreSQL, we need to check to make sure that // the debugger library is also available. if (ExecuteScalar(wxT("SELECT count(*) FROM pg_proc WHERE proname = 'edb_oid_debug';")) == wxT("0")) { canDebugEdbspl = 1; return false; } else { canDebugEdbspl = 2; return true; } return true; } pgDatabaseCollection::pgDatabaseCollection(pgaFactory *factory, pgServer *sv) : pgServerObjCollection(factory, sv) { } wxString pgDatabaseCollection::GetTranslatedMessage(int kindOfMessage) const { wxString message = wxEmptyString; switch (kindOfMessage) { case RETRIEVINGDETAILS: message = _("Retrieving details on databases"); break; case REFRESHINGDETAILS: message = _("Refreshing databases"); break; case STATISTICSREPORT: message = _("Databases statistics report"); break; case OBJSTATISTICS: message = _("Databases statistics"); break; case OBJECTSLISTREPORT: message = _("Databases list report"); break; } return message; } void pgDatabaseCollection::ShowStatistics(frmMain *form, ctlListView *statistics) { wxLogInfo(wxT("Displaying statistics for databases on ") + GetServer()->GetIdentifier()); bool hasSize = GetConnection()->HasFeature(FEATURE_SIZE); wxString restr; if (!GetServer()->GetDbRestriction().IsEmpty()) { if (restr.IsEmpty()) restr = wxT(" WHERE db.datname IN ("); else restr = wxT(" AND db.datname IN ("); restr += GetServer()->GetDbRestriction() + wxT(")\n"); } wxString sql = wxT("SELECT db.datid, db.datname, numbackends, xact_commit, xact_rollback, blks_read, blks_hit"); if (GetConnection()->BackendMinimumVersion(8, 3)) sql += wxT(", tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted"); if (GetConnection()->BackendMinimumVersion(9, 1)) sql += wxT(", stats_reset, slave.confl_tablespace, slave.confl_lock, slave.confl_snapshot, slave.confl_bufferpin, slave.confl_deadlock"); if (hasSize) sql += wxT(", pg_size_pretty(pg_database_size(db.datid)) as size"); sql += wxT("\n FROM pg_stat_database db\n"); if (GetConnection()->BackendMinimumVersion(9, 1)) sql += wxT(" JOIN pg_stat_database_conflicts slave ON db.datid=slave.datid\n"); sql += restr + wxT(" ORDER BY db.datname"); // Add the statistics view columns statistics->ClearAll(); statistics->AddColumn(_("Database"), 60); statistics->AddColumn(_("Backends"), 50); if (hasSize) statistics->AddColumn(_("Size"), 60); statistics->AddColumn(_("Xact Committed"), 60); statistics->AddColumn(_("Xact Rolled Back"), 60); statistics->AddColumn(_("Blocks Read"), 60); statistics->AddColumn(_("Blocks Hit"), 60); if (GetConnection()->BackendMinimumVersion(8, 3)) { statistics->AddColumn(_("Tuples Returned"), 60); statistics->AddColumn(_("Tuples Fetched"), 60); statistics->AddColumn(_("Tuples Inserted"), 60); statistics->AddColumn(_("Tuples Updated"), 60); statistics->AddColumn(_("Tuples Deleted"), 60); } if (GetConnection()->BackendMinimumVersion(9, 1)) { statistics->AddColumn(_("Last statistics reset"), 60); statistics->AddColumn(_("Tablespace conflicts"), 60); statistics->AddColumn(_("Lock conflicts"), 60); statistics->AddColumn(_("Snapshot conflicts"), 60); statistics->AddColumn(_("Bufferpin conflicts"), 60); statistics->AddColumn(_("Deadlock conflicts"), 60); } bool sysobj; pgSet *stats = GetServer()->ExecuteSet(sql); if (stats) { while (!stats->Eof()) { if (stats->GetVal(wxT("datname")) == wxT("template0")) sysobj = true; else sysobj = stats->GetOid(wxT("datid")) <= GetServer()->GetLastSystemOID(); if (settings->GetShowSystemObjects() || !sysobj) { statistics->InsertItem(statistics->GetItemCount(), stats->GetVal(wxT("datname")), PGICON_STATISTICS); statistics->SetItem(statistics->GetItemCount() - 1, 1, stats->GetVal(wxT("numbackends"))); if (hasSize) statistics->SetItem(statistics->GetItemCount() - 1, 2, stats->GetVal(wxT("size"))); statistics->SetItem(statistics->GetItemCount() - 1, 2 + (hasSize ? 1 : 0), stats->GetVal(wxT("xact_commit"))); statistics->SetItem(statistics->GetItemCount() - 1, 3 + (hasSize ? 1 : 0), stats->GetVal(wxT("xact_rollback"))); statistics->SetItem(statistics->GetItemCount() - 1, 4 + (hasSize ? 1 : 0), stats->GetVal(wxT("blks_read"))); statistics->SetItem(statistics->GetItemCount() - 1, 5 + (hasSize ? 1 : 0), stats->GetVal(wxT("blks_hit"))); if (GetConnection()->BackendMinimumVersion(8, 3)) { statistics->SetItem(statistics->GetItemCount() - 1, 6 + (hasSize ? 1 : 0), stats->GetVal(wxT("tup_returned"))); statistics->SetItem(statistics->GetItemCount() - 1, 7 + (hasSize ? 1 : 0), stats->GetVal(wxT("tup_fetched"))); statistics->SetItem(statistics->GetItemCount() - 1, 8 + (hasSize ? 1 : 0), stats->GetVal(wxT("tup_inserted"))); statistics->SetItem(statistics->GetItemCount() - 1, 9 + (hasSize ? 1 : 0), stats->GetVal(wxT("tup_updated"))); statistics->SetItem(statistics->GetItemCount() - 1, 10 + (hasSize ? 1 : 0), stats->GetVal(wxT("tup_deleted"))); } if (GetConnection()->BackendMinimumVersion(9, 1)) { statistics->SetItem(statistics->GetItemCount() - 1, 11 + (hasSize ? 1 : 0), stats->GetVal(wxT("stats_reset"))); statistics->SetItem(statistics->GetItemCount() - 1, 12 + (hasSize ? 1 : 0), stats->GetVal(wxT("confl_tablespace"))); statistics->SetItem(statistics->GetItemCount() - 1, 13 + (hasSize ? 1 : 0), stats->GetVal(wxT("confl_lock"))); statistics->SetItem(statistics->GetItemCount() - 1, 14 + (hasSize ? 1 : 0), stats->GetVal(wxT("confl_snapshot"))); statistics->SetItem(statistics->GetItemCount() - 1, 15 + (hasSize ? 1 : 0), stats->GetVal(wxT("confl_bufferpin"))); statistics->SetItem(statistics->GetItemCount() - 1, 16 + (hasSize ? 1 : 0), stats->GetVal(wxT("confl_deadlock"))); } } stats->MoveNext(); } delete stats; } } ///////////////////////////////////////////////////// pgDatabaseObjCollection::pgDatabaseObjCollection(pgaFactory *factory, pgDatabase *db) : pgCollection(factory) { database = db; server = database->GetServer(); } bool pgDatabaseObjCollection::CanCreate() { if (IsCollectionForType(PGM_CATALOG)) return false; return GetDatabase()->GetCreatePrivilege(); } #include "images/database.pngc" #include "images/database-sm.pngc" #include "images/databases.pngc" #include "images/closeddatabase.pngc" #include "images/closeddatabase-sm.pngc" pgDatabaseFactory::pgDatabaseFactory() : pgServerObjFactory(__("Database"), __("New Database..."), __("Create a new Database."), database_png_img, database_sm_png_img) { metaType = PGM_DATABASE; closedId = addIcon(closeddatabase_png_img); smallClosedId = addIcon(closeddatabase_sm_png_img); } pgCollection *pgDatabaseFactory::CreateCollection(pgObject *obj) { return new pgDatabaseCollection(GetCollectionFactory(), (pgServer *)obj); } pgCollection *pgDatabaseObjFactory::CreateCollection(pgObject *obj) { return new pgDatabaseObjCollection(GetCollectionFactory(), (pgDatabase *)obj); } pgDatabaseFactory databaseFactory; static pgaCollectionFactory cf(&databaseFactory, __("Databases"), databases_png_img); disconnectDatabaseFactory::disconnectDatabaseFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list) { mnu->Append(id, _("Disconnec&t database"), _("Disconnect from the selected database.")); } wxWindow *disconnectDatabaseFactory::StartDialog(frmMain *form, pgObject *obj) { ctlTree *browser = form->GetBrowser(); pgDatabase *database = (pgDatabase *)obj; if (obj->CheckOpenDialogs(browser, browser->GetSelection())) { wxString msg = _("There are properties dialogues open for one or more objects that would be refreshed. Please close the properties dialogues and try again."); wxMessageBox(msg, _("Cannot disconnect database"), wxICON_WARNING | wxOK); } else { database->Disconnect(); database->UpdateIcon(browser); browser->DeleteChildren(obj->GetId()); browser->SelectItem(browser->GetItemParent(obj->GetId())); form->execSelChange(browser->GetItemParent(obj->GetId()), true); } return 0; } bool disconnectDatabaseFactory::CheckEnable(pgObject *obj) { if (obj && obj->IsCreatedBy(databaseFactory)) return ((pgDatabase *)obj)->GetConnected() && (((pgDatabase *)obj)->GetName() != ((pgDatabase *)obj)->GetServer()->GetDatabaseName()); return false; }