/* * 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.editors.object.struct; import org.eclipse.jface.viewers.*; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.custom.TableEditor; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.*; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.core.CoreMessages; import org.jkiss.dbeaver.core.DBeaverCore; import org.jkiss.dbeaver.core.DBeaverUI; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode; import org.jkiss.dbeaver.model.navigator.DBNNode; import org.jkiss.dbeaver.model.navigator.meta.DBXTreeNode; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress; import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSEntityAttribute; import org.jkiss.dbeaver.model.struct.DBSEntityAttributeRef; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectContainer; import org.jkiss.dbeaver.model.struct.rdb.*; import org.jkiss.dbeaver.ui.DBeaverIcons; import org.jkiss.dbeaver.ui.UIUtils; import org.jkiss.dbeaver.ui.controls.CSmartCombo; import org.jkiss.dbeaver.ui.controls.itemlist.ItemListControl; import org.jkiss.utils.CommonUtils; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * EditForeignKeyPage * * @author Serge Rider */ public class EditForeignKeyPage extends BaseObjectEditPage { private static final Log log = Log.getLog(EditForeignKeyPage.class); public static class FKColumnInfo { final DBSEntityAttribute refColumn; DBSEntityAttribute ownColumn; FKColumnInfo(DBSEntityAttribute refColumn) { this.refColumn = refColumn; } public DBSEntityAttribute getRefColumn() { return refColumn; } public DBSEntityAttribute getOwnColumn() { return ownColumn; } } private DBSForeignKeyModifyRule[] supportedModifyRules; private DBSTable ownTable; private DBSTable curRefTable; private List<DBSTableConstraint> curConstraints; private DBNDatabaseNode ownerTableNode; private Combo uniqueKeyCombo; private Table columnsTable; private ItemListControl tableList; private DBSTableConstraint curConstraint; private List<? extends DBSEntityAttribute> ownColumns; private List<FKColumnInfo> fkColumns = new ArrayList<>(); private DBSForeignKeyModifyRule onDeleteRule; private DBSForeignKeyModifyRule onUpdateRule; public EditForeignKeyPage( String title, DBSTable table, DBSForeignKeyModifyRule[] supportedModifyRules) { super(title); this.ownTable = table; this.ownerTableNode = DBeaverCore.getInstance().getNavigatorModel().findNode(ownTable); this.supportedModifyRules = supportedModifyRules; if (ownerTableNode != null) { setImageDescriptor(DBeaverIcons.getImageDescriptor(ownerTableNode.getNodeIcon())); setTitle(title + " | " + NLS.bind(CoreMessages.dialog_struct_edit_fk_title, title, ownerTableNode.getNodeName())); } } @Override protected Control createPageContents(Composite parent) { final Composite panel = UIUtils.createPlaceholder(parent, 1, 5); panel.setLayoutData(new GridData(GridData.FILL_BOTH)); { final Composite tableGroup = UIUtils.createPlaceholder(panel, 2, 5); tableGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); UIUtils.createLabelText(tableGroup, CoreMessages.dialog_struct_edit_fk_label_table, ownTable.getFullyQualifiedName(DBPEvaluationContext.UI), SWT.READ_ONLY | SWT.BORDER); if (ownerTableNode != null) { try { createSchemaSelector(tableGroup); } catch (Throwable e) { log.debug("Can't create schema selector", e); } } } { DBNNode rootNode = ownerTableNode == null ? DBeaverCore.getInstance().getNavigatorModel().getRoot() : ownerTableNode.getParentNode(); UIUtils.createControlLabel(panel, CoreMessages.dialog_struct_edit_fk_label_ref_table); tableList = new ItemListControl(panel, SWT.SINGLE | SWT.SHEET | SWT.BORDER, null, rootNode, null); tableList.loadData(); final GridData gd = new GridData(GridData.FILL_BOTH); gd.widthHint = 500; gd.heightHint = 150; tableList.setLayoutData(gd); tableList.getSelectionProvider().addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { handleRefTableSelect(event.getSelection()); } }); } final Composite pkGroup = UIUtils.createPlaceholder(panel, 2); { pkGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); uniqueKeyCombo = UIUtils.createLabelCombo(pkGroup, CoreMessages.dialog_struct_edit_fk_combo_unik, SWT.DROP_DOWN | SWT.READ_ONLY); uniqueKeyCombo.setEnabled(false); uniqueKeyCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { handleUniqueKeySelect(); } }); } { UIUtils.createControlLabel(panel, CoreMessages.dialog_struct_edit_fk_label_columns); columnsTable = new Table(panel, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); columnsTable.setHeaderVisible(true); columnsTable.setLinesVisible(true); final GridData gd = new GridData(GridData.FILL_BOTH); gd.widthHint = 500; gd.heightHint = 100; columnsTable.setLayoutData(gd); UIUtils.createTableColumn(columnsTable, SWT.LEFT, CoreMessages.dialog_struct_edit_fk_column_column); UIUtils.createTableColumn(columnsTable, SWT.LEFT, CoreMessages.dialog_struct_edit_fk_column_col_type); UIUtils.createTableColumn(columnsTable, SWT.LEFT, CoreMessages.dialog_struct_edit_fk_column_ref_col); UIUtils.createTableColumn(columnsTable, SWT.LEFT, CoreMessages.dialog_struct_edit_fk_column_ref_col_type); UIUtils.packColumns(columnsTable); final TableEditor tableEditor = new TableEditor(columnsTable); tableEditor.horizontalAlignment = SWT.CENTER; tableEditor.verticalAlignment = SWT.TOP; tableEditor.grabHorizontal = true; tableEditor.minimumWidth = 50; columnsTable.addMouseListener(new ColumnsMouseListener(tableEditor, columnsTable)); } final Composite cascadeGroup = UIUtils.createPlaceholder(panel, 4, 5); { // Cascades cascadeGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); final Combo onDeleteCombo = UIUtils.createLabelCombo(cascadeGroup, CoreMessages.dialog_struct_edit_fk_combo_on_delete, SWT.DROP_DOWN | SWT.READ_ONLY); onDeleteCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); final Combo onUpdateCombo = UIUtils.createLabelCombo(cascadeGroup, CoreMessages.dialog_struct_edit_fk_combo_on_update, SWT.DROP_DOWN | SWT.READ_ONLY); onUpdateCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); for (DBSForeignKeyModifyRule modifyRule : supportedModifyRules) { onDeleteCombo.add(modifyRule.getName()); onUpdateCombo.add(modifyRule.getName()); } onDeleteCombo.select(0); onUpdateCombo.select(0); onDeleteRule = onUpdateRule = supportedModifyRules[0]; onDeleteCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onDeleteRule = supportedModifyRules[onDeleteCombo.getSelectionIndex()]; } }); onUpdateCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onUpdateRule = supportedModifyRules[onUpdateCombo.getSelectionIndex()]; } }); } //panel.setTabList(new Control[] { tableList, pkGroup, columnsTable, cascadeGroup }); return panel; } private void createSchemaSelector(Composite tableGroup) throws DBException { // Here is a trick - we need to find schema/catalog container node and list its children DBNDatabaseNode schemaContainerNode = null; for (DBNNode node = ownerTableNode.getParentNode(); node != null; node = node.getParentNode()) { if (node instanceof DBNDatabaseNode) { DBSObject nodeObject = ((DBNDatabaseNode) node).getObject(); if (nodeObject instanceof DBSSchema || nodeObject instanceof DBSCatalog) { if (node.getParentNode() instanceof DBNDatabaseNode) { schemaContainerNode = (DBNDatabaseNode) node.getParentNode(); break; } } } } if (schemaContainerNode != null) { ILabelProvider labelProvider = new LabelProvider() { @Override public Image getImage(Object element) { return DBeaverIcons.getImage(((DBNDatabaseNode) element).getNodeIcon()); } @Override public String getText(Object element) { return ((DBNDatabaseNode) element).getNodeName(); } }; boolean isSchema = (ownTable.getParentObject() instanceof DBSSchema); DBPDataSourceInfo dsInfo = ownTable.getDataSource().getInfo(); UIUtils.createControlLabel(tableGroup, isSchema ? dsInfo.getSchemaTerm() : dsInfo.getCatalogTerm()); final CSmartCombo<DBNDatabaseNode> schemaCombo = new CSmartCombo<>(tableGroup, SWT.BORDER, labelProvider); schemaCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING)); DBNDatabaseNode selectedNode = null; for (DBNNode node : schemaContainerNode.getChildren(new VoidProgressMonitor())) { if (node instanceof DBNDatabaseNode && ((DBNDatabaseNode) node).getObject() instanceof DBSObjectContainer) { schemaCombo.addItem((DBNDatabaseNode) node); if (((DBNDatabaseNode) node).getObject() == ownTable.getParentObject()) { selectedNode = (DBNDatabaseNode) node; } } } if (selectedNode != null) { schemaCombo.select(selectedNode); } schemaCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Here is another trick // We need to find table container node // This node is a child of schema node and has the same meta as our original table parent node DBNDatabaseNode newContainerNode = null; DBXTreeNode tableContainerMeta = ((DBNDatabaseNode) ownerTableNode.getParentNode()).getMeta(); DBNDatabaseNode schemaNode = schemaCombo.getSelectedItem(); if (schemaNode.getMeta() == tableContainerMeta) { newContainerNode = schemaNode; } else { try { for (DBNNode child : schemaNode.getChildren(new VoidProgressMonitor())) { if (child instanceof DBNDatabaseNode && ((DBNDatabaseNode) child).getMeta() == tableContainerMeta) { newContainerNode = (DBNDatabaseNode) child; break; } } } catch (DBException e1) { log.debug(e1); // Shouldn't be here } } if (newContainerNode != null) { tableList.setRootNode(newContainerNode); tableList.loadData(); } } }); } } private void handleRefTableSelect(ISelection selection) { DBNDatabaseNode refTableNode = null; if (!selection.isEmpty() && selection instanceof IStructuredSelection && ((IStructuredSelection) selection).size() == 1) { final Object element = ((IStructuredSelection) selection).getFirstElement(); if (element instanceof DBNDatabaseNode && ((DBNDatabaseNode) element).getObject() instanceof DBSTable && ((DBNDatabaseNode) element).getObject().isPersisted()) { refTableNode = (DBNDatabaseNode) element; } } if (refTableNode != null) { if (refTableNode.getObject() == curRefTable) { // The same selection return; } else { curRefTable = (DBSTable) refTableNode.getObject(); } } uniqueKeyCombo.removeAll(); try { curConstraints = new ArrayList<>(); curConstraint = null; if (refTableNode != null) { final DBSTable refTable = (DBSTable) refTableNode.getObject(); DBeaverUI.runInProgressService(new DBRRunnableWithProgress() { @Override public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { // Cache own table columns ownTable.getAttributes(monitor); // Cache ref table columns refTable.getAttributes(monitor); // Get constraints final Collection<? extends DBSTableConstraint> constraints = refTable.getConstraints(monitor); if (!CommonUtils.isEmpty(constraints)) { for (DBSTableConstraint constraint : constraints) { if (constraint.getConstraintType().isUnique()) { curConstraints.add(constraint); } } } } catch (DBException e) { throw new InvocationTargetException(e); } } }); } for (DBSTableConstraint constraint : curConstraints) { uniqueKeyCombo.add(constraint.getName()); } uniqueKeyCombo.select(0); uniqueKeyCombo.setEnabled(curConstraints.size() > 1); if (curConstraints.size() == 1) { curConstraint = curConstraints.get(0); } } catch (InvocationTargetException e) { UIUtils.showErrorDialog(getShell(), CoreMessages.dialog_struct_edit_fk_error_load_constraints_title, CoreMessages.dialog_struct_edit_fk_error_load_constraints_message, e.getTargetException()); } catch (InterruptedException e) { // do nothing } handleUniqueKeySelect(); updatePageState(); } private void handleUniqueKeySelect() { curConstraint = null; fkColumns.clear(); ownColumns = null; columnsTable.removeAll(); if (curConstraints.isEmpty() || uniqueKeyCombo.getSelectionIndex() < 0) { return; } curConstraint = curConstraints.get(uniqueKeyCombo.getSelectionIndex()); try { // Read column nodes with void monitor because we already cached them above for (DBSEntityAttributeRef pkColumn : curConstraint.getAttributeReferences(new VoidProgressMonitor())) { FKColumnInfo fkColumnInfo = new FKColumnInfo(pkColumn.getAttribute()); // Try to find matched column in own table Collection<? extends DBSEntityAttribute> tmpColumns = ownTable.getAttributes(new VoidProgressMonitor()); ownColumns = tmpColumns == null ? Collections.<DBSTableColumn>emptyList() : new ArrayList<>(ownTable.getAttributes(new VoidProgressMonitor())); if (!CommonUtils.isEmpty(ownColumns)) { for (DBSEntityAttribute ownColumn : ownColumns) { if (ownColumn.getName().equals(pkColumn.getAttribute().getName()) && ownTable != pkColumn.getAttribute().getParentObject()) { fkColumnInfo.ownColumn = ownColumn; break; } } } fkColumns.add(fkColumnInfo); TableItem item = new TableItem(columnsTable, SWT.NONE); if (fkColumnInfo.ownColumn != null) { item.setText(0, fkColumnInfo.ownColumn.getName()); item.setImage(0, getColumnIcon(fkColumnInfo.ownColumn)); item.setText(1, fkColumnInfo.ownColumn.getFullTypeName()); } item.setText(2, pkColumn.getAttribute().getName()); item.setImage(2, getColumnIcon(pkColumn.getAttribute())); item.setText(3, pkColumn.getAttribute().getFullTypeName()); item.setData(fkColumnInfo); } } catch (DBException e) { UIUtils.showErrorDialog(getShell(), CoreMessages.dialog_struct_edit_fk_error_load_constraint_columns_title, CoreMessages.dialog_struct_edit_fk_error_load_constraint_columns_message, e); } UIUtils.packColumns(columnsTable, true); } private Image getColumnIcon(DBSEntityAttribute column) { return DBeaverIcons.getImage(DBValueFormatting.getObjectImage(column)); } @Override public boolean isPageComplete() { if (fkColumns.isEmpty()) { return false; } for (FKColumnInfo col : fkColumns) { if (col.ownColumn == null || col.refColumn == null) { return false; } } return true; } public List<FKColumnInfo> getColumns() { return fkColumns; } public DBSForeignKeyModifyRule getOnDeleteRule() { return onDeleteRule; } public DBSForeignKeyModifyRule getOnUpdateRule() { return onUpdateRule; } public DBSTableConstraint getUniqueConstraint() { return curConstraint; } private class ColumnsMouseListener extends MouseAdapter { private final TableEditor tableEditor; private final Table columnsTable; public ColumnsMouseListener(TableEditor tableEditor, Table columnsTable) { this.tableEditor = tableEditor; this.columnsTable = columnsTable; } private void disposeOldEditor() { Control oldEditor = tableEditor.getEditor(); if (oldEditor != null) oldEditor.dispose(); } @Override public void mouseUp(MouseEvent e) { handleColumnClick(e); } private void handleColumnClick(MouseEvent e) { // Clean up any previous editor control disposeOldEditor(); final TableItem item = columnsTable.getItem(new Point(e.x, e.y)); if (item == null) { return; } int columnIndex = UIUtils.getColumnAtPos(item, e.x, e.y); if (columnIndex != 0) { return; } final FKColumnInfo fkInfo = (FKColumnInfo) item.getData(); // Identify the selected row final CCombo columnsCombo = new CCombo(columnsTable, SWT.DROP_DOWN | SWT.READ_ONLY); if (!CommonUtils.isEmpty(ownColumns)) { for (DBSEntityAttribute ownColumn : ownColumns) { columnsCombo.add(ownColumn.getName()); if (fkInfo.ownColumn == ownColumn) { columnsCombo.select(columnsCombo.getItemCount() - 1); } } if (columnsCombo.getSelectionIndex() < 0) { columnsCombo.select(0); } } // Selected by mouse columnsCombo.setFocus(); columnsCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (columnsCombo.getSelectionIndex() >= 0) { fkInfo.ownColumn = ownColumns.get(columnsCombo.getSelectionIndex()); item.setText(0, fkInfo.ownColumn.getName()); item.setImage(0, getColumnIcon(fkInfo.ownColumn)); item.setText(1, fkInfo.ownColumn.getFullTypeName()); updatePageState(); } } }); columnsCombo.addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { disposeOldEditor(); } }); tableEditor.setEditor(columnsCombo, item, 0); } } }