diff --git a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetModel.java b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetModel.java index 8cdb524dd649..25307e076e01 100644 --- a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetModel.java +++ b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetModel.java @@ -301,7 +301,7 @@ public Object getCellValue(@NotNull DBDAttributeBinding attribute, @NotNull Resu if (depth == 0) { final int index = attribute.getOrdinalPosition(); if (index >= row.values.length) { - log.debug("Bad attribute - index out of row values' bounds"); + log.debug("Bad attribute '" + attribute.getName() + "' index: " + index + " is out of row values' bounds (" + row.values.length + ")"); return null; } else { return row.values[index]; @@ -624,6 +624,11 @@ private void updateRowColors(boolean reset, List rows) { } if (!colorMapping.isEmpty()) { for (Map.Entry> entry : colorMapping.entrySet()) { + if (!ArrayUtils.contains(attributes, entry.getKey())) { + // This may happen during FK navigation - attributes are already updated while colors mapping are still old + continue; + } + for (ResultSetRow row : rows) { for (AttributeColorSettings acs : entry.getValue()) { Color background = null, foreground = null; diff --git a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetViewer.java b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetViewer.java index e6da16875630..7431cc0c365b 100644 --- a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetViewer.java +++ b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/ResultSetViewer.java @@ -1603,6 +1603,15 @@ public DBSDataContainer getDataContainer() return curState != null ? curState.dataContainer : container.getDataContainer(); } + public void setDataContainer(DBSDataContainer targetEntity, DBDDataFilter newFilter) { + // Workaround for script results + // In script mode history state isn't updated so we check for it here + if (curState == null) { + setNewState(targetEntity, model.getDataFilter()); + } + runDataPump(targetEntity, newFilter, 0, getSegmentMaxRows(), -1, true, false, null); + } + //////////////////////////////////////////////////////////// // Grid/Record mode @@ -2679,6 +2688,16 @@ public void navigateAssociation(@NotNull DBRProgressMonitor monitor, @Nullable D @Override public void navigateReference(@NotNull DBRProgressMonitor monitor, @NotNull DBSEntityAssociation association, @NotNull List rows, boolean newWindow) throws DBException + { + navigateReference(monitor, model, association, rows, newWindow); + } + + /** + * Navigate reference + * @param bindingsModel data bindings providing model. Can be a model from another results viewer. + */ + public void navigateReference(@NotNull DBRProgressMonitor monitor, @NotNull ResultSetModel bindingsModel, @NotNull DBSEntityAssociation association, @NotNull List rows, boolean newWindow) + throws DBException { if (!confirmProceed()) { return; @@ -2719,7 +2738,7 @@ public void navigateReference(@NotNull DBRProgressMonitor monitor, @NotNull DBSE for (int i = 0; i < refAttrs.size(); i++) { DBSEntityAttributeRef refAttr = refAttrs.get(i); - DBDAttributeBinding attrBinding = model.getAttributeBinding(refAttr.getAttribute()); + DBDAttributeBinding attrBinding = bindingsModel.getAttributeBinding(refAttr.getAttribute()); if (attrBinding == null) { log.error("Can't find attribute binding for ref attribute '" + refAttr.getAttribute().getName() + "'"); } else { @@ -2757,13 +2776,7 @@ private void navigateEntity(@NotNull DBRProgressMonitor monitor, boolean newWind if (newWindow) { openResultsInNewWindow(monitor, targetEntity, newFilter); } else { - DBSDataContainer targetDataContainer = (DBSDataContainer) targetEntity; - // Workaround for script results - // In script mode history state isn't updated so we check for it here - if (curState == null) { - setNewState(targetDataContainer, model.getDataFilter()); - } - runDataPump(targetDataContainer, newFilter, 0, getSegmentMaxRows(), -1, true, false, null); + setDataContainer((DBSDataContainer) targetEntity, newFilter); } } @@ -2972,6 +2985,9 @@ public void refresh() // Pump data DBSDataContainer dataContainer = getDataContainer(); + if (dataContainer == null) { + return; + } DBDDataFilter dataFilter = restoreDataFilter(dataContainer); if (container.isReadyToRun() && dataContainer != null && dataPumpJob == null) { @@ -3309,7 +3325,7 @@ public void done(IJobChangeEvent event) { return true; } - private void clearData() + public void clearData() { this.model.clearData(); this.curRow = null; diff --git a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesPanel.java b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesPanel.java index 394552dc0c6b..7fa2132d11b8 100644 --- a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesPanel.java +++ b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesPanel.java @@ -18,12 +18,15 @@ import org.eclipse.jface.action.IContributionManager; import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.jkiss.dbeaver.ui.UIUtils; -import org.jkiss.dbeaver.ui.controls.resultset.IResultSetController; import org.jkiss.dbeaver.ui.controls.resultset.IResultSetPanel; import org.jkiss.dbeaver.ui.controls.resultset.IResultSetPresentation; +import org.jkiss.dbeaver.ui.controls.resultset.ResultSetListenerAdapter; import org.jkiss.dbeaver.ui.controls.resultset.ResultSetUtils; /** @@ -52,7 +55,23 @@ public Control createContents(final IResultSetPresentation presentation, Composi loadSettings(); - this.resultsContainer = new ReferencesResultsContainer(parent, presentation); + this.resultsContainer = new ReferencesResultsContainer(parent, presentation.getController()); + + // Data listener + ResultSetListenerAdapter dataListener = new ResultSetListenerAdapter() { + @Override + public void handleResultSetLoad() { + refresh(true); + } + }; + presentation.getController().addListener(dataListener); + presentation.getControl().addDisposeListener(e -> presentation.getController().removeListener(dataListener)); + + if (presentation instanceof ISelectionProvider) { + ISelectionChangedListener selectionListener = event -> resultsContainer.refreshReferences(); + ((ISelectionProvider) presentation).addSelectionChangedListener(selectionListener); + presentation.getControl().addDisposeListener(e -> ((ISelectionProvider) presentation).removeSelectionChangedListener(selectionListener)); + } return this.resultsContainer.getControl(); } @@ -87,6 +106,7 @@ public void deactivatePanel() { @Override public void refresh(boolean force) { + resultsContainer.refreshReferences(); } @Override diff --git a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesResultsContainer.java b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesResultsContainer.java index f0218f0f04cd..fdcb8181984e 100644 --- a/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesResultsContainer.java +++ b/plugins/org.jkiss.dbeaver.ui.editors.data/src/org/jkiss/dbeaver/ui/controls/resultset/panel/references/ReferencesResultsContainer.java @@ -16,61 +16,93 @@ */ package org.jkiss.dbeaver.ui.controls.resultset.panel.references; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.DBIcon; +import org.jkiss.dbeaver.model.data.DBDAttributeBinding; import org.jkiss.dbeaver.model.data.DBDDataFilter; import org.jkiss.dbeaver.model.exec.DBCExecutionContext; +import org.jkiss.dbeaver.model.runtime.AbstractJob; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; -import org.jkiss.dbeaver.model.struct.DBSDataContainer; -import org.jkiss.dbeaver.model.struct.DBSEntityAssociation; +import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; +import org.jkiss.dbeaver.model.struct.*; +import org.jkiss.dbeaver.runtime.DBWorkbench; +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.resultset.*; +import org.jkiss.dbeaver.utils.GeneralUtils; +import org.jkiss.utils.CommonUtils; + +import java.util.*; public class ReferencesResultsContainer implements IResultSetContainer { - private final IResultSetPresentation presentation; - private DBSDataContainer dataContainer; - private ResultSetViewer referencesViewer; + private static final Log log = Log.getLog(ReferencesResultsContainer.class); + + private final IResultSetController parentController; private final Composite mainComposite; + private final CSmartCombo fkCombo; + private ResultSetViewer dataViewer; - public ReferencesResultsContainer(Composite parent, IResultSetPresentation presentation) { - this.presentation = presentation; + private DBSDataContainer parentDataContainer; + private DBSDataContainer dataContainer; - mainComposite = UIUtils.createComposite(parent, 1); + private final List referenceKeys = new ArrayList<>(); + private ReferenceKey activeReferenceKey; - CSmartCombo fkCombo = new CSmartCombo<>(mainComposite, SWT.DROP_DOWN | SWT.READ_ONLY, new LabelProvider() { + private List lastSelectedRows; - }); + public ReferencesResultsContainer(Composite parent, IResultSetController parentController) { + this.parentController = parentController; + + this.mainComposite = UIUtils.createComposite(parent, 1); + + Composite keySelectorPanel = UIUtils.createComposite(this.mainComposite, 2); + keySelectorPanel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + UIUtils.createControlLabel(keySelectorPanel, "Reference"); + fkCombo = new CSmartCombo<>(keySelectorPanel, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.CHECK, new RefKeyLabelProvider()); fkCombo.addItem(null); fkCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + fkCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + activeReferenceKey = fkCombo.getSelectedItem(); + refreshKeyValues(); + } + }); { Composite viewerContainer = new Composite(mainComposite, SWT.NONE); viewerContainer.setLayoutData(new GridData(GridData.FILL_BOTH)); viewerContainer.setLayout(new FillLayout()); - this.referencesViewer = new ResultSetViewer(viewerContainer, presentation.getController().getSite(), this); + this.dataViewer = new ResultSetViewer(viewerContainer, parentController.getSite(), this); } } public IResultSetPresentation getOwnerPresentation() { - return presentation; + return parentController.getActivePresentation(); } @Override public DBCExecutionContext getExecutionContext() { - return presentation.getController().getExecutionContext(); + return parentController.getExecutionContext(); } @Override public IResultSetController getResultSetController() { - return referencesViewer; + return dataViewer; } @Override @@ -78,11 +110,6 @@ public DBSDataContainer getDataContainer() { return this.dataContainer; } - public void setDataContainer(DBSDataContainer dataContainer) { - this.dataContainer = dataContainer; - this.referencesViewer.refresh(); - } - @Override public boolean isReadyToRun() { return true; @@ -98,7 +125,162 @@ public IResultSetDecorator createResultSetDecorator() { return new ReferencesResultsDecorator(this); } - public Control getControl() { + public Composite getControl() { return mainComposite; } + + public void refreshReferences() { + DBSDataContainer newParentContainer = this.parentController.getDataContainer(); + if (newParentContainer != parentDataContainer) { + refreshReferenceKeyList(); + } else if (dataContainer != null) { + refreshKeyValues(); + } + } + + /** + * Load list of referencing keys + */ + private void refreshReferenceKeyList() { + activeReferenceKey = null; + referenceKeys.clear(); + + UIUtils.syncExec(() -> { + dataViewer.clearData(); + fkCombo.removeAll(); + dataViewer.showEmptyPresentation(); + }); + List visibleAttributes = parentController.getModel().getVisibleAttributes(); + if (visibleAttributes.isEmpty()) { + return; + } + + parentDataContainer = parentController.getDataContainer(); + Set allEntities = new LinkedHashSet<>(); + for (DBDAttributeBinding attr : visibleAttributes) { + DBSEntityAttribute entityAttribute = attr.getEntityAttribute(); + if (entityAttribute != null) { + allEntities.add(entityAttribute.getParentObject()); + } + } + + if (!allEntities.isEmpty()) { + new AbstractJob("Load reference keys") { + @Override + protected IStatus run(DBRProgressMonitor monitor) { + try { + List refs = new ArrayList<>(); + for (DBSEntity entity : allEntities) { + Collection references = entity.getReferences(monitor); + if (references != null) { + for (DBSEntityAssociation assoc : references) { + if (assoc instanceof DBSEntityReferrer) { + List attrs = ((DBSEntityReferrer) assoc).getAttributeReferences(monitor); + if (!CommonUtils.isEmpty(attrs)) { + ReferenceKey referenceKey = new ReferenceKey(entity, assoc, attrs); + refs.add(referenceKey); + } + } + } + } + } + synchronized (referenceKeys) { + referenceKeys.clear(); + referenceKeys.addAll(refs); + if (!referenceKeys.isEmpty()) { + activeReferenceKey = referenceKeys.get(0); + } + } + UIUtils.syncExec(() -> fillKeysCombo()); + } catch (DBException e) { + return GeneralUtils.makeExceptionStatus(e); + } + return Status.OK_STATUS; + } + }.schedule(); + } + } + + private void fillKeysCombo() { + fkCombo.removeAll(); + if (referenceKeys.isEmpty()) { + fkCombo.addItem(null); + } + for (ReferenceKey key : referenceKeys) { + fkCombo.addItem(key); + if (key == activeReferenceKey) { + fkCombo.select(key); + } + } + + if (activeReferenceKey != null) { + refreshKeyValues(); + } + } + + /** + * Refresh data + */ + private void refreshKeyValues() { + if (activeReferenceKey == null) { + log.error("No active reference key"); + return; + } + if (!(activeReferenceKey.refEntity instanceof DBSDataContainer)) { + log.error("Referencing entity is not a data container"); + return; + } + dataContainer = (DBSDataContainer) activeReferenceKey.refEntity; + try { + List selectedRows = parentController.getSelection().getSelectedRows(); + if (CommonUtils.equalObjects(lastSelectedRows, selectedRows)) { + return; + } + lastSelectedRows = selectedRows; + if (selectedRows.isEmpty()) { + this.dataViewer.clearData(); + this.dataViewer.showEmptyPresentation(); + } else { + this.dataViewer.navigateReference( + new VoidProgressMonitor(), + parentController.getModel(), + activeReferenceKey.refAssociation, + selectedRows, + false); + } + } catch (DBException e) { + DBWorkbench.getPlatformUI().showError("Can't shwo references", "Error opening '" + dataContainer.getName() + "' references", e); + } + } + + private static class ReferenceKey { + DBSEntity refEntity; + DBSEntityAssociation refAssociation; + List refAttributes; + + ReferenceKey(DBSEntity refEntity, DBSEntityAssociation refAssociation, List refAttributes) { + this.refEntity = refEntity; + this.refAssociation = refAssociation; + this.refAttributes = refAttributes; + } + } + + private static class RefKeyLabelProvider extends LabelProvider { + + @Override + public Image getImage(Object element) { + return DBeaverIcons.getImage(DBIcon.TREE_ASSOCIATION); + } + + @Override + public String getText(Object element) { + if (element == null) { + return ""; + } + ReferenceKey key = (ReferenceKey) element; + return key.refAssociation.getParentObject().getName() + " (" + key.refAssociation.getName() + ")"; + } + + } + } diff --git a/plugins/org.jkiss.dbeaver.ui/src/org/jkiss/dbeaver/ui/controls/CSmartCombo.java b/plugins/org.jkiss.dbeaver.ui/src/org/jkiss/dbeaver/ui/controls/CSmartCombo.java index ac925eea893e..67062421c15a 100644 --- a/plugins/org.jkiss.dbeaver.ui/src/org/jkiss/dbeaver/ui/controls/CSmartCombo.java +++ b/plugins/org.jkiss.dbeaver.ui/src/org/jkiss/dbeaver/ui/controls/CSmartCombo.java @@ -256,7 +256,7 @@ public void addVerifyListener(final VerifyListener listener) private static int checkStyle(int style) { - int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.CHECK; return style & mask; } @@ -506,6 +506,9 @@ private void createPopup() if ((style & SWT.LEFT_TO_RIGHT) != 0) { listStyle |= SWT.LEFT_TO_RIGHT; } + if ((style & SWT.CHECK) != 0) { + listStyle |= SWT.CHECK; + } GridLayout gl = new GridLayout(1, true); gl.marginHeight = 0; gl.marginWidth = 0;