/*
* 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.ui.controls.resultset;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Display;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.DBeaverPreferences;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.impl.DBObjectNameCaseTransformer;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLQuery;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.model.struct.rdb.DBSTable;
import org.jkiss.dbeaver.model.struct.rdb.DBSTableIndex;
import org.jkiss.dbeaver.model.virtual.DBVEntity;
import org.jkiss.dbeaver.model.virtual.DBVEntityConstraint;
import org.jkiss.dbeaver.model.virtual.DBVUtils;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.utils.CommonUtils;
import java.util.*;
/**
* Utils
*/
public class ResultSetUtils
{
private static final Log log = Log.getLog(ResultSetUtils.class);
private static volatile IDialogSettings viewerSettings;
@NotNull
public static IDialogSettings getViewerSettings(String section) {
if (viewerSettings == null) {
viewerSettings = UIUtils.getDialogSettings(ResultSetViewer.class.getSimpleName());
}
return UIUtils.getSettingsSection(viewerSettings, section);
}
public static void bindAttributes(
DBCSession session,
DBCResultSet resultSet,
DBDAttributeBindingMeta[] bindings,
List<Object[]> rows) throws DBException {
final DBRProgressMonitor monitor = session.getProgressMonitor();
final DBPDataSource dataSource = session.getDataSource();
boolean readMetaData = dataSource.getContainer().getPreferenceStore().getBoolean(DBeaverPreferences.RESULT_SET_READ_METADATA);
if (!readMetaData) {
return;
}
boolean readReferences = dataSource.getContainer().getPreferenceStore().getBoolean(DBeaverPreferences.RESULT_SET_READ_REFERENCES);
final Map<DBCEntityMetaData, DBSEntity> entityBindingMap = new IdentityHashMap<>();
monitor.beginTask("Discover resultset metadata", 3);
try {
SQLQuery sqlQuery = null;
DBSEntity entity = null;
DBCStatement sourceStatement = resultSet.getSourceStatement();
if (sourceStatement != null && sourceStatement.getStatementSource() != null) {
DBCExecutionSource executionSource = sourceStatement.getStatementSource();
monitor.subTask("Discover owner entity");
DBSDataContainer dataContainer = executionSource.getDataContainer();
if (dataContainer instanceof DBSEntity) {
entity = (DBSEntity)dataContainer;
}
DBCEntityMetaData entityMeta = null;
if (entity == null) {
// Discover from entity metadata
Object sourceDescriptor = executionSource.getSourceDescriptor();
if (sourceDescriptor instanceof SQLQuery) {
sqlQuery = (SQLQuery) sourceDescriptor;
entityMeta = sqlQuery.getSingleSource();
}
if (entityMeta != null) {
entity = getEntityFromMetaData(monitor, dataSource, entityMeta);
if (entity != null) {
entityBindingMap.put(entityMeta, entity);
}
}
}
}
final Map<DBSEntity, DBDRowIdentifier> locatorMap = new IdentityHashMap<>();
monitor.subTask("Discover attributes");
for (DBDAttributeBindingMeta binding : bindings) {
monitor.subTask("Discover attribute '" + binding.getName() + "'");
DBCAttributeMetaData attrMeta = binding.getMetaAttribute();
// We got table name and column name
// To be editable we need this resultset contain set of columns from the same table
// which construct any unique key
DBSEntity attrEntity = null;
final DBCEntityMetaData attrEntityMeta = attrMeta.getEntityMetaData();
if (attrEntityMeta != null) {
attrEntity = entityBindingMap.get(attrEntityMeta);
if (attrEntity == null) {
if (entity != null && entity instanceof DBSTable && ((DBSTable) entity).isView()) {
// If this is a view then don't try to detect entity for each attribute
// MySQL returns source table name instead of view name. That's crazy.
attrEntity = entity;
} else {
attrEntity = getEntityFromMetaData(monitor, dataSource, attrEntityMeta);
}
}
if (attrEntity != null) {
entityBindingMap.put(attrEntityMeta, attrEntity);
}
}
if (attrEntity == null) {
attrEntity = entity;
}
if (attrEntity == null) {
if (attrEntityMeta != null) {
log.debug("Table '" + DBUtils.getSimpleQualifiedName(attrEntityMeta.getCatalogName(), attrEntityMeta.getSchemaName(), attrEntityMeta.getEntityName()) + "' not found in metadata catalog");
}
} else {
DBDPseudoAttribute pseudoAttribute = DBUtils.getPseudoAttribute(attrEntity, attrMeta.getName());
if (pseudoAttribute != null) {
binding.setPseudoAttribute(pseudoAttribute);
}
DBSEntityAttribute tableColumn;
if (binding.getPseudoAttribute() != null) {
tableColumn = binding.getPseudoAttribute().createFakeAttribute(attrEntity, attrMeta);
} else {
tableColumn = attrEntity.getAttribute(monitor, attrMeta.getName());
}
if (sqlQuery != null) {
if (tableColumn != null && tableColumn.getTypeID() != attrMeta.getTypeID()) {
// !! Do not try to use table column handlers for custom queries if source data type
// differs from table data type.
// Query may have expressions with the same alias as underlying table column
// and this expression may return very different data type. It breaks fetch completely.
// There should be a better solution but for now let's just disable this too smart feature.
continue;
}
/*
final SQLSelectItem selectItem = sqlQuery.getSelectItem(attrMeta.getName());
if (selectItem != null && !selectItem.isPlainColumn()) {
// It is not a column.
// It maybe an expression, function or anything else
continue;
}
*/
}
if (tableColumn != null && binding.setEntityAttribute(tableColumn)) {
// We have new type and new value handler.
// We have to fix already fetched values.
// E.g. we fetched strings and found out that we should handle them as LOBs or enums.
try {
int pos = attrMeta.getOrdinalPosition();
for (Object[] row : rows) {
row[pos] = binding.getValueHandler().getValueFromObject(session, tableColumn, row[pos], false);
}
} catch (DBCException e) {
log.warn("Error resolving attribute '" + binding.getName() + "' values", e);
}
}
}
}
monitor.worked(1);
// Init row identifiers
monitor.subTask("Detect unique identifiers");
for (DBDAttributeBindingMeta binding : bindings) {
//monitor.subTask("Find attribute '" + binding.getName() + "' identifier");
DBSEntityAttribute attr = binding.getEntityAttribute();
if (attr == null) {
continue;
}
DBSEntity attrEntity = attr.getParentObject();
if (attrEntity != null) {
DBDRowIdentifier rowIdentifier = locatorMap.get(attrEntity);
if (rowIdentifier == null) {
DBSEntityReferrer entityIdentifier = getBestIdentifier(monitor, attrEntity, bindings);
if (entityIdentifier != null) {
rowIdentifier = new DBDRowIdentifier(
attrEntity,
entityIdentifier);
locatorMap.put(attrEntity, rowIdentifier);
}
}
binding.setRowIdentifier(rowIdentifier);
}
}
monitor.worked(1);
if (readReferences) {
monitor.subTask("Late bindings");
// Read nested bindings
for (DBDAttributeBinding binding : bindings) {
binding.lateBinding(session, rows);
}
}
monitor.subTask("Complete metadata load");
// Reload attributes in row identifiers
for (DBDRowIdentifier rowIdentifier : locatorMap.values()) {
rowIdentifier.reloadAttributes(monitor, bindings);
}
}
finally {
monitor.done();
}
}
private static DBSEntity getEntityFromMetaData(DBRProgressMonitor monitor, DBPDataSource dataSource, DBCEntityMetaData entityMeta) throws DBException {
final DBSObjectContainer objectContainer = DBUtils.getAdapter(DBSObjectContainer.class, dataSource);
if (objectContainer != null) {
DBSEntity entity = getEntityFromMetaData(monitor, objectContainer, entityMeta, false);
if (entity == null) {
entity = getEntityFromMetaData(monitor, objectContainer, entityMeta, true);
}
return entity;
} else {
return null;
}
}
private static DBSEntity getEntityFromMetaData(DBRProgressMonitor monitor, DBSObjectContainer objectContainer, DBCEntityMetaData entityMeta, boolean transformName) throws DBException {
final DBPDataSource dataSource = objectContainer.getDataSource();
String catalogName = entityMeta.getCatalogName();
String schemaName = entityMeta.getSchemaName();
String entityName = entityMeta.getEntityName();
if (transformName) {
catalogName = DBObjectNameCaseTransformer.transformName(dataSource, catalogName);
schemaName = DBObjectNameCaseTransformer.transformName(dataSource, schemaName);
entityName = DBObjectNameCaseTransformer.transformName(dataSource, entityName);
}
DBSObject entityObject = DBUtils.getObjectByPath(monitor, objectContainer, catalogName, schemaName, entityName);
if (entityObject == null) {
return null;
} else if (entityObject instanceof DBSEntity) {
return (DBSEntity) entityObject;
} else {
log.debug("Unsupported table class: " + entityObject.getClass().getName());
return null;
}
}
private static DBSEntityReferrer getBestIdentifier(@NotNull DBRProgressMonitor monitor, @NotNull DBSEntity table, DBDAttributeBindingMeta[] bindings)
throws DBException
{
List<DBSEntityReferrer> identifiers = new ArrayList<>(2);
// Check for pseudo attrs (ROWID)
for (DBDAttributeBindingMeta column : bindings) {
DBDPseudoAttribute pseudoAttribute = column.getPseudoAttribute();
if (pseudoAttribute != null && pseudoAttribute.getType() == DBDPseudoAttributeType.ROWID) {
identifiers.add(new DBDPseudoReferrer(table, column));
break;
}
}
if (table instanceof DBSTable && ((DBSTable) table).isView()) {
// Skip physical identifiers for views. There are nothing anyway
} else if (identifiers.isEmpty()) {
// Check indexes first.
if (table instanceof DBSTable) {
try {
Collection<? extends DBSTableIndex> indexes = ((DBSTable)table).getIndexes(monitor);
if (!CommonUtils.isEmpty(indexes)) {
for (DBSTableIndex index : indexes) {
if (DBUtils.isIdentifierIndex(monitor, index)) {
identifiers.add(index);
break;
}
}
}
} catch (Exception e) {
// Indexes are not supported or not available
// Just skip them
log.debug(e);
}
}
if (identifiers.isEmpty()) {
// Check constraints
Collection<? extends DBSEntityConstraint> constraints = table.getConstraints(monitor);
if (constraints != null) {
for (DBSEntityConstraint constraint : constraints) {
if (DBUtils.isIdentifierConstraint(monitor, constraint)) {
identifiers.add((DBSEntityReferrer) constraint);
}
}
}
}
}
if (CommonUtils.isEmpty(identifiers)) {
// No physical identifiers
// Make new or use existing virtual identifier
DBVEntity virtualEntity = DBVUtils.findVirtualEntity(table, true);
identifiers.add(virtualEntity.getBestIdentifier());
}
if (!CommonUtils.isEmpty(identifiers)) {
// Find PK or unique key
DBSEntityReferrer uniqueId = null;
for (DBSEntityReferrer referrer : identifiers) {
if (isGoodReferrer(monitor, bindings, referrer)) {
if (referrer.getConstraintType() == DBSEntityConstraintType.PRIMARY_KEY) {
return referrer;
} else if (referrer.getConstraintType().isUnique() ||
(referrer instanceof DBSTableIndex && ((DBSTableIndex) referrer).isUnique()))
{
uniqueId = referrer;
}
}
}
return uniqueId;
}
return null;
}
private static boolean isGoodReferrer(DBRProgressMonitor monitor, DBDAttributeBinding[] bindings, DBSEntityReferrer referrer) throws DBException
{
if (referrer instanceof DBDPseudoReferrer) {
return true;
}
Collection<? extends DBSEntityAttributeRef> references = referrer.getAttributeReferences(monitor);
if (references == null || references.isEmpty()) {
return referrer instanceof DBVEntityConstraint;
}
for (DBSEntityAttributeRef ref : references) {
for (DBDAttributeBinding binding : bindings) {
if (binding.matches(ref.getAttribute(), false)) {
return true;
}
}
}
return true;
}
public static boolean equalAttributes(DBCAttributeMetaData attr1, DBCAttributeMetaData attr2) {
return
SQLUtils.compareAliases(attr1.getLabel(), attr2.getLabel()) &&
SQLUtils.compareAliases(attr1.getName(), attr2.getName()) &&
CommonUtils.equalObjects(attr1.getEntityMetaData(), attr2.getEntityMetaData()) &&
attr1.getOrdinalPosition() == attr2.getOrdinalPosition() &&
attr1.isRequired() == attr2.isRequired() &&
attr1.getMaxLength() == attr2.getMaxLength() &&
attr1.getPrecision() == attr2.getPrecision() &&
attr1.getScale() == attr2.getScale() &&
attr1.getTypeID() == attr2.getTypeID() &&
CommonUtils.equalObjects(attr1.getTypeName(), attr2.getTypeName());
}
@Nullable
public static Object getAttributeValueFromClipboard(DBDAttributeBinding attribute) throws DBCException
{
DBPDataSource dataSource = attribute.getDataSource();
Clipboard clipboard = new Clipboard(Display.getCurrent());
try (DBCSession session = DBUtils.openUtilSession(new VoidProgressMonitor(), dataSource, "Copy from clipboard")) {
String strValue = (String) clipboard.getContents(TextTransfer.getInstance());
return attribute.getValueHandler().getValueFromObject(
session, attribute.getAttribute(), strValue, true);
} finally {
clipboard.dispose();
}
}
public static void copyToClipboard(String string) {
if (string != null && string.length() > 0) {
Clipboard clipboard = new Clipboard(Display.getCurrent());
try {
TextTransfer textTransfer = TextTransfer.getInstance();
clipboard.setContents(
new Object[]{string},
new Transfer[]{textTransfer});
} finally {
clipboard.dispose();
}
}
}
public static boolean isServerSideFiltering(IResultSetController controller)
{
return
controller.getPreferenceStore().getBoolean(DBeaverPreferences.RESULT_SET_ORDER_SERVER_SIDE) &&
(controller.isHasMoreData() || !CommonUtils.isEmpty(controller.getModel().getDataFilter().getOrder()));
}
}