/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jkiss.dbeaver.ext.postgresql.model; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ext.postgresql.PostgreConstants; import org.jkiss.dbeaver.ext.postgresql.PostgreUtils; import org.jkiss.dbeaver.model.DBPRefreshableObject; import org.jkiss.dbeaver.model.DBPStatefulObject; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.exec.DBCExecutionContext; import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose; import org.jkiss.dbeaver.model.exec.DBCSession; import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement; import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; import org.jkiss.dbeaver.model.exec.jdbc.JDBCStatement; import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext; import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectCache; import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectLookupCache; import org.jkiss.dbeaver.model.meta.Association; import org.jkiss.dbeaver.model.meta.Property; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.*; import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog; import org.jkiss.utils.LongKeyMap; import java.sql.SQLException; import java.util.*; /** * PostgreDatabase */ public class PostgreDatabase implements DBSInstance, DBSCatalog, DBPRefreshableObject, DBPStatefulObject, PostgreObject, DBSObjectSelector { private static final Log log = Log.getLog(PostgreDatabase.class); private PostgreDataSource dataSource; private long oid; private String name; private long ownerId; private long encodingId; private String collate; private String ctype; private boolean isTemplate; private boolean allowConnect; private int connectionLimit; private long tablespaceId; public final RoleCache roleCache = new RoleCache(); public final AccessMethodCache accessMethodCache = new AccessMethodCache(); public final ForeignDataWrapperCache foreignDataWrapperCache = new ForeignDataWrapperCache(); public final ForeignServerCache foreignServerCache = new ForeignServerCache(); public final LanguageCache languageCache = new LanguageCache(); public final EncodingCache encodingCache = new EncodingCache(); public final TablespaceCache tablespaceCache = new TablespaceCache(); public final SchemaCache schemaCache = new SchemaCache(); public final LongKeyMap<PostgreDataType> dataTypeCache = new LongKeyMap<>(); public PostgreDatabase(PostgreDataSource dataSource, JDBCResultSet dbResult) throws SQLException { this.dataSource = dataSource; this.loadInfo(dbResult); } private void loadInfo(JDBCResultSet dbResult) throws SQLException { this.oid = JDBCUtils.safeGetLong(dbResult, "oid"); this.name = JDBCUtils.safeGetString(dbResult, "datname"); this.ownerId = JDBCUtils.safeGetLong(dbResult, "datdba"); this.encodingId = JDBCUtils.safeGetLong(dbResult, "encoding"); this.collate = JDBCUtils.safeGetString(dbResult, "datcollate"); this.ctype = JDBCUtils.safeGetString(dbResult, "datctype"); this.isTemplate = JDBCUtils.safeGetBoolean(dbResult, "datistemplate"); this.allowConnect = JDBCUtils.safeGetBoolean(dbResult, "datallowconn"); this.connectionLimit = JDBCUtils.safeGetInt(dbResult, "datconnlimit"); this.tablespaceId = JDBCUtils.safeGetLong(dbResult, "dattablespace"); } @NotNull @Override public PostgreDatabase getDatabase() { return this; } @Override public long getObjectId() { return this.oid; } @NotNull @Override @Property(viewable = true, order = 2) public String getName() { return name; } @Nullable @Override public String getDescription() { return null; } @Override public DBSObject getParentObject() { return dataSource.getContainer(); } @NotNull @Override public PostgreDataSource getDataSource() { return dataSource; } @Override public boolean isPersisted() { return true; } /////////////////////////////////////////////////// // Properties @Property(viewable = false, order = 3) public PostgreRole getDBA(DBRProgressMonitor monitor) throws DBException { return PostgreUtils.getObjectById(monitor, roleCache, this, ownerId); } @Property(viewable = false, order = 4) public PostgreTablespace getDefaultTablespace(DBRProgressMonitor monitor) throws DBException { return PostgreUtils.getObjectById(monitor, tablespaceCache, this, tablespaceId); } @Property(viewable = false, order = 5) public PostgreCharset getDefaultEncoding(DBRProgressMonitor monitor) throws DBException { return PostgreUtils.getObjectById(monitor, encodingCache, this, encodingId); } @Property(viewable = false, order = 10) public String getCollate() { return collate; } @Property(viewable = false, order = 11) public String getCtype() { return ctype; } @Property(viewable = false, order = 12) public boolean isTemplate() { return isTemplate; } @Property(viewable = false, order = 13) public boolean isAllowConnect() { return allowConnect; } @Property(viewable = false, order = 14) public int getConnectionLimit() { return connectionLimit; } /////////////////////////////////////////////////// // Instance methods @NotNull @Override public DBCExecutionContext getDefaultContext(boolean meta) { return dataSource.getDefaultContext(meta); } @NotNull @Override public DBCExecutionContext[] getAllContexts() { return dataSource.getAllContexts(); } @NotNull @Override public DBCExecutionContext openIsolatedContext(@NotNull DBRProgressMonitor monitor, @NotNull String purpose) throws DBException { return dataSource.openIsolatedContext(monitor, purpose); } @Override public void shutdown(DBRProgressMonitor monitor) { } /////////////////////////////////////////////// // Infos @Association public Collection<PostgreRole> getAuthIds(DBRProgressMonitor monitor) throws DBException { return roleCache.getAllObjects(monitor, this); } @Association public Collection<PostgreAccessMethod> getAccessMethods(DBRProgressMonitor monitor) throws DBException { return accessMethodCache.getAllObjects(monitor, this); } @Association public Collection<PostgreForeignDataWrapper> getForeignDataWrappers(DBRProgressMonitor monitor) throws DBException { return foreignDataWrapperCache.getAllObjects(monitor, this); } @Association public Collection<PostgreForeignServer> getForeignServers(DBRProgressMonitor monitor) throws DBException { return foreignServerCache.getAllObjects(monitor, this); } @Association public Collection<PostgreLanguage> getLanguages(DBRProgressMonitor monitor) throws DBException { return languageCache.getAllObjects(monitor, this); } @Association public Collection<PostgreCharset> getEncodings(DBRProgressMonitor monitor) throws DBException { return encodingCache.getAllObjects(monitor, this); } @Association public Collection<PostgreTablespace> getTablespaces(DBRProgressMonitor monitor) throws DBException { return tablespaceCache.getAllObjects(monitor, this); } /////////////////////////////////////////////// // Object container @Association public Collection<PostgreSchema> getSchemas(DBRProgressMonitor monitor) throws DBException { if (this != dataSource.getDefaultInstance()) { throw new DBException("Can't access non-default database"); } // Get all schemas return schemaCache.getAllObjects(monitor, this); } @Nullable public PostgreSchema getCatalogSchema(DBRProgressMonitor monitor) throws DBException { return getSchema(monitor, PostgreConstants.CATALOG_SCHEMA_NAME); } @Nullable PostgreSchema getCatalogSchema() { return schemaCache.getCachedObject(PostgreConstants.CATALOG_SCHEMA_NAME); } void cacheDataTypes(DBRProgressMonitor monitor) throws DBException { dataTypeCache.clear(); // Cache data types for (final PostgreSchema pgSchema : getSchemas(monitor)) { pgSchema.getDataTypes(monitor); } } public PostgreSchema getSchema(DBRProgressMonitor monitor, String name) throws DBException { if (this != dataSource.getDefaultInstance()) { throw new DBException("Can't access non-default database"); } return schemaCache.getObject(monitor, this, name); } public PostgreSchema getSchema(DBRProgressMonitor monitor, long oid) throws DBException { if (this != dataSource.getDefaultInstance()) { throw new DBException("Can't access non-default database"); } for (PostgreSchema schema : schemaCache.getAllObjects(monitor, this)) { if (schema.getObjectId() == oid) { return schema; } } return null; } PostgreTableBase findTable(DBRProgressMonitor monitor, long schemaId, long tableId) throws DBException { PostgreSchema schema = getSchema(monitor, schemaId); if (schema == null) { log.error("Catalog " + schemaId + " not found"); return null; } return schema.getTable(monitor, tableId); } @Override public Collection<? extends DBSObject> getChildren(@NotNull DBRProgressMonitor monitor) throws DBException { return getSchemas(monitor); } @Override public DBSObject getChild(@NotNull DBRProgressMonitor monitor, @NotNull String childName) throws DBException { return getSchema(monitor, childName); } @Override public Class<? extends DBSObject> getChildType(@NotNull DBRProgressMonitor monitor) throws DBException { return PostgreSchema.class; } @Override public void cacheStructure(@NotNull DBRProgressMonitor monitor, int scope) throws DBException { } @NotNull @Override public DBSObjectState getObjectState() { if (this == dataSource.getDefaultInstance()) { return DBSObjectState.NORMAL; } else { return PostgreConstants.STATE_UNAVAILABLE; } } @Override public void refreshObjectState(@NotNull DBRProgressMonitor monitor) throws DBCException { } @Override public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException { roleCache.clearCache(); accessMethodCache.clearCache(); languageCache.clearCache(); encodingCache.clearCache(); tablespaceCache.clearCache(); schemaCache.clearCache(); cacheDataTypes(monitor); return this; } public Collection<PostgreRole> getUsers(DBRProgressMonitor monitor) throws DBException { return roleCache.getAllObjects(monitor, this); } @Override public boolean supportsDefaultChange() { return true; } @Nullable @Override public PostgreSchema getDefaultObject() { return schemaCache.getCachedObject(dataSource.getActiveSchemaName()); } @Override public void setDefaultObject(@NotNull DBRProgressMonitor monitor, @NotNull DBSObject object) throws DBException { if (object instanceof PostgreSchema) { PostgreSchema oldActive = getDefaultObject(); if (oldActive == object) { return; } for (JDBCExecutionContext context : dataSource.getAllContexts()) { setSearchPath(monitor, (PostgreSchema)object, context); } dataSource.setActiveSchemaName(object.getName()); dataSource.setSearchPath(object.getName()); if (oldActive != null) { DBUtils.fireObjectSelect(oldActive, false); } DBUtils.fireObjectSelect(object, true); } } @Override public boolean refreshDefaultObject(@NotNull DBCSession session) throws DBException { return dataSource.refreshDefaultObject(session); } void setSearchPath(DBRProgressMonitor monitor, PostgreSchema schema, JDBCExecutionContext context) throws DBCException { try (JDBCSession session = context.openSession(monitor, DBCExecutionPurpose.UTIL, "Change search path")) { JDBCUtils.executeSQL(session, "SET search_path = \"$user\"," + DBUtils.getQuotedIdentifier(schema)); } catch (SQLException e) { throw new DBCException("Error setting search path", e, dataSource); } } public PostgreProcedure getProcedure(DBRProgressMonitor monitor, long schemaId, long procId) throws DBException { final PostgreSchema schema = getSchema(monitor, schemaId); if (schema != null) { return PostgreUtils.getObjectById(monitor, schema.proceduresCache, schema, procId); } return null; } public PostgreDataType getDataType(long typeId) { if (typeId <= 0) { return null; } PostgreDataType dataType = dataTypeCache.get(typeId); if (dataType != null) { return dataType; } for (PostgreSchema schema : getDatabase().schemaCache.getCachedObjects()) { dataType = schema.dataTypeCache.getDataType(typeId); if (dataType != null) { dataTypeCache.put(typeId, dataType); return dataType; } } log.debug("Data type '" + typeId + "' not found"); return null; } public PostgreDataType getDataType(String typeName) { if (typeName.endsWith("[]")) { // In some cases ResultSetMetadata returns it as [] typeName = "_" + typeName.substring(0, typeName.length() - 2); } String alias = PostgreConstants.DATA_TYPE_ALIASES.get(typeName); if (alias != null) { typeName = alias; } { // First check system catalog final PostgreSchema schema = getCatalogSchema(); if (schema != null) { final PostgreDataType dataType = schema.dataTypeCache.getCachedObject(typeName); if (dataType != null) { return dataType; } } } // Check schemas in search path final List<String> searchPath = dataSource.getSearchPath(); for (String schemaName : searchPath) { final PostgreSchema schema = schemaCache.getCachedObject(schemaName); if (schema != null) { final PostgreDataType dataType = schema.dataTypeCache.getCachedObject(typeName); if (dataType != null) { return dataType; } } } // Check the rest for (PostgreSchema schema : schemaCache.getCachedObjects()) { if (searchPath.contains(schema.getName())) { continue; } final PostgreDataType dataType = schema.dataTypeCache.getCachedObject(typeName); if (dataType != null) { return dataType; } } log.debug("Data type '" + typeName + "' not found in database '" + getName() + "'"); return null; } @Override public String toString() { return name; } class RoleCache extends JDBCObjectCache<PostgreDatabase, PostgreRole> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT a.oid,a.* FROM pg_catalog.pg_roles a " + "\nORDER BY a.oid" ); } @Override protected PostgreRole fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreRole(owner, dbResult); } @Override protected boolean handleCacheReadError(DBException error) { // #271, #501: in some databases (AWS?) pg_authid is not accessible // FIXME: maybe some better workaround? if (PostgreConstants.EC_PERMISSION_DENIED.equals(error.getDatabaseState())) { log.warn(error); setCache(Collections.<PostgreRole>emptyList()); return true; } return false; } } class AccessMethodCache extends JDBCObjectCache<PostgreDatabase, PostgreAccessMethod> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT am.oid,am.* FROM pg_catalog.pg_am am " + "\nORDER BY am.oid" ); } @Override protected PostgreAccessMethod fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreAccessMethod(owner, dbResult); } } class EncodingCache extends JDBCObjectCache<PostgreDatabase, PostgreCharset> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT c.contoencoding as encid,pg_catalog.pg_encoding_to_char(c.contoencoding) as encname\n" + "FROM pg_catalog.pg_conversion c\n" + "GROUP BY c.contoencoding\n" + "ORDER BY 2\n" ); } @Override protected PostgreCharset fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreCharset(owner, dbResult); } } class LanguageCache extends JDBCObjectCache<PostgreDatabase, PostgreLanguage> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT l.oid,l.* FROM pg_catalog.pg_language l " + "\nORDER BY l.oid" ); } @Override protected PostgreLanguage fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreLanguage(owner, dbResult); } } class ForeignDataWrapperCache extends JDBCObjectCache<PostgreDatabase, PostgreForeignDataWrapper> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT l.oid,l.*,p.pronamespace as handler_schema_id " + "\nFROM pg_catalog.pg_foreign_data_wrapper l" + "\nLEFT OUTER JOIN pg_catalog.pg_proc p ON p.oid=l.fdwhandler " + "\nORDER BY l.fdwname" ); } @Override protected PostgreForeignDataWrapper fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreForeignDataWrapper(owner, dbResult); } } class ForeignServerCache extends JDBCObjectCache<PostgreDatabase, PostgreForeignServer> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT l.oid,l.* FROM pg_catalog.pg_foreign_server l" + "\nORDER BY l.srvname" ); } @Override protected PostgreForeignServer fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreForeignServer(owner, dbResult); } } class TablespaceCache extends JDBCObjectCache<PostgreDatabase, PostgreTablespace> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase owner) throws SQLException { return session.prepareStatement( "SELECT t.oid,t.* FROM pg_catalog.pg_tablespace t " + "\nORDER BY t.oid" ); } @Override protected PostgreTablespace fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { return new PostgreTablespace(owner, dbResult); } } static class SchemaCache extends JDBCObjectLookupCache<PostgreDatabase, PostgreSchema> { @NotNull @Override public JDBCStatement prepareLookupStatement(@NotNull JDBCSession session, @NotNull PostgreDatabase database, @Nullable PostgreSchema object, @Nullable String objectName) throws SQLException { /* // Do not apply filters // We need all schemas to have access to types return session.prepareStatement( "SELECT n.oid,n.* FROM pg_catalog.pg_namespace n ORDER BY nspname"); */ StringBuilder catalogQuery = new StringBuilder("SELECT n.oid,n.*,d.description FROM pg_catalog.pg_namespace n\n" + "LEFT OUTER JOIN pg_catalog.pg_description d ON d.objoid=n.oid\n"); DBSObjectFilter catalogFilters = database.getDataSource().getContainer().getObjectFilter(PostgreSchema.class, null, false); if ((catalogFilters != null && !catalogFilters.isNotApplicable()) || object != null || objectName != null) { if (object != null || objectName != null) { catalogFilters = new DBSObjectFilter(); catalogFilters.addInclude(object != null ? object.getName() : objectName); } else { catalogFilters = new DBSObjectFilter(catalogFilters); // Always read catalog schema catalogFilters.addInclude(PostgreConstants.CATALOG_SCHEMA_NAME); } JDBCUtils.appendFilterClause(catalogQuery, catalogFilters, "nspname", true); } catalogQuery.append(" ORDER BY nspname"); JDBCPreparedStatement dbStat = session.prepareStatement(catalogQuery.toString()); if (catalogFilters != null) { JDBCUtils.setFilterParameters(dbStat, 1, catalogFilters); } return dbStat; } @Override protected PostgreSchema fetchObject(@NotNull JDBCSession session, @NotNull PostgreDatabase owner, @NotNull JDBCResultSet resultSet) throws SQLException, DBException { String name = JDBCUtils.safeGetString(resultSet, "nspname"); if (name == null) { return null; } if (PostgreSchema.isUtilitySchema(name) && !owner.getDataSource().getContainer().isShowUtilityObjects()) { return null; } return new PostgreSchema(owner, name, resultSet); } } }