/* * 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.jface.action.*; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.*; import org.eclipse.jface.text.rules.FastPartitioner; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.source.*; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; import org.eclipse.jface.text.source.projection.ProjectionSupport; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.PreferencesUtil; import org.eclipse.ui.internal.editors.text.EditorsPlugin; import org.eclipse.ui.texteditor.*; import org.eclipse.ui.texteditor.templates.ITemplatesPage; import org.eclipse.ui.themes.IThemeManager; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.core.CoreCommands; import org.jkiss.dbeaver.core.DBeaverActivator; import org.jkiss.dbeaver.core.DBeaverCore; import org.jkiss.dbeaver.core.DBeaverUI; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.exec.DBCExecutionContext; import org.jkiss.dbeaver.model.impl.sql.BasicSQLDialect; import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.*; import org.jkiss.dbeaver.ui.ActionUtils; import org.jkiss.dbeaver.ui.ICommentsSupport; import org.jkiss.dbeaver.ui.IErrorVisualizer; import org.jkiss.dbeaver.ui.TextUtils; import org.jkiss.dbeaver.ui.editors.sql.syntax.SQLPartitionScanner; import org.jkiss.dbeaver.ui.editors.sql.syntax.SQLRuleManager; import org.jkiss.dbeaver.ui.editors.sql.syntax.tokens.SQLToken; import org.jkiss.dbeaver.ui.editors.sql.templates.SQLTemplatesPage; import org.jkiss.dbeaver.ui.editors.sql.util.SQLSymbolInserter; import org.jkiss.dbeaver.ui.editors.text.BaseTextEditor; import org.jkiss.dbeaver.ui.preferences.*; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.Pair; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; /** * SQL Executor */ public abstract class SQLEditorBase extends BaseTextEditor implements IErrorVisualizer { static protected final Log log = Log.getLog(SQLEditorBase.class); public static final String SQL_EDITOR_CONTEXT = "org.jkiss.dbeaver.ui.editors.sql"; static { // SQL editor preferences. Do this here because it initializes display // (that's why we can't run it in prefs initializer classes which run before workbench creation) { IPreferenceStore editorStore = EditorsPlugin.getDefault().getPreferenceStore(); editorStore.setDefault(SQLPreferenceConstants.MATCHING_BRACKETS, true); editorStore.setDefault(SQLPreferenceConstants.MATCHING_BRACKETS_COLOR, "128,128,128"); } } @NotNull private final SQLSyntaxManager syntaxManager; @NotNull private final SQLRuleManager ruleManager; private ProjectionSupport projectionSupport; private ProjectionAnnotationModel annotationModel; //private Map<Annotation, Position> curAnnotations; private IAnnotationAccess annotationAccess; private boolean hasVerticalRuler = true; private SQLTemplatesPage templatesPage; private IPropertyChangeListener themeListener; public SQLEditorBase() { super(); syntaxManager = new SQLSyntaxManager(); ruleManager = new SQLRuleManager(syntaxManager); themeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (event.getProperty().equals(IThemeManager.CHANGE_CURRENT_THEME) || event.getProperty().startsWith("org.jkiss.dbeaver.sql.editor")) { reloadSyntaxRules(); // Reconfigure to let comments/strings colors to take effect getSourceViewer().configure(getSourceViewerConfiguration()); } } }; PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener(themeListener); //setDocumentProvider(new SQLDocumentProvider()); setSourceViewerConfiguration(new SQLEditorSourceViewerConfiguration(this, getPreferenceStore())); setKeyBindingScopes(new String[]{TEXT_EDITOR_CONTEXT, SQL_EDITOR_CONTEXT}); //$NON-NLS-1$ } @Nullable public abstract DBCExecutionContext getExecutionContext(); public final DBPDataSource getDataSource() { DBCExecutionContext context = getExecutionContext(); return context == null ? null : context.getDataSource(); } public DBPPreferenceStore getActivePreferenceStore() { if (this instanceof IDataSourceContainerProvider) { DBPDataSourceContainer container = ((IDataSourceContainerProvider) this).getDataSourceContainer(); if (container != null) { return container.getPreferenceStore(); } } DBPDataSource dataSource = getDataSource(); return dataSource == null ? DBeaverCore.getGlobalPreferenceStore() : dataSource.getContainer().getPreferenceStore(); } @NotNull public SQLDialect getSQLDialect() { DBPDataSource dataSource = getDataSource(); // Refresh syntax if (dataSource instanceof SQLDataSource) { return ((SQLDataSource) dataSource).getSQLDialect(); } return BasicSQLDialect.INSTANCE; } public boolean hasAnnotations() { return false; } @NotNull public SQLSyntaxManager getSyntaxManager() { return syntaxManager; } @NotNull public SQLRuleManager getRuleManager() { return ruleManager; } public ProjectionAnnotationModel getAnnotationModel() { return annotationModel; } public SQLEditorSourceViewerConfiguration getViewerConfiguration() { return (SQLEditorSourceViewerConfiguration) super.getSourceViewerConfiguration(); } @Override public void createPartControl(Composite parent) { setRangeIndicator(new DefaultRangeIndicator()); super.createPartControl(new SQLEditorControl(parent, this)); ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); projectionSupport = new ProjectionSupport( viewer, getAnnotationAccess(), getSharedColors()); projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error"); //$NON-NLS-1$ projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning"); //$NON-NLS-1$ projectionSupport.install(); viewer.doOperation(ProjectionViewer.TOGGLE); annotationModel = viewer.getProjectionAnnotationModel(); // Symbol inserter { SQLSymbolInserter symbolInserter = new SQLSymbolInserter(this); DBPPreferenceStore preferenceStore = getActivePreferenceStore(); boolean closeSingleQuotes = preferenceStore.getBoolean(SQLPreferenceConstants.SQLEDITOR_CLOSE_SINGLE_QUOTES); boolean closeDoubleQuotes = preferenceStore.getBoolean(SQLPreferenceConstants.SQLEDITOR_CLOSE_DOUBLE_QUOTES); boolean closeBrackets = preferenceStore.getBoolean(SQLPreferenceConstants.SQLEDITOR_CLOSE_BRACKETS); symbolInserter.setCloseSingleQuotesEnabled(closeSingleQuotes); symbolInserter.setCloseDoubleQuotesEnabled(closeDoubleQuotes); symbolInserter.setCloseBracketsEnabled(closeBrackets); ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer instanceof ITextViewerExtension) { ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(symbolInserter); } } } @Override public void updatePartControl(IEditorInput input) { super.updatePartControl(input); } @Override protected IVerticalRuler createVerticalRuler() { return hasVerticalRuler ? super.createVerticalRuler() : new VerticalRuler(0); } public void setHasVerticalRuler(boolean hasVerticalRuler) { this.hasVerticalRuler = hasVerticalRuler; } protected ISharedTextColors getSharedColors() { return DBeaverUI.getSharedTextColors(); } @Override protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { fAnnotationAccess= getAnnotationAccess(); fOverviewRuler= createOverviewRuler(getSharedColors()); SQLEditorSourceViewer sourceViewer = createSourceViewer(parent, ruler, styles, fOverviewRuler); getSourceViewerDecorationSupport(sourceViewer); return sourceViewer; } protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) { char[] matchChars = {'(', ')', '[', ']', '{', '}'}; //which brackets to match ICharacterPairMatcher matcher; try { matcher = new DefaultCharacterPairMatcher(matchChars, SQLPartitionScanner.SQL_PARTITIONING, true); } catch (Throwable e) { // If we below Eclipse 4.2.1 matcher = new DefaultCharacterPairMatcher(matchChars, SQLPartitionScanner.SQL_PARTITIONING); } support.setCharacterPairMatcher(matcher); support.setMatchingCharacterPainterPreferenceKeys(SQLPreferenceConstants.MATCHING_BRACKETS, SQLPreferenceConstants.MATCHING_BRACKETS_COLOR); super.configureSourceViewerDecorationSupport(support); } @NotNull protected SQLEditorSourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles, IOverviewRuler overviewRuler) { return new SQLEditorSourceViewer( parent, ruler, overviewRuler, true, styles); } @Override protected IAnnotationAccess createAnnotationAccess() { return new SQLMarkerAnnotationAccess(); } /* protected void adjustHighlightRange(int offset, int length) { ISourceViewer viewer = getSourceViewer(); if (viewer instanceof ITextViewerExtension5) { ITextViewerExtension5 extension = (ITextViewerExtension5) viewer; extension.exposeModelRange(new Region(offset, length)); } } */ @Override public Object getAdapter(Class required) { if (projectionSupport != null) { Object adapter = projectionSupport.getAdapter( getSourceViewer(), required); if (adapter != null) return adapter; } if (ITemplatesPage.class.equals(required)) { return getTemplatesPage(); } return super.getAdapter(required); } public SQLTemplatesPage getTemplatesPage() { if (templatesPage == null) templatesPage = new SQLTemplatesPage(this); return templatesPage; } @Override public void dispose() { if (themeListener != null) { PlatformUI.getWorkbench().getThemeManager().removePropertyChangeListener(themeListener); themeListener = null; } super.dispose(); } @Override protected void createActions() { super.createActions(); ResourceBundle bundle = DBeaverActivator.getCoreResourceBundle(); IAction a = new TextOperationAction( bundle, SQLEditorContributor.getActionResourcePrefix(SQLEditorContributor.ACTION_CONTENT_ASSIST_PROPOSAL), this, ISourceViewer.CONTENTASSIST_PROPOSALS); a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); setAction(SQLEditorContributor.ACTION_CONTENT_ASSIST_PROPOSAL, a); a = new TextOperationAction( bundle, SQLEditorContributor.getActionResourcePrefix(SQLEditorContributor.ACTION_CONTENT_ASSIST_TIP), this, ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION); a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_CONTEXT_INFORMATION); setAction(SQLEditorContributor.ACTION_CONTENT_ASSIST_TIP, a); a = new TextOperationAction( bundle, SQLEditorContributor.getActionResourcePrefix(SQLEditorContributor.ACTION_CONTENT_ASSIST_INFORMATION), this, ISourceViewer.INFORMATION); a.setActionDefinitionId(ITextEditorActionDefinitionIds.SHOW_INFORMATION); setAction(SQLEditorContributor.ACTION_CONTENT_ASSIST_INFORMATION, a); a = new TextOperationAction( bundle, SQLEditorContributor.getActionResourcePrefix(SQLEditorContributor.ACTION_CONTENT_FORMAT_PROPOSAL), this, ISourceViewer.FORMAT); a.setActionDefinitionId(CoreCommands.CMD_CONTENT_FORMAT); setAction(SQLEditorContributor.ACTION_CONTENT_FORMAT_PROPOSAL, a); setAction(ITextEditorActionConstants.CONTEXT_PREFERENCES, new Action("Preferences...") { //$NON-NLS-1$ public void run() { Shell shell = getSourceViewer().getTextWidget().getShell(); String[] preferencePages = collectContextMenuPreferencePages(); if (preferencePages.length > 0 && (shell == null || !shell.isDisposed())) PreferencesUtil.createPreferenceDialogOn(shell, preferencePages[0], preferencePages, getEditorInput()).open(); } }); /* // Add the task action to the Edit pulldown menu (bookmark action is 'free') ResourceAction ra = new AddTaskAction(bundle, "AddTask.", this); ra.setHelpContextId(ITextEditorHelpContextIds.ADD_TASK_ACTION); ra.setActionDefinitionId(ITextEditorActionDefinitionIds.ADD_TASK); setAction(IDEActionFactory.ADD_TASK.getId(), ra); */ } @Override public void editorContextMenuAboutToShow(IMenuManager menu) { super.editorContextMenuAboutToShow(menu); menu.add(new Separator("content"));//$NON-NLS-1$ addAction(menu, GROUP_SQL_EXTRAS, SQLEditorContributor.ACTION_CONTENT_ASSIST_PROPOSAL); addAction(menu, GROUP_SQL_EXTRAS, SQLEditorContributor.ACTION_CONTENT_ASSIST_TIP); addAction(menu, GROUP_SQL_EXTRAS, SQLEditorContributor.ACTION_CONTENT_ASSIST_INFORMATION); { MenuManager formatMenu = new MenuManager("Format", "format"); IAction formatAction = getAction(SQLEditorContributor.ACTION_CONTENT_FORMAT_PROPOSAL); if (formatAction != null) { formatMenu.add(formatAction); } formatMenu.add(getAction(ITextEditorActionConstants.UPPER_CASE)); formatMenu.add(getAction(ITextEditorActionConstants.LOWER_CASE)); formatMenu.add(new Separator()); formatMenu.add(ActionUtils.makeCommandContribution(getSite(), "org.jkiss.dbeaver.ui.editors.sql.word.wrap")); formatMenu.add(ActionUtils.makeCommandContribution(getSite(), "org.jkiss.dbeaver.ui.editors.sql.comment.single")); formatMenu.add(ActionUtils.makeCommandContribution(getSite(), "org.jkiss.dbeaver.ui.editors.sql.comment.multi")); menu.insertAfter(GROUP_SQL_ADDITIONS, formatMenu); } } public void reloadSyntaxRules() { // Refresh syntax SQLDialect dialect = getSQLDialect(); syntaxManager.init(dialect, getActivePreferenceStore()); ruleManager.refreshRules(getDataSource(), getEditorInput()); Document document = getDocument(); if (document != null) { IDocumentPartitioner partitioner = new FastPartitioner( new SQLPartitionScanner(dialect), SQLPartitionScanner.SQL_CONTENT_TYPES); partitioner.connect(document); document.setDocumentPartitioner(SQLPartitionScanner.SQL_PARTITIONING, partitioner); ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer(); if (projectionViewer != null && projectionViewer.getAnnotationModel() != null && document.getLength() > 0) { // Refresh viewer //projectionViewer.getTextWidget().redraw(); try { projectionViewer.reinitializeProjection(); } catch (Throwable ex) { // We can catch OutOfMemory here for too big/complex documents log.warn("Can't initialize SQL syntax projection", ex); //$NON-NLS-1$ } } } /* Color fgColor = ruleManager.getColor(SQLConstants.CONFIG_COLOR_TEXT); Color bgColor = ruleManager.getColor(getDataSource() == null ? SQLConstants.CONFIG_COLOR_DISABLED : SQLConstants.CONFIG_COLOR_BACKGROUND); final StyledText textWidget = getTextViewer().getTextWidget(); if (fgColor != null) { textWidget.setForeground(fgColor); } textWidget.setBackground(bgColor); */ // Update configuration if (getSourceViewerConfiguration() instanceof SQLEditorSourceViewerConfiguration) { ((SQLEditorSourceViewerConfiguration) getSourceViewerConfiguration()).onDataSourceChange(); } final IVerticalRuler verticalRuler = getVerticalRuler(); if (verticalRuler != null) { verticalRuler.update(); } } public boolean hasActiveQuery() { Document document = getDocument(); if (document == null) { return false; } ISelectionProvider selectionProvider = getSelectionProvider(); if (selectionProvider == null) { return false; } ITextSelection selection = (ITextSelection) selectionProvider.getSelection(); String selText = selection.getText(); if (CommonUtils.isEmpty(selText) && selection.getOffset() >= 0 && selection.getOffset() < document.getLength()) { try { IRegion lineRegion = document.getLineInformationOfOffset(selection.getOffset()); selText = document.get(lineRegion.getOffset(), lineRegion.getLength()); } catch (BadLocationException e) { log.warn(e); return false; } } return !CommonUtils.isEmptyTrimmed(selText); } @Nullable protected SQLScriptElement extractActiveQuery() { SQLScriptElement element; ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection(); String selText = selection.getText().trim(); selText = SQLUtils.trimQueryStatement(getSyntaxManager(), selText); if (!CommonUtils.isEmpty(selText)) { SQLScriptElement parsedElement = parseQuery(getDocument(), selection.getOffset(), selection.getOffset() + selection.getLength(), selection.getOffset(), false); if (parsedElement instanceof SQLControlCommand) { // This is a command element = parsedElement; } else { // Use selected query as is element = new SQLQuery(getDataSource(), selText, selection.getOffset(), selection.getLength()); } } else if (selection.getOffset() >= 0) { element = extractQueryAtPos(selection.getOffset()); } else { element = null; } // Check query do not ends with delimiter // (this may occur if user selected statement including delimiter) if (element == null || CommonUtils.isEmpty(element.getText())) { return null; } if (element instanceof SQLQuery && getActivePreferenceStore().getBoolean(ModelPreferences.SQL_PARAMETERS_ENABLED)) { ((SQLQuery)element).setParameters(parseParameters(getDocument(), (SQLQuery)element)); } return element; } public SQLScriptElement extractQueryAtPos(int currentPos) { Document document = getDocument(); if (document == null || document.getLength() == 0) { return null; } final int docLength = document.getLength(); IDocumentPartitioner partitioner = document.getDocumentPartitioner(SQLPartitionScanner.SQL_PARTITIONING); if (partitioner != null) { // Move to default partition. We don't want to be in the middle of multi-line comment or string while (currentPos < docLength && isMultiCommentPartition(partitioner, currentPos)) { currentPos++; } } // Extract part of document between empty lines int startPos = 0; boolean useBlankLines = syntaxManager.isBlankLineDelimiter(); final String[] statementDelimiters = syntaxManager.getStatementDelimiters(); try { int currentLine = document.getLineOfOffset(currentPos); int lineOffset = document.getLineOffset(currentLine); if (TextUtils.isEmptyLine(document, currentLine)) { return null; } int firstLine = currentLine; while (firstLine > 0) { if (useBlankLines) { if (TextUtils.isEmptyLine(document, firstLine) && isDefaultPartition(partitioner, document.getLineOffset(firstLine))) { break; } } else { for (String delim : statementDelimiters) { final int offset = TextUtils.getOffsetOf(document, firstLine, delim); if (offset >= 0 && isDefaultPartition(partitioner, offset)) { break; } } } firstLine--; } startPos = document.getLineOffset(firstLine); // Move currentPos at line begin currentPos = lineOffset; } catch (BadLocationException e) { log.warn(e); } return parseQuery(document, startPos, document.getLength(), currentPos, false); } public SQLScriptElement extractNextQuery(boolean next) { ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection(); int offset = selection.getOffset(); SQLScriptElement curElement = extractQueryAtPos(offset); if (curElement == null) { return null; } Document document = getDocument(); if (document == null) { return null; } try { int docLength = document.getLength(); int curPos; if (next) { final String[] statementDelimiters = syntaxManager.getStatementDelimiters(); curPos = curElement.getOffset() + curElement.getLength(); while (curPos < docLength) { char c = document.getChar(curPos); if (!Character.isWhitespace(c)) { boolean isDelimiter = false; for (String delim : statementDelimiters) { if (delim.indexOf(c) != -1) { isDelimiter = true; } } if (!isDelimiter) { break; } } curPos++; } } else { curPos = curElement.getOffset() - 1; while (curPos >= 0) { char c = document.getChar(curPos); if (!Character.isWhitespace(c)) { break; } curPos--; } } if (curPos <= 0 || curPos >= docLength) { return null; } return extractQueryAtPos(curPos); } catch (BadLocationException e) { log.warn(e); return null; } } private static boolean isDefaultPartition(IDocumentPartitioner partitioner, int currentPos) { return partitioner == null || IDocument.DEFAULT_CONTENT_TYPE.equals(partitioner.getContentType(currentPos)); } private static boolean isMultiCommentPartition(IDocumentPartitioner partitioner, int currentPos) { return partitioner != null && SQLPartitionScanner.CONTENT_TYPE_SQL_MULTILINE_COMMENT.equals(partitioner.getContentType(currentPos)); } protected void startScriptEvaluation() { ruleManager.startEval(); } protected void endScriptEvaluation() { ruleManager.endEval(); } protected SQLScriptElement parseQuery(final IDocument document, final int startPos, final int endPos, final int currentPos, final boolean scriptMode) { if (endPos - startPos <= 0) { return null; } SQLDialect dialect = getSQLDialect(); // Parse range boolean useBlankLines = !scriptMode && syntaxManager.isBlankLineDelimiter(); ruleManager.setRange(document, startPos, endPos - startPos); int statementStart = startPos; int bracketDepth = 0; boolean hasBlocks = false; boolean hasValuableTokens = false; boolean hasBlockHeader = false; String blockTogglePattern = null; int lastTokenLineFeeds = 0; for (; ; ) { IToken token = ruleManager.nextToken(); int tokenOffset = ruleManager.getTokenOffset(); int tokenLength = ruleManager.getTokenLength(); int tokenType = token instanceof SQLToken ? ((SQLToken)token).getType() : SQLToken.T_UNKNOWN; boolean isDelimiter = tokenType == SQLToken.T_DELIMITER; boolean isControl = false; String delimiterText = null; if (isDelimiter) { // Save delimiter text try { delimiterText = document.get(tokenOffset, tokenLength); } catch (BadLocationException e) { log.debug(e); } } else if (useBlankLines && token.isWhitespace() && tokenLength >= 2) { // Check for blank line delimiter if (lastTokenLineFeeds + countLineFeeds(document, tokenOffset, tokenLength) >= 2) { isDelimiter = true; } } lastTokenLineFeeds = 0; if (tokenLength == 1) { // Check for bracket block begin/end try { char aChar = document.getChar(tokenOffset); if (aChar == '(' || aChar == '{' || aChar == '[') { bracketDepth++; } else if (aChar == ')' || aChar == '}' || aChar == ']') { bracketDepth--; } } catch (BadLocationException e) { log.warn(e); } } if (tokenType == SQLToken.T_BLOCK_HEADER) { bracketDepth++; hasBlocks = true; hasBlockHeader = true; } else if (tokenType == SQLToken.T_BLOCK_TOGGLE) { String togglePattern; try { togglePattern = document.get(tokenOffset, tokenLength); } catch (BadLocationException e) { log.warn(e); togglePattern = ""; } // Second toggle pattern must be the same as first one. // Toggles can be nested (PostgreSQL) and we need to count only outer if (bracketDepth == 1 && togglePattern.equals(blockTogglePattern)) { bracketDepth--; blockTogglePattern = null; } else if (bracketDepth == 0 && blockTogglePattern == null) { bracketDepth++; blockTogglePattern = togglePattern; } else { log.debug("Block toggle token inside another block. Can't process it"); } hasBlocks = true; } else if (tokenType == SQLToken.T_BLOCK_BEGIN) { if (!hasBlockHeader) { bracketDepth++; } hasBlocks = true; } else if (bracketDepth > 0 && tokenType == SQLToken.T_BLOCK_END) { // Sometimes query contains END clause without BEGIN. E.g. CASE, IF, etc. // This END doesn't mean block if (hasBlocks) { bracketDepth--; } hasBlockHeader = false; } else if (isDelimiter && bracketDepth > 0) { // Delimiter in some brackets - ignore it continue; } else if (tokenType == SQLToken.T_SET_DELIMITER || tokenType == SQLToken.T_CONTROL) { isDelimiter = true; isControl = true; } else if (tokenType == SQLToken.T_COMMENT) { lastTokenLineFeeds = tokenLength < 2 ? 0 : countLineFeeds(document, tokenOffset + tokenLength - 2, 2); } boolean cursorInsideToken = currentPos >= tokenOffset && currentPos < tokenOffset + tokenLength; if (isControl && cursorInsideToken) { // Control query try { String controlText = document.get(tokenOffset, tokenLength); return new SQLControlCommand( getDataSource(), syntaxManager, controlText.trim(), tokenOffset, tokenLength, tokenType == SQLToken.T_SET_DELIMITER); } catch (BadLocationException e) { log.warn("Can't extract control statement", e); //$NON-NLS-1$ return null; } } if (hasValuableTokens && (token.isEOF() || (isDelimiter && tokenOffset >= currentPos) || tokenOffset > endPos)) { if (tokenOffset > endPos) { tokenOffset = endPos; } if (tokenOffset >= document.getLength()) { // Sometimes (e.g. when comment finishing script text) // last token offset is beyond document range tokenOffset = document.getLength(); } assert (tokenOffset >= currentPos); try { // remove leading spaces while (statementStart < tokenOffset && Character.isWhitespace(document.getChar(statementStart))) { statementStart++; } // remove trailing spaces while (statementStart < tokenOffset && Character.isWhitespace(document.getChar(tokenOffset - 1))) { tokenOffset--; tokenLength++; } if (tokenOffset == statementStart) { // Empty statement if (token.isEOF()) { return null; } statementStart = tokenOffset + tokenLength; continue; } String queryText = document.get(statementStart, tokenOffset - statementStart); queryText = SQLUtils.fixLineFeeds(queryText); if (isDelimiter && (hasBlocks ? dialect.isDelimiterAfterBlock() : dialect.isDelimiterAfterQuery())) { if (delimiterText != null) { queryText += delimiterText; } } int queryEndPos = tokenOffset; if (tokenType == SQLToken.T_DELIMITER) { queryEndPos += tokenLength; } // make script line return new SQLQuery( getDataSource(), queryText.trim(), statementStart, queryEndPos - statementStart); } catch (BadLocationException ex) { log.warn("Can't extract query", ex); //$NON-NLS-1$ return null; } } if (isDelimiter) { statementStart = tokenOffset + tokenLength; } if (token.isEOF()) { return null; } if (!hasValuableTokens && !token.isWhitespace() && !isControl) { if (tokenType == SQLToken.T_COMMENT) { hasValuableTokens = dialect.supportsCommentQuery(); } else { hasValuableTokens = true; } } } } private static int countLineFeeds(final IDocument document, final int offset, final int length) { int lfCount = 0; try { for (int i = offset; i < offset + length; i++) { if (document.getChar(i) == '\n') { lfCount++; } } } catch (BadLocationException e) { log.error(e); } return lfCount; } protected List<SQLQueryParameter> parseParameters(IDocument document, SQLQuery query) { final SQLDialect sqlDialect = getSQLDialect(); boolean execQuery = false; List<SQLQueryParameter> parameters = null; ruleManager.setRange(document, query.getOffset(), query.getLength()); boolean firstKeyword = true; for (;;) { IToken token = ruleManager.nextToken(); int tokenOffset = ruleManager.getTokenOffset(); final int tokenLength = ruleManager.getTokenLength(); if (token.isEOF() || tokenOffset > query.getOffset() + query.getLength()) { break; } // Handle only parameters which are not in SQL blocks int tokenType = SQLToken.T_UNKNOWN; if (token instanceof SQLToken) { tokenType = ((SQLToken) token).getType(); } if (token.isWhitespace() || tokenType == SQLToken.T_COMMENT) { continue; } if (firstKeyword) { // Detect query type try { String tokenText = document.get(tokenOffset, tokenLength); if (ArrayUtils.containsIgnoreCase(sqlDialect.getDDLKeywords(), tokenText)) { // DDL doesn't support parameters return null; } execQuery = ArrayUtils.containsIgnoreCase(sqlDialect.getExecuteKeywords(), tokenText); } catch (BadLocationException e) { log.warn(e); } firstKeyword = false; } if (tokenType == SQLToken.T_PARAMETER && tokenLength > 0) { try { String paramName = document.get(tokenOffset, tokenLength); if (execQuery && paramName.equals("?")) { // Skip ? parameters for stored procedures (they have special meaning? [DB2]) continue; } if (parameters == null) { parameters = new ArrayList<>(); } SQLQueryParameter parameter = new SQLQueryParameter( parameters.size(), paramName, tokenOffset - query.getOffset(), tokenLength); SQLQueryParameter previous = null; if (parameter.isNamed()) { for (int i = parameters.size(); i > 0; i--) { if (parameters.get(i - 1).getName().equals(paramName)) { previous = parameters.get(i - 1); break; } } } parameter.setPrevious(previous); parameters.add(parameter); } catch (BadLocationException e) { log.warn("Can't extract query parameter", e); } } } return parameters; } public boolean isDisposed() { return getSourceViewer() != null && getSourceViewer().getTextWidget() != null && getSourceViewer().getTextWidget().isDisposed(); } @Nullable @Override public ICommentsSupport getCommentsSupport() { final SQLDialect dialect = getSQLDialect(); return new ICommentsSupport() { @Nullable @Override public Pair<String, String> getMultiLineComments() { return dialect.getMultiLineComments(); } @Override public String[] getSingleLineComments() { return dialect.getSingleLineComments(); } }; } protected String[] collectContextMenuPreferencePages() { String[] ids = super.collectContextMenuPreferencePages(); String[] more = new String[ids.length + 5]; more[ids.length] = PrefPageSQLEditor.PAGE_ID; more[ids.length + 1] = PrefPageSQLExecute.PAGE_ID; more[ids.length + 2] = PrefPageSQLCompletion.PAGE_ID; more[ids.length + 3] = PrefPageSQLFormat.PAGE_ID; more[ids.length + 4] = PrefPageSQLTemplates.PAGE_ID; System.arraycopy(ids, 0, more, 0, ids.length); return more; } @Override public boolean visualizeError(@NotNull DBRProgressMonitor monitor, @NotNull Throwable error) { Document document = getDocument(); SQLQuery query = new SQLQuery(getDataSource(), document.get(), 0, document.getLength()); return scrollCursorToError(monitor, query, error); } /** * Error handling */ protected boolean scrollCursorToError(@NotNull DBRProgressMonitor monitor, @NotNull SQLQuery query, @NotNull Throwable error) { try { DBCExecutionContext context = getExecutionContext(); boolean scrolled = false; DBPErrorAssistant errorAssistant = DBUtils.getAdapter(DBPErrorAssistant.class, context.getDataSource()); if (errorAssistant != null) { DBPErrorAssistant.ErrorPosition[] positions = errorAssistant.getErrorPosition( monitor, context, query.getText(), error); if (positions != null && positions.length > 0) { int queryStartOffset = query.getOffset(); int queryLength = query.getLength(); DBPErrorAssistant.ErrorPosition pos = positions[0]; if (pos.line < 0) { if (pos.position >= 0) { // Only position getSelectionProvider().setSelection(new TextSelection(queryStartOffset + pos.position, 1)); scrolled = true; } } else { // Line + position Document document = getDocument(); if (document != null) { int startLine = document.getLineOfOffset(queryStartOffset); int errorOffset = document.getLineOffset(startLine + pos.line); int errorLength; if (pos.position >= 0) { errorOffset += pos.position; errorLength = 1; } else { errorLength = document.getLineLength(startLine + pos.line); } if (errorOffset < queryStartOffset) errorOffset = queryStartOffset; if (errorLength > queryLength) errorLength = queryLength; getSelectionProvider().setSelection(new TextSelection(errorOffset, errorLength)); scrolled = true; } } } } return scrolled; // if (!scrolled) { // // Can't position on error - let's just select entire problem query // showStatementInEditor(result.getStatement(), true); // } } catch (Exception e) { log.warn("Error positioning on query error", e); return false; } } }