/* * 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.oracle.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.oracle.model.source.OracleStatefulObject; import org.jkiss.dbeaver.model.DBPEvaluationContext; import org.jkiss.dbeaver.model.DBPNamedObject2; import org.jkiss.dbeaver.model.DBPRefreshableObject; import org.jkiss.dbeaver.model.DBUtils; 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.DBObjectNameCaseTransformer; 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.JDBCStructCache; import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCTable; import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCTableColumn; import org.jkiss.dbeaver.model.meta.Association; import org.jkiss.dbeaver.model.meta.IPropertyCacheValidator; import org.jkiss.dbeaver.model.meta.LazyProperty; import org.jkiss.dbeaver.model.meta.Property; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectState; import org.jkiss.dbeaver.model.struct.rdb.DBSTableForeignKey; import org.jkiss.dbeaver.model.struct.rdb.DBSTableIndex; import org.jkiss.utils.CommonUtils; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; /** * OracleTable base */ public abstract class OracleTableBase extends JDBCTable<OracleDataSource, OracleSchema> implements DBPNamedObject2, DBPRefreshableObject, OracleStatefulObject { private static final Log log = Log.getLog(OracleTableBase.class); public static class TableAdditionalInfo { volatile boolean loaded = false; boolean isLoaded() { return loaded; } } public static class AdditionalInfoValidator implements IPropertyCacheValidator<OracleTableBase> { @Override public boolean isPropertyCached(OracleTableBase object, Object propertyId) { return object.getAdditionalInfo().isLoaded(); } } public static class CommentsValidator implements IPropertyCacheValidator<OracleTableBase> { @Override public boolean isPropertyCached(OracleTableBase object, Object propertyId) { return object.comment != null; } } public final TriggerCache triggerCache = new TriggerCache(); private final TablePrivCache tablePrivCache = new TablePrivCache(); public abstract TableAdditionalInfo getAdditionalInfo(); protected abstract String getTableTypeName(); protected boolean valid; private String comment; protected OracleTableBase(OracleSchema schema, String name, boolean persisted) { super(schema, name, persisted); } protected OracleTableBase(OracleSchema oracleSchema, ResultSet dbResult) { super(oracleSchema, true); setName(JDBCUtils.safeGetString(dbResult, "TABLE_NAME")); this.valid = "VALID".equals(JDBCUtils.safeGetString(dbResult, "STATUS")); //this.comment = JDBCUtils.safeGetString(dbResult, "COMMENTS"); } @Override public JDBCStructCache<OracleSchema, ? extends JDBCTable, ? extends JDBCTableColumn> getCache() { return getContainer().tableCache; } @Override @NotNull public OracleSchema getSchema() { return super.getContainer(); } @NotNull @Override @Property(viewable = true, editable = true, valueTransformer = DBObjectNameCaseTransformer.class, order = 1) public String getName() { return super.getName(); } @Nullable @Override public String getDescription() { return getComment(); } @NotNull @Override public String getFullyQualifiedName(DBPEvaluationContext context) { return DBUtils.getFullQualifiedName(getDataSource(), getContainer(), this); } @Property(viewable = true, editable = true, updatable = true, order = 100) @LazyProperty(cacheValidator = CommentsValidator.class) public synchronized String getComment(DBRProgressMonitor monitor) throws DBException { if (comment == null) { try (JDBCSession session = DBUtils.openMetaSession(monitor, getDataSource(), "Load table comments")) { comment = JDBCUtils.queryString( session, "SELECT COMMENTS FROM ALL_TAB_COMMENTS WHERE OWNER=? AND TABLE_NAME=? AND TABLE_TYPE=?", getSchema().getName(), getName(), getTableTypeName()); if (comment == null) { comment = ""; } } catch (SQLException e) { log.warn("Can't fetch table '" + getName() + "' comment", e); } } return comment; } void loadColumnComments(DBRProgressMonitor monitor) { try { try (JDBCSession session = DBUtils.openMetaSession(monitor, getDataSource(), "Load table column comments")) { try (JDBCPreparedStatement stat = session.prepareStatement("SELECT COLUMN_NAME,COMMENTS FROM SYS.ALL_COL_COMMENTS cc WHERE CC.OWNER=? AND cc.TABLE_NAME=?")) { stat.setString(1, getSchema().getName()); stat.setString(2, getName()); try (JDBCResultSet resultSet = stat.executeQuery()) { while (resultSet.next()) { String colName = resultSet.getString(1); String colComment = resultSet.getString(2); OracleTableColumn col = getAttribute(monitor, colName); if (col == null) { log.warn("Column '" + colName + "' not found in table '" + getFullyQualifiedName(DBPEvaluationContext.DDL) + "'"); } else { col.setComment(CommonUtils.notEmpty(colComment)); } } } } } for (OracleTableColumn col : getAttributes(monitor)) { col.cacheComment(); } } catch (Exception e) { log.warn("Error fetching table '" + getName() + "' column comments", e); } } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } @Override public Collection<OracleTableColumn> getAttributes(@NotNull DBRProgressMonitor monitor) throws DBException { return getContainer().tableCache.getChildren(monitor, getContainer(), this); } @Override public OracleTableColumn getAttribute(@NotNull DBRProgressMonitor monitor, @NotNull String attributeName) throws DBException { return getContainer().tableCache.getChild(monitor, getContainer(), this, attributeName); } @Override public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException { getContainer().constraintCache.clearObjectCache(this); return getContainer().tableCache.refreshObject(monitor, getContainer(), this); } @Association public Collection<OracleTableTrigger> getTriggers(DBRProgressMonitor monitor) throws DBException { return triggerCache.getAllObjects(monitor, this); } @Override public Collection<? extends DBSTableIndex> getIndexes(DBRProgressMonitor monitor) throws DBException { return null; } @Nullable @Override @Association public synchronized Collection<OracleTableConstraint> getConstraints(@NotNull DBRProgressMonitor monitor) throws DBException { return getContainer().constraintCache.getObjects(monitor, getContainer(), this); } public OracleTableConstraint getConstraint(DBRProgressMonitor monitor, String ukName) throws DBException { return getContainer().constraintCache.getObject(monitor, getContainer(), this, ukName); } public DBSTableForeignKey getForeignKey(DBRProgressMonitor monitor, String ukName) throws DBException { return DBUtils.findObject(getAssociations(monitor), ukName); } @Override public Collection<OracleTableForeignKey> getAssociations(@NotNull DBRProgressMonitor monitor) throws DBException { return null; } @Override public Collection<OracleTableForeignKey> getReferences(@NotNull DBRProgressMonitor monitor) throws DBException { return null; } public String getDDL(DBRProgressMonitor monitor, OracleDDLFormat ddlFormat) throws DBException { return OracleUtils.getDDL(monitor, getTableTypeName(), this, ddlFormat); } @NotNull @Override public DBSObjectState getObjectState() { return valid ? DBSObjectState.NORMAL : DBSObjectState.INVALID; } public static OracleTableBase findTable(DBRProgressMonitor monitor, OracleDataSource dataSource, String ownerName, String tableName) throws DBException { OracleSchema refSchema = dataSource.getSchema(monitor, ownerName); if (refSchema == null) { log.warn("Referenced schema '" + ownerName + "' not found"); return null; } else { OracleTableBase refTable = refSchema.tableCache.getObject(monitor, refSchema, tableName); if (refTable == null) { log.warn("Referenced table '" + tableName + "' not found in schema '" + ownerName + "'"); } return refTable; } } @Association public Collection<OraclePrivTable> getTablePrivs(DBRProgressMonitor monitor) throws DBException { return tablePrivCache.getAllObjects(monitor, this); } static class TriggerCache extends JDBCStructCache<OracleTableBase, OracleTableTrigger, OracleTriggerColumn> { TriggerCache() { super("TRIGGER_NAME"); } @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull OracleTableBase owner) throws SQLException { JDBCPreparedStatement dbStat = session.prepareStatement( "SELECT *\n" + "FROM " + OracleUtils.getAdminAllViewPrefix(owner.getDataSource()) + "TRIGGERS WHERE TABLE_OWNER=? AND TABLE_NAME=?\n" + "ORDER BY TRIGGER_NAME"); dbStat.setString(1, owner.getSchema().getName()); dbStat.setString(2, owner.getName()); return dbStat; } @Override protected OracleTableTrigger fetchObject(@NotNull JDBCSession session, @NotNull OracleTableBase owner, @NotNull JDBCResultSet resultSet) throws SQLException, DBException { return new OracleTableTrigger(owner, resultSet); } @Override protected JDBCStatement prepareChildrenStatement(@NotNull JDBCSession session, @NotNull OracleTableBase owner, @Nullable OracleTableTrigger forObject) throws SQLException { JDBCPreparedStatement dbStat = session.prepareStatement( "SELECT TRIGGER_NAME,TABLE_OWNER,TABLE_NAME,COLUMN_NAME,COLUMN_LIST,COLUMN_USAGE\n" + "FROM SYS.ALL_TRIGGER_COLS WHERE TRIGGER_OWNER=?" + (forObject == null ? "" : " AND TRIGGER_NAME=?") + "\nORDER BY TRIGGER_NAME"); dbStat.setString(1, owner.getName()); if (forObject != null) { dbStat.setString(2, forObject.getName()); } return dbStat; } @Override protected OracleTriggerColumn fetchChild(@NotNull JDBCSession session, @NotNull OracleTableBase owner, @NotNull OracleTableTrigger parent, @NotNull JDBCResultSet dbResult) throws SQLException, DBException { OracleTableBase refTable = OracleTableBase.findTable( session.getProgressMonitor(), owner.getDataSource(), JDBCUtils.safeGetString(dbResult, "TABLE_OWNER"), JDBCUtils.safeGetString(dbResult, "TABLE_NAME")); if (refTable != null) { final String columnName = JDBCUtils.safeGetString(dbResult, "COLUMN_NAME"); OracleTableColumn tableColumn = refTable.getAttribute(session.getProgressMonitor(), columnName); if (tableColumn == null) { log.debug("Column '" + columnName + "' not found in table '" + refTable.getFullyQualifiedName(DBPEvaluationContext.DDL) + "' for trigger '" + parent.getName() + "'"); } return new OracleTriggerColumn(session.getProgressMonitor(), parent, tableColumn, dbResult); } return null; } } static class TablePrivCache extends JDBCObjectCache<OracleTableBase, OraclePrivTable> { @Override protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull OracleTableBase tableBase) throws SQLException { final JDBCPreparedStatement dbStat = session.prepareStatement( "SELECT p.*\n" + "FROM DBA_TAB_PRIVS p\n" + "WHERE p.OWNER=? AND p.TABLE_NAME =?"); dbStat.setString(1, tableBase.getSchema().getName()); dbStat.setString(2, tableBase.getName()); return dbStat; } @Override protected OraclePrivTable fetchObject(@NotNull JDBCSession session, @NotNull OracleTableBase tableBase, @NotNull JDBCResultSet resultSet) throws SQLException, DBException { return new OraclePrivTable(tableBase, resultSet); } } }