/* * 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.sql; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFileState; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.action.*; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.text.*; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.custom.SashForm; 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.eclipse.ui.*; import org.eclipse.ui.ide.FileStoreEditorInput; import org.eclipse.ui.texteditor.DefaultRangeIndicator; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.rulers.IColumnSupport; import org.eclipse.ui.texteditor.rulers.RulerColumnDescriptor; import org.eclipse.ui.texteditor.rulers.RulerColumnRegistry; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.DBeaverPreferences; import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.core.*; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.data.DBDDataFilter; import org.jkiss.dbeaver.model.data.DBDDataReceiver; import org.jkiss.dbeaver.model.exec.*; import org.jkiss.dbeaver.model.exec.plan.DBCPlan; import org.jkiss.dbeaver.model.exec.plan.DBCPlanStyle; import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner; import org.jkiss.dbeaver.model.impl.DefaultServerOutputReader; import org.jkiss.dbeaver.model.impl.sql.SQLQueryTransformerCount; import org.jkiss.dbeaver.model.preferences.DBPPreferenceListener; import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.qm.QMUtils; import org.jkiss.dbeaver.model.runtime.AbstractJob; import org.jkiss.dbeaver.model.runtime.DBRProgressListener; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress; import org.jkiss.dbeaver.model.sql.*; import org.jkiss.dbeaver.model.struct.DBSDataContainer; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectSelector; import org.jkiss.dbeaver.runtime.sql.SQLQueryJob; import org.jkiss.dbeaver.runtime.sql.SQLQueryListener; import org.jkiss.dbeaver.runtime.sql.SQLResultsConsumer; import org.jkiss.dbeaver.tools.transfer.IDataTransferProducer; import org.jkiss.dbeaver.tools.transfer.database.DatabaseTransferProducer; import org.jkiss.dbeaver.tools.transfer.wizard.DataTransferWizard; import org.jkiss.dbeaver.ui.*; import org.jkiss.dbeaver.ui.actions.datasource.DataSourceHandler; import org.jkiss.dbeaver.ui.controls.resultset.IResultSetContainer; import org.jkiss.dbeaver.ui.controls.resultset.IResultSetListener; import org.jkiss.dbeaver.ui.controls.resultset.ResultSetViewer; import org.jkiss.dbeaver.ui.dialogs.ActiveWizardDialog; import org.jkiss.dbeaver.ui.dialogs.EnterNameDialog; import org.jkiss.dbeaver.ui.editors.DatabaseEditorUtils; import org.jkiss.dbeaver.ui.editors.EditorUtils; import org.jkiss.dbeaver.ui.editors.INonPersistentEditorInput; import org.jkiss.dbeaver.ui.editors.StringEditorInput; import org.jkiss.dbeaver.ui.editors.sql.log.SQLLogPanel; import org.jkiss.dbeaver.ui.editors.text.ScriptPositionColumn; import org.jkiss.dbeaver.ui.views.SQLResultsView; import org.jkiss.dbeaver.ui.views.plan.ExplainPlanViewer; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.dbeaver.utils.RuntimeUtils; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.IOUtils; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.*; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * SQL Executor */ public class SQLEditor extends SQLEditorBase implements IDataSourceContainerProviderEx, DBPContextProvider, DBPEventListener, ISaveablePart2, DBPDataSourceUser, DBPDataSourceHandler, DBPPreferenceListener { private static final long SCRIPT_UI_UPDATE_PERIOD = 100; private static Image IMG_DATA_GRID = DBeaverActivator.getImageDescriptor("/icons/sql/page_data_grid.png").createImage(); //$NON-NLS-1$ private static Image IMG_DATA_GRID_LOCKED = DBeaverActivator.getImageDescriptor("/icons/sql/page_data_grid_locked.png").createImage(); //$NON-NLS-1$ private static Image IMG_EXPLAIN_PLAN = DBeaverActivator.getImageDescriptor("/icons/sql/page_explain_plan.png").createImage(); //$NON-NLS-1$ private static Image IMG_LOG = DBeaverActivator.getImageDescriptor("/icons/sql/page_error.png").createImage(); //$NON-NLS-1$ private static Image IMG_OUTPUT = DBeaverActivator.getImageDescriptor("/icons/sql/page_output.png").createImage(); //$NON-NLS-1$ private static Image IMG_OUTPUT_ALERT = DBeaverActivator.getImageDescriptor("/icons/sql/page_output_alert.png").createImage(); //$NON-NLS-1$ public static final String VAR_CONNECTION_NAME = "connectionName"; public static final String VAR_FILE_NAME = "fileName"; public static final String DEFAULT_PATTERN = "<${" + VAR_CONNECTION_NAME + "}> ${" + VAR_FILE_NAME + "}"; public static final String VAR_FILE_EXT = "fileExt"; public static final String VAR_DRIVER_NAME = "driverName"; private SashForm sashForm; private Control editorControl; private CTabFolder resultTabs; private ToolItem toolOutputItem; private SQLLogPanel logViewer; private SQLEditorOutputViewer outputViewer; private volatile QueryProcessor curQueryProcessor; private final List<QueryProcessor> queryProcessors = new ArrayList<>(); private DBPDataSourceContainer dataSourceContainer; private DBPDataSource curDataSource; private volatile DBCExecutionContext executionContext; private volatile DBCExecutionContext lastExecutionContext; private volatile boolean syntaxLoaded = false; private volatile boolean ownContext = false; private final FindReplaceTarget findReplaceTarget = new FindReplaceTarget(); private final List<SQLQuery> runningQueries = new ArrayList<>(); private QueryResultsContainer curResultsContainer; private Image editorImage; private ToolItem toolLogItem; private final SQLScriptContext scriptContext; public SQLEditor() { super(); this.scriptContext = new SQLScriptContext(new OutputLogWriter()); } @Override public DBCExecutionContext getExecutionContext() { return executionContext; } @Nullable public IProject getProject() { IFile file = EditorUtils.getFileFromInput(getEditorInput()); return file == null ? null : file.getProject(); } @Nullable @Override public int[] getCurrentLines() { synchronized (runningQueries) { Document document = getDocument(); if (document == null || runningQueries.isEmpty()) { return null; } List<Integer> lines = new ArrayList<>(runningQueries.size() * 2); for (SQLQuery statementInfo : runningQueries) { try { int firstLine = document.getLineOfOffset(statementInfo.getOffset()); int lastLine = document.getLineOfOffset(statementInfo.getOffset() + statementInfo.getLength()); for (int k = firstLine; k <= lastLine; k++) { lines.add(k); } } catch (BadLocationException e) { // ignore - this may happen is SQL was edited after execution start } } if (lines.isEmpty()) { return null; } int[] results = new int[lines.size()]; for (int i = 0; i < lines.size(); i++) { results[i] = lines.get(i); } return results; } } @Nullable @Override public DBPDataSourceContainer getDataSourceContainer() { return dataSourceContainer; } @Override public boolean setDataSourceContainer(@Nullable DBPDataSourceContainer container) { if (container == dataSourceContainer) { onDataSourceChange(); return true; } // Release ds container releaseContainer(); closeAllJobs(); dataSourceContainer = container; if (dataSourceContainer != null) { dataSourceContainer.getPreferenceStore().addPropertyChangeListener(this); dataSourceContainer.getRegistry().addDataSourceListener(this); } IEditorInput input = getEditorInput(); if (input != null) { EditorUtils.setInputDataSource(input, container, true); } checkConnected(false, null); setPartName(getEditorName()); onDataSourceChange(); if (dataSourceContainer != null) { dataSourceContainer.acquire(this); } return true; } private void updateExecutionContext() { if (dataSourceContainer == null) { releaseExecutionContext(); } else { // Get/open context final DBPDataSource dataSource = dataSourceContainer.getDataSource(); if (dataSource == null) { releaseExecutionContext(); } else if (curDataSource != dataSource) { releaseExecutionContext(); curDataSource = dataSource; if (curDataSource.getContainer().getPreferenceStore().getBoolean(DBeaverPreferences.EDITOR_SEPARATE_CONNECTION)) { final OpenContextJob job = new OpenContextJob(dataSource); job.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { if (job.error != null) { releaseExecutionContext(); UIUtils.showErrorDialog(getSite().getShell(), "Open context", "Can't open editor connection", job.error); } else { DBeaverUI.syncExec(new Runnable() { @Override public void run() { onDataSourceChange(); } }); } } }); job.schedule(); /* try { DBeaverUI.runInProgressDialog(new DBRRunnableWithProgress() { @Override public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask("Open SQLEditor isolated connection", 1); try { String title = "SQLEditor <" + getEditorInput().getPath().removeFileExtension().lastSegment() + ">"; monitor.subTask("Open context " + title); executionContext = dataSource.openIsolatedContext(monitor, title); } catch (DBException e) { throw new InvocationTargetException(e); } finally { monitor.done(); } ownContext = true; } }); } catch (InvocationTargetException e) { releaseExecutionContext(); UIUtils.showErrorDialog(getSite().getShell(), "Open context", "Can't open editor connection", e); } */ } else { executionContext = dataSource.getDefaultContext(false); } } } } private void releaseExecutionContext() { if (ownContext && executionContext != null && executionContext.isConnected()) { // Close context in separate job (otherwise it can block UI) new CloseContextJob(executionContext).schedule(); } executionContext = null; ownContext = false; curDataSource = null; } private void releaseContainer() { releaseExecutionContext(); if (dataSourceContainer != null) { dataSourceContainer.getPreferenceStore().removePropertyChangeListener(this); dataSourceContainer.getRegistry().removeDataSourceListener(this); dataSourceContainer.release(this); dataSourceContainer = null; } } private class OutputLogWriter extends Writer { @Override public void write(@NotNull final char[] cbuf, final int off, final int len) throws IOException { DBeaverUI.syncExec(new Runnable() { @Override public void run() { if (!outputViewer.isDisposed()) { outputViewer.getOutputWriter().write(cbuf, off, len); outputViewer.scrollToEnd(); if (!outputViewer.isVisible()) { updateOutputViewerIcon(true); } } } }); } @Override public void flush() throws IOException { outputViewer.getOutputWriter().flush(); } @Override public void close() throws IOException { } } private class OpenContextJob extends AbstractJob { private final DBPDataSource dataSource; private Throwable error; OpenContextJob(DBPDataSource dataSource) { super("Open connection to " + dataSource.getContainer().getName()); this.dataSource = dataSource; setUser(true); } @Override protected IStatus run(DBRProgressMonitor monitor) { monitor.beginTask("Open SQLEditor isolated connection", 1); try { String title = "SQLEditor <" + getEditorInput().getName() + ">"; monitor.subTask("Open context " + title); executionContext = dataSource.openIsolatedContext(monitor, title); } catch (DBException e) { error = e; return Status.OK_STATUS; } finally { monitor.done(); } ownContext = true; return Status.OK_STATUS; } } private class CloseContextJob extends AbstractJob { private final DBCExecutionContext context; CloseContextJob(DBCExecutionContext context) { super("Close context " + context.getContextName()); this.context = context; setUser(true); } @Override protected IStatus run(DBRProgressMonitor monitor) { monitor.beginTask("Close SQLEditor isolated connection", 1); try { if (QMUtils.isTransactionActive(context)) { DataSourceHandler.closeActiveTransaction(monitor, context, true); } monitor.subTask("Close context " + context.getContextName()); context.close(); } finally { monitor.done(); } return Status.OK_STATUS; } } @Override public boolean isDirty() { for (QueryProcessor queryProcessor : queryProcessors) { if (queryProcessor.isDirty()) { return true; } } if (ownContext && QMUtils.isTransactionActive(executionContext)) { return true; } if (isNonPersistentEditor()) { // Console is never dirty return false; } return super.isDirty(); } @Nullable @Override public Object getAdapter(Class required) { if (required == IFindReplaceTarget.class) { return findReplaceTarget; } ResultSetViewer resultsView = getActiveResultSetViewer(); if (resultsView != null) { if (required == ResultSetViewer.class) { return resultsView; } Object adapter = resultsView.getAdapter(required); if (adapter != null) { return adapter; } } return super.getAdapter(required); } private boolean checkConnected(boolean forceConnect, DBRProgressListener onFinish) { // Connect to datasource final DBPDataSourceContainer dataSourceContainer = getDataSourceContainer(); boolean doConnect = dataSourceContainer != null && (forceConnect || dataSourceContainer.getPreferenceStore().getBoolean(DBeaverPreferences.EDITOR_CONNECT_ON_ACTIVATE)); if (doConnect) { if (!dataSourceContainer.isConnected()) { DataSourceHandler.connectToDataSource(null, dataSourceContainer, onFinish); } } return dataSourceContainer != null && dataSourceContainer.isConnected(); } @Override public void createPartControl(Composite parent) { setRangeIndicator(new DefaultRangeIndicator()); sashForm = UIUtils.createPartDivider(this, parent, SWT.VERTICAL | SWT.SMOOTH); sashForm.setSashWidth(5); UIUtils.setHelp(sashForm, IHelpContextIds.CTX_SQL_EDITOR); super.createPartControl(sashForm); editorControl = sashForm.getChildren()[0]; getSite().setSelectionProvider(new DynamicSelectionProvider()); createResultTabs(); // Update controls onDataSourceChange(); setAction(ITextEditorActionConstants.SHOW_INFORMATION, null); //toolTipAction.setEnabled(false); CoreFeatures.SQL_EDITOR_OPEN.use(); } private void createResultTabs() { resultTabs = new CTabFolder(sashForm, SWT.TOP | SWT.FLAT); resultTabs.setLayoutData(new GridData(GridData.FILL_BOTH)); resultTabs.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Object data = e.item.getData(); if (data instanceof QueryResultsContainer) { curResultsContainer = (QueryResultsContainer) data; curQueryProcessor = curResultsContainer.queryProcessor; ResultSetViewer rsv = curResultsContainer.getResultSetController(); if (rsv != null) { //rsv.getActivePresentation().getControl().setFocus(); } } } }); getTextViewer().getTextWidget().addTraverseListener(new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { if (e.detail == SWT.TRAVERSE_PAGE_NEXT) { ResultSetViewer viewer = getActiveResultSetViewer(); if (viewer != null && viewer.getActivePresentation().getControl().isVisible()) { viewer.getActivePresentation().getControl().setFocus(); e.doit = false; e.detail = SWT.TRAVERSE_NONE; } } } }); resultTabs.setSimple(true); //resultTabs.setMRUVisible(true); { ToolBar rsToolbar = new ToolBar(resultTabs, SWT.HORIZONTAL | SWT.RIGHT | SWT.WRAP); toolLogItem = new ToolItem(rsToolbar, SWT.CHECK); toolLogItem.setText("Log"); toolLogItem.setToolTipText(ActionUtils.findCommandDescription(CoreCommands.CMD_SQL_SHOW_LOG, getSite(), false)); toolLogItem.setImage(IMG_LOG); toolLogItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { showExecutionLogPanel(); } }); toolOutputItem = new ToolItem(rsToolbar, SWT.CHECK); toolOutputItem.setText("Output"); toolOutputItem.setToolTipText(ActionUtils.findCommandDescription(CoreCommands.CMD_SQL_SHOW_OUTPUT, getSite(), false)); toolOutputItem.setImage(IMG_OUTPUT); toolOutputItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { toolOutputItem.setImage(IMG_OUTPUT); showOutputPanel(); } }); resultTabs.setTopRight(rsToolbar); } resultTabs.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { if (e.button == 2) { CTabItem item = resultTabs.getItem(new Point(e.x, e.y)); if (item != null && item.getShowClose()) { item.dispose(); } } } }); resultTabs.addListener(SWT.MouseDoubleClick, new Listener() { @Override public void handleEvent(Event event) { if (event.button != 1) { return; } CTabItem selectedItem = resultTabs.getItem(new Point(event.getBounds().x, event.getBounds().y)); if (selectedItem != null && selectedItem == resultTabs.getSelection()) { toggleEditorMaximize(); } } }); // Extra views //planView = new ExplainPlanViewer(this, resultTabs); logViewer = new SQLLogPanel(resultTabs, this); outputViewer = new SQLEditorOutputViewer(getSite(), resultTabs, SWT.NONE); // Create results tab createQueryProcessor(true); { MenuManager menuMgr = new MenuManager(); Menu menu = menuMgr.createContextMenu(resultTabs); menuMgr.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(IMenuManager manager) { manager.add(ActionUtils.makeCommandContribution(getSite(), CoreCommands.CMD_SQL_EDITOR_MAXIMIZE_PANEL)); if (resultTabs.getItemCount() > 1) { manager.add(new Action("Close multiple results") { @Override public void run() { closeExtraResultTabs(null); } }); } final CTabItem activeTab = resultTabs.getSelection(); if (activeTab != null && resultTabs.indexOf(activeTab) > 0 && activeTab.getData() instanceof QueryResultsContainer) { final QueryResultsContainer resultsContainer = (QueryResultsContainer)activeTab.getData(); manager.add(new Separator()); final boolean isPinned = resultsContainer.isPinned(); manager.add(new Action(isPinned ? "Unpin tab" : "Pin tab") { @Override public void run() { resultsContainer.setPinned(!isPinned); } }); manager.add(new Action("Set tab title") { @Override public void run() { EnterNameDialog dialog = new EnterNameDialog(resultTabs.getShell(), "Tab title", activeTab.getText()); if (dialog.open() == IDialogConstants.OK_ID) { activeTab.setText(dialog.getResult()); } } }); } if (activeTab != null && activeTab.getShowClose()) { manager.add(ActionUtils.makeCommandContribution(getSite(), CoreCommands.CMD_SQL_EDITOR_CLOSE_TAB)); } } }); menuMgr.setRemoveAllWhenShown(true); resultTabs.setMenu(menu); } } private void showExtraView(final ToolItem toolItem, String name, String toolTip, Image image, Control view) { for (CTabItem item : resultTabs.getItems()) { if (item.getData() == view) { if (resultTabs.getSelection() == item) { item.dispose(); toolItem.setSelection(false); return; } else { resultTabs.setSelection(item); return; } } } if (view == outputViewer) { updateOutputViewerIcon(false); outputViewer.resetNewOutput(); } // Create new tab toolItem.setSelection(true); CTabItem item = new CTabItem(resultTabs, SWT.CLOSE); item.setControl(view); item.setText(name); item.setToolTipText(toolTip); item.setImage(image); item.setData(view); // De-select tool item on tab close item.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { toolItem.setSelection(false); } }); resultTabs.setSelection(item); } public void closeActiveTab() { CTabItem tabItem = resultTabs.getSelection(); if (tabItem != null && tabItem.getShowClose()) { tabItem.dispose(); } } public void showOutputPanel() { if (sashForm.getMaximizedControl() != null) { sashForm.setMaximizedControl(null); } showExtraView(toolOutputItem, CoreMessages.editors_sql_output, "Database server output log", IMG_OUTPUT, outputViewer); } public void showExecutionLogPanel() { if (sashForm.getMaximizedControl() != null) { sashForm.setMaximizedControl(null); } showExtraView(toolLogItem, CoreMessages.editors_sql_execution_log, "SQL query execution log", IMG_LOG, logViewer); } public void toggleResultPanel() { if (sashForm.getMaximizedControl() == null) { sashForm.setMaximizedControl(editorControl); } else { sashForm.setMaximizedControl(null); } } public void toggleEditorMaximize() { if (sashForm.getMaximizedControl() == null) { sashForm.setMaximizedControl(resultTabs); } else { sashForm.setMaximizedControl(null); } } public void toggleActivePanel() { if (sashForm.getMaximizedControl() == null) { if (UIUtils.hasFocus(resultTabs)) { final Control editorControl = getEditorControl(); if (editorControl != null) { editorControl.setFocus(); } } else { CTabItem selTab = resultTabs.getSelection(); if (selTab != null) { ResultSetViewer viewer = getActiveResultSetViewer(); if (viewer != null && viewer.getActivePresentation().getControl().isVisible()) { viewer.getActivePresentation().getControl().setFocus(); } else { selTab.getControl().setFocus(); } } } } } @Override public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException { super.init(site, editorInput); } @Override protected void doSetInput(IEditorInput editorInput) throws CoreException { // Check for file existence try { if (editorInput instanceof IFileEditorInput) { final IFile file = ((IFileEditorInput) editorInput).getFile(); if (!file.exists()) { file.create(new ByteArrayInputStream(new byte[]{}), true, new NullProgressMonitor()); } } } catch (Exception e) { log.error("Error checking SQL file", e); } try { super.doSetInput(editorInput); } catch (Throwable e) { // Something bas may happend. E.g. OutOfMemory error in case of rtoo big input file. StringWriter out = new StringWriter(); e.printStackTrace(new PrintWriter(out, true)); editorInput = new StringEditorInput("Error", CommonUtils.truncateString(out.toString(), 10000), true, GeneralUtils.UTF8_ENCODING); doSetInput(editorInput); log.error("Error loading input SQL file", e); } syntaxLoaded = false; setDataSourceContainer(EditorUtils.getInputDataSource(editorInput)); setPartName(getEditorName()); if (isNonPersistentEditor()) { setTitleImage(DBeaverIcons.getImage(UIIcon.SQL_CONSOLE)); } editorImage = getTitleImage(); } @Override public String getTitleToolTip() { DBPDataSourceContainer dataSourceContainer = getDataSourceContainer(); if (dataSourceContainer == null) { return super.getTitleToolTip(); } final IEditorInput editorInput = getEditorInput(); String scriptPath; if (editorInput instanceof IFileEditorInput) { scriptPath = ((IFileEditorInput) editorInput).getFile().getFullPath().toString(); } else if (editorInput instanceof IPathEditorInput) { scriptPath = ((IPathEditorInput) editorInput).getPath().toString(); } else if (editorInput instanceof IURIEditorInput) { final URI uri = ((IURIEditorInput) editorInput).getURI(); if ("file".equals(uri.getScheme())) { scriptPath = new File(uri).getAbsolutePath(); } else { scriptPath = uri.toString(); } } else if (editorInput instanceof INonPersistentEditorInput) { scriptPath = "SQL Console"; } else { scriptPath = editorInput.getName(); if (CommonUtils.isEmpty(scriptPath)) { scriptPath = "<not a file>"; } } return "Script: " + scriptPath + " \nConnection: " + dataSourceContainer.getName() + " \nType: " + (dataSourceContainer.getDriver().getFullName()) + " \nURL: " + dataSourceContainer.getConnectionConfiguration().getUrl(); } private String getEditorName() { final IFile file = EditorUtils.getFileFromInput(getEditorInput()); String scriptName; if (file != null) { scriptName = file.getFullPath().removeFileExtension().lastSegment(); } else { File localFile = EditorUtils.getLocalFileFromInput(getEditorInput()); if (localFile != null) { return localFile.getName(); } else { scriptName = getEditorInput().getName(); } } DBPDataSourceContainer dataSourceContainer = getDataSourceContainer(); DBPPreferenceStore preferenceStore = getActivePreferenceStore(); String pattern = preferenceStore.getString(DBeaverPreferences.SCRIPT_TITLE_PATTERN); Map<String, Object> vars = new HashMap<>(); vars.put(VAR_CONNECTION_NAME, dataSourceContainer == null ? "none" : dataSourceContainer.getName()); vars.put(VAR_FILE_NAME, scriptName); vars.put(VAR_FILE_EXT, file == null ? "" : file.getFullPath().getFileExtension()); vars.put(VAR_DRIVER_NAME, dataSourceContainer == null ? "?" : dataSourceContainer.getDriver().getFullName()); return GeneralUtils.replaceVariables(pattern, new GeneralUtils.MapResolver(vars)); } @Override public void setFocus() { super.setFocus(); } public void explainQueryPlan() { final SQLScriptElement scriptElement = extractActiveQuery(); if (scriptElement == null) { setStatus(CoreMessages.editors_sql_status_empty_query_string, DBPMessageType.ERROR); return; } if (!(scriptElement instanceof SQLQuery)) { setStatus("Can't explain plan for command", DBPMessageType.ERROR); return; } explainQueryPlan((SQLQuery) scriptElement); } private void explainQueryPlan(SQLQuery sqlQuery) { // 1. Determine whether planner supports plan extraction DBCQueryPlanner planner = DBUtils.getAdapter(DBCQueryPlanner.class, getDataSource()); if (planner == null) { UIUtils.showErrorDialog(getSite().getShell(), "Execution plan", "Execution plan explain isn't supported by current datasource"); return; } DBCPlanStyle planStyle = planner.getPlanStyle(); if (planStyle == DBCPlanStyle.QUERY) { explainPlanFromQuery(planner, sqlQuery); return; } ExplainPlanViewer planView = null; for (CTabItem item : resultTabs.getItems()) { if (item.getData() instanceof ExplainPlanViewer) { ExplainPlanViewer pv = (ExplainPlanViewer) item.getData(); if (pv.getQuery() != null && pv.getQuery().equals(sqlQuery)) { resultTabs.setSelection(item); planView = pv; break; } } } if (planView == null) { planView = new ExplainPlanViewer(this, resultTabs); final CTabItem item = new CTabItem(resultTabs, SWT.CLOSE); item.setControl(planView.getControl()); item.setText("Exec. Plan"); item.setToolTipText("Execution plan for\n" + sqlQuery.getText()); item.setImage(IMG_EXPLAIN_PLAN); item.setData(planView); UIUtils.disposeControlOnItemDispose(item); resultTabs.setSelection(item); } try { planView.explainQueryPlan(getExecutionContext(), sqlQuery); } catch (DBCException e) { UIUtils.showErrorDialog( sashForm.getShell(), CoreMessages.editors_sql_error_execution_plan_title, CoreMessages.editors_sql_error_execution_plan_message, e); } } private void explainPlanFromQuery(final DBCQueryPlanner planner, final SQLQuery sqlQuery) { final String[] planQueryString = new String[1]; DBRRunnableWithProgress queryObtainTask = new DBRRunnableWithProgress() { @Override public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.UTIL, "Prepare plan query")) { DBCPlan plan = planner.planQueryExecution(session, sqlQuery.getText()); planQueryString[0] = plan.getPlanQueryString(); } catch (Exception e) { log.error(e); } } }; if (RuntimeUtils.runTask(queryObtainTask, "Retrieve plan query", 5000) && !CommonUtils.isEmpty(planQueryString[0])) { SQLQuery planQuery = new SQLQuery(getDataSource(), planQueryString[0]); processQueries(Collections.<SQLScriptElement>singletonList(planQuery), true, false, true); } } public void processSQL(boolean newTab, boolean script) { processSQL(newTab, script, null); } public void processSQL(boolean newTab, boolean script, SQLQueryTransformer transformer) { IDocument document = getDocument(); if (document == null) { setStatus(CoreMessages.editors_sql_status_cant_obtain_document, DBPMessageType.ERROR); return; } List<SQLScriptElement> elements; if (script) { // Execute all SQL statements consequently ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection(); if (selection.getLength() > 1) { elements = extractScriptQueries(selection.getOffset(), selection.getLength()); } else { elements = extractScriptQueries(0, document.getLength()); } } else { // Execute statement under cursor or selected text (if selection present) SQLScriptElement sqlQuery = extractActiveQuery(); if (sqlQuery == null) { setStatus(CoreMessages.editors_sql_status_empty_query_string, DBPMessageType.ERROR); return; } else { elements = Collections.singletonList(sqlQuery); } } try { if (transformer != null) { DBPDataSource dataSource = getDataSource(); if (dataSource instanceof SQLDataSource) { List<SQLScriptElement> xQueries = new ArrayList<>(elements.size()); for (int i = 0; i < elements.size(); i++) { SQLScriptElement element = elements.get(i); if (element instanceof SQLQuery) { SQLQuery query = transformer.transformQuery((SQLDataSource) dataSource, (SQLQuery) element); if (query != null) { xQueries.add(query); } } else { xQueries.add(element); } } elements = xQueries; } } } catch (DBException e) { UIUtils.showErrorDialog(getSite().getShell(), "Bad query", "Can't execute query", e); return; } processQueries(elements, newTab, false, true); } public void exportDataFromQuery() { SQLScriptElement sqlQuery = extractActiveQuery(); if (sqlQuery instanceof SQLQuery) { processQueries(Collections.singletonList(sqlQuery), false, true, true); } else { UIUtils.showErrorDialog( getSite().getShell(), "Extract data", "Can't extract data from control command"); } } private void processQueries(@NotNull final List<SQLScriptElement> queries, final boolean newTab, final boolean export, final boolean checkSession) { if (queries.isEmpty()) { // Nothing to process return; } final DBPDataSourceContainer container = getDataSourceContainer(); if (checkSession) { try { DBRProgressListener connectListener = new DBRProgressListener() { @Override public void onTaskFinished(IStatus status) { if (!status.isOK() || container == null || !container.isConnected()) { UIUtils.showErrorDialog( getSite().getShell(), CoreMessages.editors_sql_error_cant_obtain_session, null, status); return; } // Make a small pause to let all UI connection listeners to finish try { Thread.sleep(100); } catch (InterruptedException e) { // it's ok } DBeaverUI.syncExec(new Runnable() { @Override public void run() { processQueries(queries, newTab, export, false); } }); } }; if (!checkSession(connectListener)) { return; } } catch (DBException ex) { ResultSetViewer viewer = getActiveResultSetViewer(); if (viewer != null) { viewer.setStatus(ex.getMessage(), DBPMessageType.ERROR); } UIUtils.showErrorDialog( getSite().getShell(), CoreMessages.editors_sql_error_cant_obtain_session, ex.getMessage()); return; } } if (dataSourceContainer == null) { return; } if (sashForm.getMaximizedControl() != null) { sashForm.setMaximizedControl(null); } // Save editor if (getActivePreferenceStore().getBoolean(SQLPreferenceConstants.AUTO_SAVE_ON_EXECUTE) && isDirty()) { doSave(new NullProgressMonitor()); } final boolean isSingleQuery = (queries.size() == 1); if (!newTab || !isSingleQuery) { // We don't need new tab or we are executing a script - so close all extra tabs closeExtraResultTabs(null); } if (newTab) { // Execute each query in a new tab for (int i = 0; i < queries.size(); i++) { SQLScriptElement query = queries.get(i); QueryProcessor queryProcessor = (i == 0 && !isSingleQuery ? curQueryProcessor : createQueryProcessor(queries.size() == 1)); queryProcessor.processQueries(Collections.singletonList(query), true, export); } } else { // Use current tab. // If current tab was pinned then use first tab final QueryResultsContainer firstResults = curQueryProcessor.getFirstResults(); if (firstResults.isPinned()) { curQueryProcessor = queryProcessors.get(0); } closeExtraResultTabs(curQueryProcessor); if (firstResults.tabItem != null) { // Do not switch tab if Output tab is active CTabItem selectedTab = resultTabs.getSelection(); if (selectedTab == null || selectedTab.getData() != outputViewer) { resultTabs.setSelection(firstResults.tabItem); } } curQueryProcessor.processQueries(queries, false, export); } } private List<SQLScriptElement> extractScriptQueries(int startOffset, int length) { List<SQLScriptElement> queryList = new ArrayList<>(); IDocument document = getDocument(); if (document == null) { return queryList; } this.startScriptEvaluation(); try { for (int queryOffset = startOffset; ; ) { SQLScriptElement query = parseQuery(document, queryOffset, startOffset + length, queryOffset, true); if (query == null) { break; } queryList.add(query); queryOffset = query.getOffset() + query.getLength(); } } finally { this.endScriptEvaluation(); } if (getActivePreferenceStore().getBoolean(ModelPreferences.SQL_PARAMETERS_ENABLED)) { // Parse parameters for (SQLScriptElement query : queryList) { if (query instanceof SQLQuery) { ((SQLQuery)query).setParameters(parseParameters(getDocument(), (SQLQuery) query)); } } } return queryList; } private void setStatus(String status, DBPMessageType messageType) { ResultSetViewer resultsView = getActiveResultSetViewer(); if (resultsView != null) { resultsView.setStatus(status, messageType); } } private void closeExtraResultTabs(@Nullable QueryProcessor queryProcessor) { // Close all tabs except first one for (int i = resultTabs.getItemCount() - 1; i > 0; i--) { CTabItem item = resultTabs.getItem(i); if (item.getData() instanceof QueryResultsContainer && item.getShowClose()) { QueryResultsContainer resultsProvider = (QueryResultsContainer)item.getData(); if (queryProcessor != null && queryProcessor != resultsProvider.queryProcessor) { continue; } if (queryProcessor != null && queryProcessor.resultContainers.size() < 2) { // Do not remove first tab for this processor continue; } item.dispose(); } } } private boolean checkSession(DBRProgressListener onFinish) throws DBException { DBPDataSourceContainer ds = getDataSourceContainer(); if (ds == null) { throw new DBException("No active connection"); } if (!ds.isConnected()) { boolean doConnect = ds.getPreferenceStore().getBoolean(DBeaverPreferences.EDITOR_CONNECT_ON_EXECUTE); if (doConnect) { return checkConnected(true, onFinish); } else { throw new DBException("Disconnected from database"); } } return true; } private void onDataSourceChange() { updateExecutionContext(); if (sashForm == null || sashForm.isDisposed()) { reloadSyntaxRules(); return; } DatabaseEditorUtils.setPartBackground(this, sashForm); DBCExecutionContext executionContext = getExecutionContext(); if (syntaxLoaded && lastExecutionContext == executionContext) { return; } for (QueryProcessor queryProcessor : queryProcessors) { for (QueryResultsContainer resultsProvider : queryProcessor.getResultContainers()) { ResultSetViewer rsv = resultsProvider.getResultSetController(); if (rsv != null) { if (executionContext == null) { rsv.setStatus(CoreMessages.editors_sql_status_not_connected_to_database); } else { rsv.setStatus(CoreMessages.editors_sql_staus_connected_to + executionContext.getDataSource().getContainer().getName() + "'"); //$NON-NLS-2$ } } } } // Update command states SQLEditorPropertyTester.firePropertyChange(SQLEditorPropertyTester.PROP_CAN_EXECUTE); SQLEditorPropertyTester.firePropertyChange(SQLEditorPropertyTester.PROP_CAN_EXPLAIN); reloadSyntaxRules(); if (getDataSourceContainer() == null) { sashForm.setMaximizedControl(editorControl); } else { sashForm.setMaximizedControl(null); } lastExecutionContext = executionContext; syntaxLoaded = true; } @Override public void beforeConnect() { } @Override public void beforeDisconnect() { closeAllJobs(); } @Override public void dispose() { // Release ds container releaseContainer(); closeAllJobs(); final IEditorInput editorInput = getEditorInput(); IFile sqlFile = EditorUtils.getFileFromInput(editorInput); logViewer = null; outputViewer = null; queryProcessors.clear(); curResultsContainer = null; curQueryProcessor = null; super.dispose(); if (sqlFile != null && !PlatformUI.getWorkbench().isClosing()) { deleteFileIfEmpty(sqlFile); } } private void deleteFileIfEmpty(IFile sqlFile) { if (sqlFile == null || !sqlFile.exists()) { return; } if (!getActivePreferenceStore().getBoolean(DBeaverPreferences.SCRIPT_DELETE_EMPTY)) { return; } File osFile = sqlFile.getLocation().toFile(); if (!osFile.exists() || osFile.length() != 0) { // Not empty return; } try { IProgressMonitor monitor = new NullProgressMonitor(); IFileState[] fileHistory = sqlFile.getHistory(monitor); if (!ArrayUtils.isEmpty(fileHistory)) { for (IFileState historyItem : fileHistory) { try (InputStream contents = historyItem.getContents()) { int cValue = contents.read(); if (cValue != -1) { // At least once there was some content saved return; } } } } // This file is empty and never (at least during this session) had any contents. // Drop it. log.debug("Delete empty SQL script '" + sqlFile.getFullPath().toOSString() + "'"); sqlFile.delete(true, monitor); } catch (Exception e) { log.error("Can't delete empty script file", e); //$NON-NLS-1$ } } private void closeAllJobs() { for (QueryProcessor queryProcessor : queryProcessors) { queryProcessor.closeJob(); } } private int getTotalQueryRunning() { int jobsRunning = 0; for (QueryProcessor queryProcessor : queryProcessors) { jobsRunning += queryProcessor.curJobRunning.get(); } return jobsRunning; } @Override public void handleDataSourceEvent(final DBPEvent event) { if (event.getObject() == getDataSourceContainer()) { DBeaverUI.asyncExec( new Runnable() { @Override public void run() { switch (event.getAction()) { case OBJECT_REMOVE: getSite().getWorkbenchWindow().getActivePage().closeEditor(SQLEditor.this, false); break; default: break; } onDataSourceChange(); } } ); } } @Override public void doSave(IProgressMonitor monitor) { monitor.beginTask("Save data changes...", 1); try { monitor.subTask("Save '" + getPartName() + "' changes..."); SaveJob saveJob = new SaveJob(); saveJob.schedule(); // Wait until job finished UIUtils.waitJobCompletion(saveJob); if (!saveJob.success) { monitor.setCanceled(true); return; } } finally { monitor.done(); } super.doSave(monitor); } @Override public boolean isSaveAsAllowed() { return true; } @Override public void doSaveAs() { saveToExternalFile(); } @Override public int promptToSaveOnClose() { int jobsRunning = getTotalQueryRunning(); if (jobsRunning > 0) { log.warn("There are " + jobsRunning + " SQL job(s) still running in the editor"); // MessageBox messageBox = new MessageBox(getSite().getShell(), SWT.ICON_WARNING | SWT.OK); // messageBox.setMessage(CoreMessages.editors_sql_save_on_close_message); // messageBox.setText(CoreMessages.editors_sql_save_on_close_text); // messageBox.open(); // return ISaveablePart2.CANCEL; } for (QueryProcessor queryProcessor : queryProcessors) { for (QueryResultsContainer resultsProvider : queryProcessor.getResultContainers()) { ResultSetViewer rsv = resultsProvider.getResultSetController(); if (rsv != null && rsv.isDirty()) { return rsv.promptToSaveOnClose(); } } } // End transaction if (!DataSourceHandler.checkAndCloseActiveTransaction(new DBCExecutionContext[] {executionContext})) { return ISaveablePart2.CANCEL; } if (isNonPersistentEditor()) { return ISaveablePart2.NO; } if (getActivePreferenceStore().getBoolean(SQLPreferenceConstants.AUTO_SAVE_ON_CLOSE)) { return ISaveablePart2.YES; } return ISaveablePart2.DEFAULT; } protected void afterSaveToFile(File saveFile) { try { IFileStore fileStore = EFS.getStore(saveFile.toURI()); IEditorInput input = new FileStoreEditorInput(fileStore); EditorUtils.setInputDataSource(input, getDataSourceContainer(), false); init(getEditorSite(), input); } catch (CoreException e) { UIUtils.showErrorDialog(getSite().getShell(), "File save", "Can't open SQL editor from external file", e); } } @Nullable private ResultSetViewer getActiveResultSetViewer() { if (curResultsContainer != null) { return curResultsContainer.getResultSetController(); } return null; } private void showScriptPositionRuler(boolean show) { IColumnSupport columnSupport = (IColumnSupport) getAdapter(IColumnSupport.class); if (columnSupport != null) { RulerColumnDescriptor positionColumn = RulerColumnRegistry.getDefault().getColumnDescriptor(ScriptPositionColumn.ID); columnSupport.setColumnVisible(positionColumn, show); } } private void showStatementInEditor(final SQLQuery query, final boolean select) { DBeaverUI.runUIJob("Select SQL query in editor", new DBRRunnableWithProgress() { @Override public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException { if (select) { selectAndReveal(query.getOffset(), query.getLength()); setStatus(query.getText(), DBPMessageType.INFORMATION); } else { getSourceViewer().revealRange(query.getOffset(), query.getLength()); } } }); } @Override public void reloadSyntaxRules() { super.reloadSyntaxRules(); if (outputViewer != null) { outputViewer.refreshStyles(); } } private QueryProcessor createQueryProcessor(boolean setSelection) { final QueryProcessor queryProcessor = new QueryProcessor(); curQueryProcessor = queryProcessor; curResultsContainer = queryProcessor.getFirstResults(); if (setSelection && curResultsContainer.tabItem != null) { resultTabs.setSelection(curResultsContainer.tabItem); } return queryProcessor; } @Override public void preferenceChange(PreferenceChangeEvent event) { if (event.getProperty().equals(ModelPreferences.SCRIPT_STATEMENT_DELIMITER) || event.getProperty().equals(ModelPreferences.SCRIPT_IGNORE_NATIVE_DELIMITER) || event.getProperty().equals(ModelPreferences.SCRIPT_STATEMENT_DELIMITER_BLANK) || event.getProperty().equals(ModelPreferences.SQL_PARAMETERS_ENABLED) || event.getProperty().equals(ModelPreferences.SQL_ANONYMOUS_PARAMETERS_MARK) || event.getProperty().equals(ModelPreferences.SQL_ANONYMOUS_PARAMETERS_ENABLED) || event.getProperty().equals(ModelPreferences.SQL_NAMED_PARAMETERS_PREFIX)) { reloadSyntaxRules(); } } public class QueryProcessor implements SQLResultsConsumer { private SQLQueryJob curJob; private AtomicInteger curJobRunning = new AtomicInteger(0); private final List<QueryResultsContainer> resultContainers = new ArrayList<>(); private DBDDataReceiver curDataReceiver = null; QueryProcessor() { // Create first (default) results provider queryProcessors.add(this); createResultsProvider(0); } private QueryResultsContainer createResultsProvider(int resultSetNumber) { QueryResultsContainer resultsProvider = new QueryResultsContainer(this, resultSetNumber); resultContainers.add(resultsProvider); return resultsProvider; } @NotNull QueryResultsContainer getFirstResults() { return resultContainers.get(0); } @Nullable QueryResultsContainer getResults(SQLQuery query) { for (QueryResultsContainer provider : resultContainers) { if (provider.query == query) { return provider; } } return null; } List<QueryResultsContainer> getResultContainers() { return resultContainers; } private void closeJob() { final SQLQueryJob job = curJob; if (job != null) { if (job.getState() == Job.RUNNING) { job.cancel(); } curJob.close(); curJob = null; } } void processQueries(final List<SQLScriptElement> queries, final boolean fetchResults, boolean export) { if (queries.isEmpty()) { // Nothing to process return; } if (curJobRunning.get() > 0) { UIUtils.showErrorDialog( getSite().getShell(), CoreMessages.editors_sql_error_cant_execute_query_title, CoreMessages.editors_sql_error_cant_execute_query_message); return; } final DBCExecutionContext executionContext = getExecutionContext(); if (executionContext == null) { UIUtils.showErrorDialog( getSite().getShell(), CoreMessages.editors_sql_error_cant_execute_query_title, CoreMessages.editors_sql_status_not_connected_to_database); return; } final boolean isSingleQuery = (queries.size() == 1); // Prepare execution job { showScriptPositionRuler(true); QueryResultsContainer resultsContainer = getFirstResults(); SQLQueryListener listener = new SQLEditorQueryListener(this); final SQLQueryJob job = new SQLQueryJob( getSite(), isSingleQuery ? CoreMessages.editors_sql_job_execute_query : CoreMessages.editors_sql_job_execute_script, executionContext, resultsContainer, queries, scriptContext, this, listener); if (export || isSingleQuery) { resultsContainer.query = queries.get(0); } if (export) { // Assign current job from active query and open wizard resultsContainer.lastGoodQuery = null; curJob = job; ActiveWizardDialog dialog = new ActiveWizardDialog( getSite().getWorkbenchWindow(), new DataTransferWizard( new IDataTransferProducer[] { new DatabaseTransferProducer(resultsContainer, null)}, null), new StructuredSelection(this)); dialog.open(); } else if (isSingleQuery) { closeJob(); curJob = job; ResultSetViewer rsv = resultsContainer.getResultSetController(); if (rsv != null) { rsv.resetDataFilter(false); rsv.resetHistory(); rsv.refresh(); } } else { if (fetchResults) { job.setFetchResultSets(true); } job.schedule(); curJob = job; } } } public boolean isDirty() { for (QueryResultsContainer resultsProvider : resultContainers) { ResultSetViewer rsv = resultsProvider.getResultSetController(); if (rsv != null && rsv.isDirty()) { return true; } } return false; } void removeResults(QueryResultsContainer resultsContainer) { if (resultContainers.size() > 1) { resultContainers.remove(resultsContainer); } else { queryProcessors.remove(this); if (curQueryProcessor == this) { if (queryProcessors.isEmpty()) { curQueryProcessor = null; curResultsContainer = null; } else { curQueryProcessor = queryProcessors.get(0); curResultsContainer = curQueryProcessor.getFirstResults(); } } } } @Nullable @Override public DBDDataReceiver getDataReceiver(final SQLQuery statement, final int resultSetNumber) { if (curDataReceiver != null) { return curDataReceiver; } final boolean isStatsResult = (statement != null && statement.getData() == SQLQueryJob.STATS_RESULTS); // if (isStatsResult) { // // Maybe it was already open // for (QueryResultsProvider provider : resultContainers) { // if (provider.query != null && provider.query.getData() == SQLQueryJob.STATS_RESULTS) { // resultSetNumber = provider.resultSetNumber; // break; // } // } // } if (resultSetNumber >= resultContainers.size() && !isDisposed()) { // Open new results processor in UI thread DBeaverUI.syncExec(new Runnable() { @Override public void run() { createResultsProvider(resultSetNumber); } }); } if (resultSetNumber >= resultContainers.size()) { // Editor seems to be disposed - no data receiver return null; } final QueryResultsContainer resultsProvider = resultContainers.get(resultSetNumber); // Open new results processor in UI thread DBeaverUI.syncExec(new Runnable() { @Override public void run() { if (statement != null && !resultTabs.isDisposed()) { resultsProvider.query = statement; resultsProvider.lastGoodQuery = statement; String tabName = null; String toolTip = CommonUtils.truncateString(statement.getText(), 1000); // Special statements (not real statements) have their name in data if (isStatsResult) { tabName = "Statistics"; int queryIndex = queryProcessors.indexOf(QueryProcessor.this); if (queryIndex > 0) { tabName += " - " + (queryIndex + 1); } } resultsProvider.updateResultsName(tabName, toolTip); } } }); ResultSetViewer rsv = resultsProvider.getResultSetController(); return rsv == null ? null : rsv.getDataReceiver(); } } public class QueryResultsContainer implements DBSDataContainer, IResultSetContainer, IResultSetListener, IDataSourceContainerProvider { private final QueryProcessor queryProcessor; private final CTabItem tabItem; private final ResultSetViewer viewer; private final int resultSetNumber; private SQLScriptElement query = null; private SQLScriptElement lastGoodQuery = null; private QueryResultsContainer(QueryProcessor queryProcessor, int resultSetNumber) { this.queryProcessor = queryProcessor; this.resultSetNumber = resultSetNumber; boolean detachedViewer = false; SQLResultsView sqlView = null; if (detachedViewer) { try { sqlView = (SQLResultsView) getSite().getPage().showView(SQLResultsView.VIEW_ID, null, IWorkbenchPage.VIEW_CREATE); } catch (Throwable e) { UIUtils.showErrorDialog(getSite().getShell(), "Detached results", "Can't open results view", e); } } if (sqlView != null) { // Detached results viewer sqlView.setContainer(this); this.viewer = sqlView.getViewer(); this.tabItem = null; } else { // Embedded results viewer this.viewer = new ResultSetViewer(resultTabs, getSite(), this); this.viewer.addListener(this); int tabCount = resultTabs.getItemCount(); int tabIndex = 0; for (int i = tabCount; i > 0; i--) { if (resultTabs.getItem(i - 1).getData() instanceof QueryResultsContainer) { tabIndex = i; break; } } tabItem = new CTabItem(resultTabs, SWT.NONE, tabIndex); int queryIndex = queryProcessors.indexOf(queryProcessor); String tabName = getResultsTabName(resultSetNumber, queryIndex, null); tabItem.setText(tabName); tabItem.setImage(IMG_DATA_GRID); tabItem.setData(this); if (queryIndex > 0 || resultSetNumber > 0) { tabItem.setShowClose(true); } tabItem.setControl(viewer.getControl()); UIUtils.disposeControlOnItemDispose(tabItem); } viewer.getControl().addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { QueryResultsContainer.this.queryProcessor.removeResults(QueryResultsContainer.this); if (QueryResultsContainer.this == curResultsContainer) { curResultsContainer = null; } } }); } void updateResultsName(String resultSetName, String toolTip) { if (tabItem != null && !tabItem.isDisposed()) { if (!CommonUtils.isEmpty(resultSetName)) { tabItem.setText(resultSetName); } tabItem.setToolTipText(toolTip); } } boolean isPinned() { return tabItem != null && resultTabs.indexOf(tabItem) > 0 && !tabItem.getShowClose(); } void setPinned(boolean pinned) { tabItem.setShowClose(!pinned); tabItem.setImage(pinned ? IMG_DATA_GRID_LOCKED : IMG_DATA_GRID); } @Override public DBCExecutionContext getExecutionContext() { return SQLEditor.this.getExecutionContext(); } @Nullable @Override public ResultSetViewer getResultSetController() { return viewer; } @Nullable @Override public DBSDataContainer getDataContainer() { return this; } @Override public boolean isReadyToRun() { return queryProcessor.curJob == null || queryProcessor.curJobRunning.get() == 0; } @Override public int getSupportedFeatures() { int features = DATA_SELECT; features |= DATA_COUNT; if (getQueryResultCounts() <= 1) { features |= DATA_FILTER; } return features; } @NotNull @Override public DBCStatistics readData(@NotNull DBCExecutionSource source, @NotNull DBCSession session, @NotNull DBDDataReceiver dataReceiver, DBDDataFilter dataFilter, long firstRow, long maxRows, long flags) throws DBCException { final SQLQueryJob job = queryProcessor.curJob; if (job == null) { throw new DBCException("No active query - can't read data"); } try { if (dataReceiver != viewer.getDataReceiver()) { // Some custom receiver. Probably data export queryProcessor.curDataReceiver = dataReceiver; } else { queryProcessor.curDataReceiver = null; } // Count number of results for this query. If > 1 then we will refresh them all at once int resultCounts = getQueryResultCounts(); if (resultCounts <= 1 && resultSetNumber > 0) { job.setFetchResultSetNumber(resultSetNumber); } else { job.setFetchResultSetNumber(-1); } job.setResultSetLimit(firstRow, maxRows); job.setDataFilter(dataFilter); job.extractData(session, query, resultCounts > 1 ? 0 : resultSetNumber); lastGoodQuery = job.getLastGoodQuery(); return job.getStatistics(); } finally { // Nullify custom data receiver queryProcessor.curDataReceiver = null; } } private int getQueryResultCounts() { int resultCounts = 0; for (QueryResultsContainer qrc : queryProcessor.resultContainers) { if (qrc.query == query) { resultCounts++; } } return resultCounts; } @Override public long countData(@NotNull DBCExecutionSource source, @NotNull DBCSession session, DBDDataFilter dataFilter) throws DBCException { DBPDataSource dataSource = getDataSource(); if (!(dataSource instanceof SQLDataSource)) { throw new DBCException("Query transform is not supported by datasource"); } if (!(query instanceof SQLQuery)) { throw new DBCException("Can't count rows for control command"); } try { SQLQuery countQuery = new SQLQueryTransformerCount().transformQuery((SQLDataSource) dataSource, (SQLQuery) query); try (DBCStatement dbStatement = DBUtils.makeStatement(source, session, DBCStatementType.QUERY, countQuery, 0, 0)) { if (dbStatement.executeStatement()) { try (DBCResultSet rs = dbStatement.openResultSet()) { if (rs.nextRow()) { Object countValue = rs.getAttributeValue(0); if (countValue instanceof Number) { return ((Number) countValue).longValue(); } else { throw new DBCException("Unexpected row count value: " + countValue); } } else { throw new DBCException("Row count result is empty"); } } } else { throw new DBCException("Row count query didn't return any value"); } } } catch (DBException e) { throw new DBCException("Error executing row count", e); } } @Nullable @Override public String getDescription() { return CoreMessages.editors_sql_description; } @Nullable @Override public DBSObject getParentObject() { return getDataSourceContainer(); } @Nullable @Override public DBPDataSource getDataSource() { return SQLEditor.this.getDataSource(); } @Override public boolean isPersisted() { return true; } @NotNull @Override public String getName() { String name = lastGoodQuery != null ? lastGoodQuery.getOriginalText() : (query == null ? null : query.getOriginalText()); if (name == null) { name = "SQL"; } return name; } @Nullable @Override public DBPDataSourceContainer getDataSourceContainer() { return SQLEditor.this.getDataSourceContainer(); } @Override public String toString() { return query == null ? "SQL Query / " + SQLEditor.this.getEditorInput().getName() : query.getOriginalText(); } @Override public void handleResultSetLoad() { } @Override public void handleResultSetChange() { updateDirtyFlag(); } } private String getResultsTabName(int resultSetNumber, int queryIndex, String name) { String tabName = name; if (CommonUtils.isEmpty(tabName)) { tabName = CoreMessages.editors_sql_data_grid; } if (resultSetNumber > 0) { tabName += " - " + (resultSetNumber + 1); } else if (queryIndex > 0) { tabName += " - " + (queryIndex + 1); } return tabName; } private class SQLEditorQueryListener implements SQLQueryListener { private final QueryProcessor queryProcessor; private boolean scriptMode; private long lastUIUpdateTime; private final ITextSelection originalSelection = (ITextSelection) getSelectionProvider().getSelection(); private int topOffset, visibleLength; private SQLEditorQueryListener(QueryProcessor queryProcessor) { this.queryProcessor = queryProcessor; } @Override public void onStartScript() { lastUIUpdateTime = -1; scriptMode = true; DBeaverUI.syncExec(new Runnable() { @Override public void run() { sashForm.setMaximizedControl(editorControl); } }); } @Override public void onStartQuery(DBCSession session, final SQLQuery query) { boolean isInExecute = getTotalQueryRunning() > 0; if (!isInExecute) { setTitleImage(DBeaverIcons.getImage(UIIcon.SQL_SCRIPT_EXECUTE)); } queryProcessor.curJobRunning.incrementAndGet(); synchronized (runningQueries) { runningQueries.add(query); } if (lastUIUpdateTime < 0 || System.currentTimeMillis() - lastUIUpdateTime > SCRIPT_UI_UPDATE_PERIOD) { DBeaverUI.syncExec(new Runnable() { @Override public void run() { TextViewer textViewer = getTextViewer(); if (textViewer != null) { topOffset = textViewer.getTopIndexStartOffset(); visibleLength = textViewer.getBottomIndexEndOffset() - topOffset; } } }); if (scriptMode) { showStatementInEditor(query, false); } lastUIUpdateTime = System.currentTimeMillis(); } } @Override public void onEndQuery(final DBCSession session, final SQLQueryResult result) { synchronized (runningQueries) { runningQueries.remove(result.getStatement()); } queryProcessor.curJobRunning.decrementAndGet(); if (getTotalQueryRunning() <= 0) { setTitleImage(editorImage); } if (isDisposed()) { return; } DBeaverUI.runUIJob("Process SQL query result", new DBRRunnableWithProgress() { @Override public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException { // Finish query processQueryResult(session, result); // Update dirty flag updateDirtyFlag(); } }); } private void processQueryResult(DBCSession session, SQLQueryResult result) { if (!scriptMode) { runPostExecuteActions(result); } SQLQuery query = result.getStatement(); Throwable error = result.getError(); if (error != null) { setStatus(GeneralUtils.getFirstMessage(error), DBPMessageType.ERROR); if (!scrollCursorToError(session.getProgressMonitor(), query, error)) { int errorQueryOffset = query.getOffset(); int errorQueryLength = query.getLength(); if (errorQueryOffset >= 0 && errorQueryLength > 0) { if (scriptMode) { getSelectionProvider().setSelection(new TextSelection(errorQueryOffset, errorQueryLength)); } else { getSelectionProvider().setSelection(originalSelection); } } } } else if (!scriptMode && getActivePreferenceStore().getBoolean(SQLPreferenceConstants.RESET_CURSOR_ON_EXECUTE)) { getSelectionProvider().setSelection(originalSelection); } // Get results window (it is possible that it was closed till that moment { for (QueryResultsContainer cr : queryProcessor.resultContainers) { cr.viewer.updateFiltersText(false); } // Set tab name only if we have just one resultset // If query produced multiple results - leave their names as is if (scriptMode || queryProcessor.getResultContainers().size() == 1) { QueryResultsContainer results = queryProcessor.getResults(query); if (results != null) { int queryIndex = queryProcessors.indexOf(queryProcessor); String resultSetName = getResultsTabName(results.resultSetNumber, queryIndex, result.getResultSetName()); results.updateResultsName(resultSetName, null); } } } if (dataSourceContainer != null && !scriptMode && dataSourceContainer.getPreferenceStore().getBoolean(SQLPreferenceConstants.BEEP_ON_QUERY_END)) { Display.getCurrent().beep(); } if (result.getQueryTime() > DBeaverCore.getGlobalPreferenceStore().getLong(DBeaverPreferences.AGENT_LONG_OPERATION_TIMEOUT) * 1000) { DBeaverUI.notifyAgent( "Query completed [" + getEditorInput().getName() + "]" + GeneralUtils.getDefaultLineSeparator() + CommonUtils.truncateString(query.getText(), 200), !result.hasError() ? IStatus.INFO : IStatus.ERROR); } } @Override public void onEndScript(final DBCStatistics statistics, final boolean hasErrors) { if (isDisposed()) { return; } runPostExecuteActions(null); DBeaverUI.syncExec(new Runnable() { @Override public void run() { sashForm.setMaximizedControl(null); if (!hasErrors) { getSelectionProvider().setSelection(originalSelection); } QueryResultsContainer results = queryProcessor.getFirstResults(); ResultSetViewer viewer = results.getResultSetController(); if (viewer != null) { viewer.getModel().setStatistics(statistics); viewer.updateStatusMessage(); } } }); } } public void updateDirtyFlag() { firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY); } private class FindReplaceTarget extends DynamicFindReplaceTarget { private boolean lastFocusInEditor = true; @Override public IFindReplaceTarget getTarget() { ResultSetViewer rsv = getActiveResultSetViewer(); TextViewer textViewer = getTextViewer(); boolean focusInEditor = textViewer != null && textViewer.getTextWidget().isFocusControl(); if (!focusInEditor) { if (rsv != null && rsv.getActivePresentation().getControl().isFocusControl()) { focusInEditor = false; } else { focusInEditor = lastFocusInEditor; } } lastFocusInEditor = focusInEditor; if (!focusInEditor && rsv != null) { IFindReplaceTarget nested = rsv.getAdapter(IFindReplaceTarget.class); if (nested != null) { return nested; } } else if (textViewer != null) { return textViewer.getFindReplaceTarget(); } return null; } } private class DynamicSelectionProvider extends CompositeSelectionProvider { private boolean lastFocusInEditor = true; @Override public ISelectionProvider getProvider() { ResultSetViewer rsv = getActiveResultSetViewer(); TextViewer textViewer = getTextViewer(); boolean focusInEditor = textViewer != null && textViewer.getTextWidget().isFocusControl(); if (!focusInEditor) { if (rsv != null && rsv.getActivePresentation().getControl().isFocusControl()) { focusInEditor = false; } else { focusInEditor = lastFocusInEditor; } } lastFocusInEditor = focusInEditor; if (!focusInEditor && rsv != null) { return rsv; } else if (textViewer != null) { return textViewer.getSelectionProvider(); } else { return null; } } } private void runPostExecuteActions(@Nullable SQLQueryResult result) { final DBCExecutionContext executionContext = getExecutionContext(); if (executionContext != null) { final DBPDataSource dataSource = executionContext.getDataSource(); // Dump server output DBCServerOutputReader outputReader = DBUtils.getAdapter(DBCServerOutputReader.class, dataSource); if (outputReader == null && result != null) { outputReader = new DefaultServerOutputReader(result); } if (outputReader != null && outputReader.isServerOutputEnabled()) { dumpServerOutput(executionContext, outputReader); } // Refresh active object if (result == null || !result.hasError() && getActivePreferenceStore().getBoolean(SQLPreferenceConstants.REFRESH_DEFAULTS_AFTER_EXECUTE)) { final DBSObjectSelector objectSelector = DBUtils.getAdapter(DBSObjectSelector.class, dataSource); if (objectSelector != null) { new AbstractJob("Refresh default object") { @Override protected IStatus run(DBRProgressMonitor monitor) { try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.UTIL, "Refresh default object")) { objectSelector.refreshDefaultObject(session); } catch (Exception e) { log.error(e); } return Status.OK_STATUS; } }.schedule(); } } } } private void dumpServerOutput(@NotNull final DBCExecutionContext executionContext, @NotNull final DBCServerOutputReader outputReader) { new AbstractJob("Dump server output") { @Override protected IStatus run(DBRProgressMonitor monitor) { final StringWriter dump = new StringWriter(); try { outputReader.readServerOutput(monitor, executionContext, new PrintWriter(dump, true)); final String dumpString = dump.toString(); if (!dumpString.isEmpty()) { DBeaverUI.asyncExec(new Runnable() { @Override public void run() { if (outputViewer.isDisposed()) { return; } try { IOUtils.copyText(new StringReader(dumpString), outputViewer.getOutputWriter()); } catch (IOException e) { log.error(e); } if (outputViewer.isHasNewOutput()) { outputViewer.scrollToEnd(); updateOutputViewerIcon(true); } } }); } } catch (Exception e) { log.error(e); } return Status.OK_STATUS; } }.schedule(); } private void updateOutputViewerIcon(boolean alert) { Image image = alert ? IMG_OUTPUT_ALERT : IMG_OUTPUT; CTabItem outputItem = UIUtils.getTabItem(resultTabs, outputViewer); if (outputItem != null && outputItem != resultTabs.getSelection()) { outputItem.setImage(image); } else { toolOutputItem.setImage(image); } } private class SaveJob extends AbstractJob { private transient Boolean success = null; SaveJob() { super("Save '" + getPartName() + "' data changes..."); setUser(true); } @Override protected IStatus run(DBRProgressMonitor monitor) { try { for (QueryProcessor queryProcessor : queryProcessors) { for (QueryResultsContainer resultsProvider : queryProcessor.getResultContainers()) { ResultSetViewer rsv = resultsProvider.getResultSetController(); if (rsv != null && rsv.isDirty()) { rsv.doSave(monitor); } } } success = true; return Status.OK_STATUS; } catch (Throwable e) { success = false; log.error(e); return GeneralUtils.makeExceptionStatus(e); } finally { if (success == null) { success = true; } } } } }