/* * 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.views.plan; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IContributionManager; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.*; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbenchPart; import org.jkiss.dbeaver.core.CoreCommands; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.exec.DBCExecutionContext; import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner; import org.jkiss.dbeaver.model.sql.SQLQuery; import org.jkiss.dbeaver.runtime.properties.PropertyCollector; import org.jkiss.dbeaver.ui.ActionUtils; import org.jkiss.dbeaver.ui.DBeaverIcons; import org.jkiss.dbeaver.ui.UIIcon; import org.jkiss.dbeaver.ui.UIUtils; import org.jkiss.dbeaver.ui.properties.PropertyTreeViewer; import org.jkiss.utils.CommonUtils; /** * ResultSetViewer */ public class ExplainPlanViewer implements IPropertyChangeListener { //static final Log log = Log.getLog(ResultSetViewer.class); private SashForm planPanel; private Text sqlText; private PlanNodesTree planTree; private PropertyTreeViewer planProperties; private DBCExecutionContext executionContext; private SQLQuery query; private DBCQueryPlanner planner; private RefreshPlanAction refreshPlanAction; private ToggleViewAction toggleViewAction; private final SashForm leftPanel; public ExplainPlanViewer(final IWorkbenchPart workbenchPart, Composite parent) { super(); createActions(); Composite composite = UIUtils.createPlaceholder(parent, 1); this.planPanel = UIUtils.createPartDivider(workbenchPart, composite, SWT.HORIZONTAL); this.planPanel.setLayoutData(new GridData(GridData.FILL_BOTH)); this.planPanel.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); final GridLayout gl = new GridLayout(1, false); gl.marginWidth = 0; gl.marginHeight = 0; this.planPanel.setLayout(gl); { leftPanel = UIUtils.createPartDivider(workbenchPart, planPanel, SWT.VERTICAL); leftPanel.setLayoutData(new GridData(GridData.FILL_BOTH)); this.planTree = new PlanNodesTree(leftPanel, SWT.SHEET, workbenchPart.getSite()) { @Override protected void fillCustomActions(IContributionManager contributionManager) { contributionManager.add(toggleViewAction); contributionManager.add(refreshPlanAction); } }; this.planTree.setShowDivider(true); this.planTree.createProgressPanel(composite); GridData gd = new GridData(GridData.FILL_BOTH); gd.horizontalIndent = 0; gd.verticalIndent = 0; planTree.setLayoutData(gd); sqlText = new Text(leftPanel, SWT.BORDER | SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | SWT.READ_ONLY); leftPanel.setWeights(new int[] {80, 20}); //leftPanel.setMaximizedControl(planTree); } { planProperties = new PropertyTreeViewer(planPanel, SWT.H_SCROLL | SWT.V_SCROLL); } planPanel.setWeights(new int[] {70, 30}); //planPanel.setMaximizedControl(planTree); planTree.getControl().addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { String message = null; if (planner == null) { message = "No connection or data source doesn't support execution plan"; } else if (CommonUtils.isEmpty(sqlText.getText())) { message = "Select a query and run " + ActionUtils.findCommandDescription( CoreCommands.CMD_EXPLAIN_PLAN, workbenchPart.getSite(), false); } if (message != null) { Rectangle bounds = planTree.getBounds(); Point ext = e.gc.textExtent(message); e.gc.drawText(message, (bounds.width - ext.x) / 2, bounds.height / 3 + 20); } } }); planTree.getItemsViewer().addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { showPlanNode(); } }); this.planTree.getControl().addTraverseListener(new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { if (toggleViewAction.isEnabled() && (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) { toggleViewAction.run(); e.doit = false; e.detail = SWT.TRAVERSE_NONE; } } }); } public SQLQuery getQuery() { return query; } private void showPlanNode() { ISelection selection = planTree.getItemsViewer().getSelection(); if (selection.isEmpty()) { planProperties.clearProperties(); } else if (selection instanceof IStructuredSelection) { Object element = ((IStructuredSelection) selection).getFirstElement(); PropertyCollector propertySource = new PropertyCollector(element, true); propertySource.collectProperties(); planProperties.loadProperties(propertySource); } } private void createActions() { this.toggleViewAction = new ToggleViewAction(); this.toggleViewAction.setEnabled(false); this.refreshPlanAction = new RefreshPlanAction(); this.refreshPlanAction.setEnabled(false); } public Control getControl() { return planPanel.getParent(); } public Viewer getViewer() { return planTree.getItemsViewer(); } @Override public void propertyChange(PropertyChangeEvent event) { } public void explainQueryPlan(DBCExecutionContext executionContext, SQLQuery query) throws DBCException { this.executionContext = executionContext; this.query = query; if (this.executionContext != null) { DBPDataSource dataSource = executionContext.getDataSource(); planner = DBUtils.getAdapter(DBCQueryPlanner.class, dataSource); } else { planner = null; } planTree.clearListData(); refreshPlanAction.setEnabled(false); if (planner == null) { throw new DBCException("This datasource doesn't support execution plans"); } if (planTree.isLoading()) { UIUtils.showMessageBox( getControl().getShell(), "Can't explain plan", "Explain plan already running", SWT.ICON_ERROR); return; } sqlText.setText(query.getText()); planTree.init(this.executionContext, planner, query.getText()); planTree.loadData(); refreshPlanAction.setEnabled(true); toggleViewAction.setEnabled(true); } private class RefreshPlanAction extends Action { private RefreshPlanAction() { super("Reevaluate", DBeaverIcons.getImageDescriptor(UIIcon.REFRESH)); } @Override public void run() { if (planTree != null) { planTree.loadData(); } } } private class ToggleViewAction extends Action { private ToggleViewAction() { super("View Source", DBeaverIcons.getImageDescriptor(UIIcon.SQL_TEXT)); } @Override public void run() { final Control maxControl = leftPanel.getMaximizedControl(); if (maxControl == null) { leftPanel.setMaximizedControl(planTree); } else { leftPanel.setMaximizedControl(null); } } } }