/* * DBeaver - Universal Database Manager * Copyright (C) 2016 Karl Griesser (fullref@gmail.com) * 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.exasol.model; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.Platform; 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.exasol.ExasolConstants; import org.jkiss.dbeaver.ext.exasol.ExasolDataSourceProvider; import org.jkiss.dbeaver.ext.exasol.ExasolSQLDialect; import org.jkiss.dbeaver.ext.exasol.manager.security.*; import org.jkiss.dbeaver.ext.exasol.model.plan.ExasolPlanAnalyser; import org.jkiss.dbeaver.model.DBPDataSourceContainer; import org.jkiss.dbeaver.model.DBPDataSourceInfo; import org.jkiss.dbeaver.model.DBPErrorAssistant; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; 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.JDBCDatabaseMetaData; import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; import org.jkiss.dbeaver.model.exec.plan.DBCPlan; import org.jkiss.dbeaver.model.exec.plan.DBCPlanStyle; import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner; import org.jkiss.dbeaver.model.impl.DBSObjectCache; import org.jkiss.dbeaver.model.impl.jdbc.JDBCDataSource; import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext; import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectSimpleCache; import org.jkiss.dbeaver.model.meta.Association; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSDataType; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectSelector; import org.jkiss.dbeaver.model.struct.DBSStructureAssistant; import org.jkiss.utils.CommonUtils; import java.sql.SQLException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ExasolDataSource extends JDBCDataSource implements DBSObjectSelector, DBCQueryPlanner, IAdaptable { private static final Log LOG = Log.getLog(ExasolDataSource.class); private static final String GET_CURRENT_SCHEMA = "SELECT CURRENT_SCHEMA"; private static final String SET_CURRENT_SCHEMA = "OPEN SCHEMA \"%s\""; private DBSObjectCache<ExasolDataSource, ExasolSchema> schemaCache; private DBSObjectCache<ExasolDataSource, ExasolVirtualSchema> virtualSchemaCache; private ExasolCurrentUserPrivileges exasolCurrentUserPrivileges; private DBSObjectCache<ExasolDataSource, ExasolUser> userCache = null; private DBSObjectCache<ExasolDataSource, ExasolRole> roleCache = null; private DBSObjectCache<ExasolDataSource, ExasolConnection> connectionCache = null; private DBSObjectCache<ExasolDataSource, ExasolDataType> dataTypeCache = new JDBCObjectSimpleCache<>( ExasolDataType.class, "SELECT * FROM EXA_SQL_TYPES"); private DBSObjectCache<ExasolDataSource, ExasolRoleGrant> roleGrantCache = null; private DBSObjectCache<ExasolDataSource, ExasolSystemGrant> systemGrantCache = null; private DBSObjectCache<ExasolDataSource, ExasolConnectionGrant> connectionGrantCache = null; private DBSObjectCache<ExasolDataSource, ExasolBaseObjectGrant> baseTableGrantCache = null; private int driverMajorVersion = 5; private String activeSchemaName; // ----------------------- // Constructors // ----------------------- public ExasolDataSource(DBRProgressMonitor monitor, DBPDataSourceContainer container) throws DBException { super(monitor, container, new ExasolSQLDialect()); } // ----------------------- // Initialization/Structure // ----------------------- @Override public void initialize(@NotNull DBRProgressMonitor monitor) throws DBException { super.initialize(monitor); try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Load data source meta info")) { // First try to get active schema from special register 'CURRENT // SCHEMA' this.activeSchemaName = determineActiveSchema(session); this.exasolCurrentUserPrivileges = new ExasolCurrentUserPrivileges( monitor, session, this); this.driverMajorVersion = session.getMetaData().getDriverMajorVersion(); } catch (SQLException e) { LOG.warn("Error reading active schema", e); } String schemaSQL = "select b.object_name,b.owner,b.created,b.object_comment from EXA_ALL_OBJECTS b " + "inner join EXA_SCHEMAS s on b.object_name = s.schema_name where b.object_type = 'SCHEMA' "; if (exasolCurrentUserPrivileges.getatLeastV6()) { //additional where clause to filter virtual schemas schemaSQL += " and not schema_is_virtual "; //build virtual schema cache for >V6 databases virtualSchemaCache = new JDBCObjectSimpleCache<>( ExasolVirtualSchema.class, "select" + " SCHEMA_NAME as OBJECT_NAME," + " SCHEMA_OWNER AS OWNER," + " ADAPTER_SCRIPT," + " LAST_REFRESH," + " LAST_REFRESH_BY," + " ADAPTER_NOTES," + " OBJECT_COMMENT," + " CREATED" + " from" + " EXA_VIRTUAL_SCHEMAS s" + " INNER JOIN" + " EXA_ALL_OBJECTS o" + " ON" + " o.OBJECT_NAME = s.SCHEMA_NAME AND" + " o.OBJECT_TYPE = 'SCHEMA'" ); } schemaSQL += " union all select distinct SCHEMA_NAME as \"OBJECT_NAME\", 'SYS' as owner, cast(null as timestamp) as created, '' as \"OBJECT_COMMENT\" from SYS.EXA_SYSCAT " + "order by b.object_name"; schemaCache = new JDBCObjectSimpleCache<>( ExasolSchema.class, schemaSQL); try { this.dataTypeCache.getAllObjects(monitor, this); } catch (DBException e) { LOG.warn("Error reading types info", e); this.dataTypeCache .setCache(Collections.<ExasolDataType> emptyList()); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForUsers()) { this.userCache = new JDBCObjectSimpleCache<>(ExasolUser.class, "select * from EXA_DBA_USERS ORDER BY USER_NAME"); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForRoles()) { this.roleCache = new JDBCObjectSimpleCache<>(ExasolRole.class, "SELECT * FROM EXA_DBA_ROLES ORDER BY ROLE_NAME"); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForConnections()) { this.connectionCache = new JDBCObjectSimpleCache<>( ExasolConnection.class, "SELECT * FROM EXA_DBA_CONNECTIONS ORDER BY CONNECTION_NAME"); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForConnectionPrivs()) { this.connectionGrantCache = new JDBCObjectSimpleCache<>( ExasolConnectionGrant.class,"SELECT c.*,P.ADMIN_OPTION,P.GRANTEE FROM SYS.EXA_DBA_CONNECTION_PRIVS P " + "INNER JOIN SYS.EXA_DBA_CONNECTIONS C on P.GRANTED_CONNECTION = C.CONNECTION_NAME ORDER BY P.GRANTEE,C.CONNECTION_NAME "); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForObjectPrivs()) { this.baseTableGrantCache = new JDBCObjectSimpleCache<>( ExasolBaseObjectGrant.class,"SELECT " + " OBJECT_SCHEMA," + " OBJECT_TYPE," + " GRANTEE," + " OBJECT_NAME," + " GROUP_CONCAT(" + " DISTINCT PRIVILEGE" + " ORDER BY" + " OBJECT_SCHEMA," + " OBJECT_NAME" + " SEPARATOR '|'" + " ) as PRIVS " + " FROM" + " SYS.EXA_DBA_OBJ_PRIVS P" + " GROUP BY" + " OBJECT_SCHEMA," + " OBJECT_TYPE," + " GRANTEE," + " OBJECT_NAME ORDER BY GRANTEE,OBJECT_SCHEMA,OBJECT_TYPE,OBJECT_NAME"); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForSystemPrivs()) { this.systemGrantCache = new JDBCObjectSimpleCache<>( ExasolSystemGrant.class, "SELECT GRANTEE,PRIVILEGE,ADMIN_OPTION FROM SYS.EXA_DBA_SYS_PRIVS ORDER BY GRANTEE,PRIVILEGE"); } if (exasolCurrentUserPrivileges.getUserIsAuthorizedForRolePrivs()) { this.roleGrantCache = new JDBCObjectSimpleCache<>( ExasolRoleGrant.class, "select r.*,p.ADMIN_OPTION,p.GRANTEE from EXA_DBA_ROLES r " + "INNER JOIN EXA_DBA_ROLE_PRIVS p ON p.GRANTED_ROLE = r.ROLE_NAME ORDER BY P.GRANTEE,R.ROLE_NAME" ); } } private Pattern ERROR_POSITION_PATTERN = Pattern.compile("(.+)\\[line ([0-9]+), column ([0-9]+)\\]"); int getDriverMajorVersion() { return this.driverMajorVersion; } @Nullable @Override public ErrorPosition[] getErrorPosition(@NotNull DBRProgressMonitor monitor, @NotNull DBCExecutionContext context, @NotNull String query, @NotNull Throwable error) { while (error instanceof DBException) { if (error.getCause() == null) { return null; } error = error.getCause(); } String message = error.getMessage(); if (!CommonUtils.isEmpty(message)) { Matcher matcher = ERROR_POSITION_PATTERN.matcher(message); List<ErrorPosition> positions = new ArrayList<>(); while (matcher.find()) { DBPErrorAssistant.ErrorPosition pos = new DBPErrorAssistant.ErrorPosition(); pos.info = matcher.group(1); pos.line = Integer.parseInt(matcher.group(2)) - 1; pos.position = Integer.parseInt(matcher.group(3)) - 1; positions.add(pos); } if (!positions.isEmpty()) { return positions.toArray(new ErrorPosition[positions.size()]); } } return null; } protected void initializeContextState(@NotNull DBRProgressMonitor monitor, @NotNull JDBCExecutionContext context, boolean setActiveObject) throws DBCException { if (setActiveObject) { setCurrentSchema(monitor, context, getDefaultObject()); } } private String determineActiveSchema(JDBCSession session) throws SQLException { // First try to get active schema from special register 'CURRENT SCHEMA' String defSchema = JDBCUtils.queryString(session, GET_CURRENT_SCHEMA); if (defSchema == null) { return ""; } return defSchema.trim(); } @Override public <T> T getAdapter(Class<T> adapter) { if (adapter == DBSStructureAssistant.class) { return adapter.cast(new ExasolStructureAssistant(this)); } return super.getAdapter(adapter); } @Override public void cacheStructure(@NotNull DBRProgressMonitor monitor, int scope) throws DBException { // TODO DF: No idea what to do with this method, what it is used for... } // ----------------------- // Connection related Info // ----------------------- @Override protected String getConnectionUserName( @NotNull DBPConnectionConfiguration connectionInfo) { return connectionInfo.getUserName(); } @NotNull @Override public ExasolDataSource getDataSource() { return this; } @Override protected DBPDataSourceInfo createDataSourceInfo( @NotNull JDBCDatabaseMetaData metaData) { final ExasolDataSourceInfo info = new ExasolDataSourceInfo(metaData); info.setSupportsResultSetScroll(false); return info; } @Override protected Map<String, String> getInternalConnectionProperties( DBRProgressMonitor monitor, String purpose) throws DBCException { Map<String, String> props = new HashMap<>(); props.putAll(ExasolDataSourceProvider.getConnectionsProps()); return props; } @Override public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException { super.refreshObject(monitor); this.schemaCache.clearCache(); if (this.userCache != null) this.userCache.clearCache(); this.dataTypeCache.clearCache(); if (this.roleCache != null) this.roleCache.clearCache(); if (this.connectionCache != null) this.connectionCache.clearCache(); //caches for security if (this.connectionGrantCache != null) this.connectionGrantCache.clearCache(); if (this.baseTableGrantCache != null) this.baseTableGrantCache.clearCache(); if (this.systemGrantCache != null) this.systemGrantCache.clearCache(); if (this.roleCache != null) this.roleCache.clearCache(); this.initialize(monitor); return this; } // -------------------------- // Manage Children: ExasolSchema // -------------------------- @Override public boolean supportsDefaultChange() { return true; } @Override public Class<? extends ExasolSchema> getChildType( @NotNull DBRProgressMonitor monitor) throws DBException { return ExasolSchema.class; } @Override public Collection<ExasolSchema> getChildren( @NotNull DBRProgressMonitor monitor) throws DBException { Collection<ExasolSchema> totalList = getSchemas(monitor); if (exasolCurrentUserPrivileges.getatLeastV6()) totalList.addAll(getVirtualSchemas(monitor)); return totalList; } @Override public ExasolSchema getChild(@NotNull DBRProgressMonitor monitor, @NotNull String childName) throws DBException { if (exasolCurrentUserPrivileges.getatLeastV6()) return getSchema(monitor, childName) != null ? getSchema(monitor,childName) : getVirtualSchema(monitor, childName); return getSchema(monitor, childName); } @Override public ExasolSchema getDefaultObject() { return activeSchemaName == null ? null : schemaCache.getCachedObject(activeSchemaName); } @Override public void setDefaultObject(@NotNull DBRProgressMonitor monitor, @NotNull DBSObject object) throws DBException { final ExasolSchema oldSelectedEntity = getDefaultObject(); if (!(object instanceof ExasolSchema)) { throw new IllegalArgumentException( "Invalid object type: " + object); } for (JDBCExecutionContext context : getAllContexts()) { setCurrentSchema(monitor, context, (ExasolSchema) object); } activeSchemaName = object.getName(); // Send notifications if (oldSelectedEntity != null) { DBUtils.fireObjectSelect(oldSelectedEntity, false); } if (this.activeSchemaName != null) { DBUtils.fireObjectSelect(object, true); } } @Override public boolean refreshDefaultObject(@NotNull DBCSession session) throws DBException { try { final String newSchemaName = determineActiveSchema( (JDBCSession) session); if (!CommonUtils.equalObjects(newSchemaName, activeSchemaName)) { final ExasolSchema newSchema = schemaCache .getCachedObject(newSchemaName); if (newSchema != null) { setDefaultObject(session.getProgressMonitor(), newSchema); return true; } } return false; } catch (SQLException e) { throw new DBException(e, this); } } private void setCurrentSchema(DBRProgressMonitor monitor, JDBCExecutionContext executionContext, ExasolSchema object) throws DBCException { if (object == null) { LOG.debug("Null current schema"); return; } try (JDBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.UTIL, "Set active schema")) { JDBCUtils.executeSQL(session, String.format(SET_CURRENT_SCHEMA, object.getName())); } catch (SQLException e) { throw new DBCException(e, this); } } // -------------- // Associations // -------------- @Association public Collection<ExasolSchema> getSchemas(DBRProgressMonitor monitor) throws DBException { return schemaCache.getAllObjects(monitor, this); } public ExasolSchema getSchema(DBRProgressMonitor monitor, String name) throws DBException { return schemaCache.getObject(monitor, this, name); } @Association public Collection<ExasolVirtualSchema> getVirtualSchemas(DBRProgressMonitor monitor) throws DBException { return virtualSchemaCache.getAllObjects(monitor, this); } public ExasolVirtualSchema getVirtualSchema(DBRProgressMonitor monitor, String name) throws DBException { return virtualSchemaCache.getObject(monitor, this, name); } @Association public Collection<ExasolUser> getUsers(DBRProgressMonitor monitor) throws DBException { return userCache.getAllObjects(monitor, this); } public ExasolUser getUser(DBRProgressMonitor monitor, String name) throws DBException { return userCache.getObject(monitor, this, name); } @Association public Collection<ExasolRole> getRoles(DBRProgressMonitor monitor) throws DBException { return roleCache.getAllObjects(monitor, this); } public ExasolRole getRole(DBRProgressMonitor monitor, String name) throws DBException { return roleCache.getObject(monitor, this, name); } @Association public Collection<ExasolConnection> getConnections( DBRProgressMonitor monitor) throws DBException { return connectionCache.getAllObjects(monitor, this); } public ExasolConnection getConnection(DBRProgressMonitor monitor, String name) throws DBException { return connectionCache.getObject(monitor, this, name); } @Association public Collection<ExasolBaseObjectGrant> getBaseTableGrants(DBRProgressMonitor monitor) throws DBException { return baseTableGrantCache.getAllObjects(monitor, this); } public Collection<ExasolTableGrant> getTableGrants(DBRProgressMonitor monitor) throws DBException { Collection<ExasolTableGrant> grants = new ArrayList<>(); for (ExasolBaseObjectGrant grant: this.getBaseTableGrants(monitor)) { //only add tables if (grant.getType() == ExasolTableObjectType.TABLE) grants.add(new ExasolTableGrant(monitor, grant)); } return grants; } public Collection<ExasolViewGrant> getViewGrants(DBRProgressMonitor monitor) throws DBException { Collection<ExasolViewGrant> grants = new ArrayList<>(); for (ExasolBaseObjectGrant grant: this.getBaseTableGrants(monitor)) { //only add tables if (grant.getType() == ExasolTableObjectType.VIEW) grants.add(new ExasolViewGrant(grant)); } return grants; } public Collection<ExasolScriptGrant> getScriptGrants(DBRProgressMonitor monitor) throws DBException { Collection<ExasolScriptGrant> grants = new ArrayList<>(); for (ExasolBaseObjectGrant grant: this.getBaseTableGrants(monitor)) { //only add tables if (grant.getType() == ExasolTableObjectType.SCRIPT) grants.add(new ExasolScriptGrant(grant)); } return grants; } public Collection<ExasolSchemaGrant> getSchemaGrants(DBRProgressMonitor monitor) throws DBException { Collection<ExasolSchemaGrant> grants = new ArrayList<>(); for (ExasolBaseObjectGrant grant: this.getBaseTableGrants(monitor)) { //only add tables if (grant.getType() == ExasolTableObjectType.SCHEMA) grants.add(new ExasolSchemaGrant(grant)); } return grants; } public Collection<ExasolConnectionGrant> getConnectionGrants(DBRProgressMonitor monitor) throws DBException { return connectionGrantCache.getAllObjects(monitor, this); } public Collection<ExasolSystemGrant> getSystemGrants(DBRProgressMonitor monitor) throws DBException { return this.systemGrantCache.getAllObjects(monitor, this); } public Collection<ExasolRoleGrant> getRoleGrants(DBRProgressMonitor monitor) throws DBException { return this.roleGrantCache.getAllObjects(monitor, this); } @Association public Collection<ExasolDataType> getDataTypes(DBRProgressMonitor monitor) throws DBException { return dataTypeCache.getAllObjects(monitor, this); } public ExasolDataType getDataType(DBRProgressMonitor monitor, String name) throws DBException { return dataTypeCache.getObject(monitor, this, name); } // ----------------------- // Authorities // ----------------------- public boolean isAuthorizedForUsers() { return this.exasolCurrentUserPrivileges.getUserIsAuthorizedForUsers(); } public boolean isAuthorizedForConnections() { return this.exasolCurrentUserPrivileges .getUserIsAuthorizedForConnections(); } public boolean isAuthorizedForRoles() { return this.exasolCurrentUserPrivileges.getUserIsAuthorizedForRoles(); } public boolean isAuthorizedForRolePrivs() { return this.exasolCurrentUserPrivileges.getUserIsAuthorizedForRolePrivs(); } public boolean isatLeastV6() { return this.exasolCurrentUserPrivileges.getatLeastV6(); } public boolean isatLeastV5() { return this.exasolCurrentUserPrivileges.getatLeastV5(); } public boolean isAuthorizedForConnectionPrivs() { return this.exasolCurrentUserPrivileges.getUserIsAuthorizedForConnectionPrivs(); } public boolean isAuthorizedForObjectPrivs() { return this.exasolCurrentUserPrivileges.getUserIsAuthorizedForObjectPrivs(); } // ------------------------- // Standards Getters // ------------------------- public DBSObjectCache<ExasolDataSource, ExasolUser> getUserCache() { return userCache; } public DBSObjectCache<ExasolDataSource, ExasolSchema> getSchemaCache() { return schemaCache; } @Override public Collection<? extends DBSDataType> getLocalDataTypes() { try { return getDataTypes(new VoidProgressMonitor()); } catch (DBException e) { LOG.error("DBException occured when reading system dataTypes: ", e); return null; } } @Override public String getConnectionURL(DBPConnectionConfiguration connectionInfo) { //Default Port String port = ":8563"; if (!CommonUtils.isEmpty(connectionInfo.getHostPort())) { port = ":" + connectionInfo.getHostPort(); } Map<String, String> properties = connectionInfo.getProperties(); StringBuilder url = new StringBuilder(128); url.append("jdbc:exa:").append(connectionInfo.getHostName()).append(port); //check if we got an backup host list String backupHostList = connectionInfo.getProviderProperty(ExasolConstants.DRV_BACKUP_HOST_LIST); if (!CommonUtils.isEmpty(backupHostList)) url.append(",").append(backupHostList).append(port); if (!url.toString().toUpperCase().contains("CLIENTNAME")) { // Client info can only be provided in the url with the exasol driver String clientName = Platform.getProduct().getName(); Object propClientName = properties.get(ExasolConstants.DRV_CLIENT_NAME); if (propClientName != null) clientName = propClientName.toString(); url.append(";clientname=").append(clientName); } if (!url.toString().toUpperCase().contains("CLIENTVERSION")) { String clientVersion=Platform.getProduct().getDefiningBundle().getVersion().toString(); Object propClientName = properties.get(ExasolConstants.DRV_CLIENT_VERSION); if (propClientName != null) clientVersion = propClientName.toString(); url.append(";clientversion=").append(clientVersion); } Object querytimeout = properties.get(ExasolConstants.DRV_QUERYTIMEOUT); if (querytimeout != null) url.append(";").append(ExasolConstants.DRV_QUERYTIMEOUT).append("=").append(querytimeout); Object connecttimeout = properties.get(ExasolConstants.DRV_CONNECT_TIMEOUT); if (connecttimeout != null) url.append(";").append(ExasolConstants.DRV_CONNECT_TIMEOUT).append("=").append(connecttimeout); return url.toString(); } @Override public DBSDataType getLocalDataType(String typeName) { try { return getDataType(new VoidProgressMonitor(), typeName); } catch (DBException e) { LOG.error("DBException occured when reading system dataType: " + typeName, e); return null; } } @NotNull @Override public DBCPlan planQueryExecution(@NotNull DBCSession session, @NotNull String query) throws DBCException { ExasolPlanAnalyser plan = new ExasolPlanAnalyser(this, query); plan.explain(session); return plan; } @NotNull @Override public DBCPlanStyle getPlanStyle() { return DBCPlanStyle.PLAN; } DBSObjectCache<ExasolDataSource, ExasolDataType> getDataTypeCache() { return dataTypeCache; } }