diff --git a/plugins/org.jkiss.dbeaver.core/plugin.xml b/plugins/org.jkiss.dbeaver.core/plugin.xml index 39be91eea176a6d1f027247c43a6ffdbea94099f..f127a3031201a90149ceb4d19b18c8d6ea331cc4 100644 --- a/plugins/org.jkiss.dbeaver.core/plugin.xml +++ b/plugins/org.jkiss.dbeaver.core/plugin.xml @@ -3302,8 +3302,9 @@ - - + + + diff --git a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/preferences/PrefPageSQLFormat.java b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/preferences/PrefPageSQLFormat.java index d2aa671a8c8ba085041a92cd652eb3493110c619..2f72f2b345c6c8ce94b69b5cba86e345cd1c6c94 100644 --- a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/preferences/PrefPageSQLFormat.java +++ b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/preferences/PrefPageSQLFormat.java @@ -38,7 +38,7 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer; import org.jkiss.dbeaver.model.DBPIdentifierCase; import org.jkiss.dbeaver.model.exec.DBCExecutionContext; import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; -import org.jkiss.dbeaver.model.sql.format.external.SQLExternalFormatter; +import org.jkiss.dbeaver.model.sql.format.external.SQLFormatterExternal; import org.jkiss.dbeaver.registry.sql.SQLFormatterConfigurationRegistry; import org.jkiss.dbeaver.registry.sql.SQLFormatterDescriptor; import org.jkiss.dbeaver.ui.UIUtils; @@ -159,6 +159,7 @@ public class PrefPageSQLFormat extends TargetPrefPage @Override public void widgetSelected(SelectionEvent e) { showFormatterSettings(); + performApply(); } }); formatterSelector.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING)); @@ -193,13 +194,13 @@ public class PrefPageSQLFormat extends TargetPrefPage externalCmdText, new TextContentAdapter(), new SimpleContentProposalProvider(new String[] { - GeneralUtils.variablePattern(SQLExternalFormatter.VAR_FILE) + GeneralUtils.variablePattern(SQLFormatterExternal.VAR_FILE) })); - UIUtils.setContentProposalToolTip(externalCmdText, CoreMessages.pref_page_sql_format_label_external_set_content_tool_tip, SQLExternalFormatter.VAR_FILE); + UIUtils.setContentProposalToolTip(externalCmdText, CoreMessages.pref_page_sql_format_label_external_set_content_tool_tip, SQLFormatterExternal.VAR_FILE); externalUseFile = UIUtils.createLabelCheckbox(externalGroup, CoreMessages.pref_page_sql_format_label_external_use_temp_file, - CoreMessages.pref_page_sql_format_label_external_use_temp_file_tip + GeneralUtils.variablePattern(SQLExternalFormatter.VAR_FILE), + CoreMessages.pref_page_sql_format_label_external_use_temp_file_tip + GeneralUtils.variablePattern(SQLFormatterExternal.VAR_FILE), false); externalTimeout = UIUtils.createLabelSpinner(externalGroup, CoreMessages.pref_page_sql_format_label_external_exec_timeout, @@ -344,7 +345,7 @@ public class PrefPageSQLFormat extends TargetPrefPage private void showFormatterSettings() { SQLFormatterDescriptor selFormatter = formatters.get(formatterSelector.getSelectionIndex()); - boolean isExternal = selFormatter.getId().equalsIgnoreCase(SQLExternalFormatter.FORMATTER_ID); + boolean isExternal = selFormatter.getId().equalsIgnoreCase(SQLFormatterExternal.FORMATTER_ID); defaultGroup.setVisible(!isExternal); externalGroup.setVisible(isExternal); ((GridData)defaultGroup.getLayoutData()).exclude = isExternal; diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/ModelPreferences.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/ModelPreferences.java index fe8e40382ae87d718e57aa8f33bbf7720e3aadfa..1a51c89ea79c67397494e658683fd1171e87c837 100644 --- a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/ModelPreferences.java +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/ModelPreferences.java @@ -26,7 +26,7 @@ import org.jkiss.dbeaver.model.impl.preferences.BundlePreferenceStore; import org.jkiss.dbeaver.model.qm.QMConstants; import org.jkiss.dbeaver.model.qm.QMObjectType; import org.jkiss.dbeaver.model.sql.SQLConstants; -import org.jkiss.dbeaver.model.sql.format.tokenized.SQLTokenizedFormatter; +import org.jkiss.dbeaver.model.sql.format.tokenized.SQLFormatterTokenized; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.dbeaver.utils.PrefUtils; import org.osgi.framework.Bundle; @@ -160,7 +160,7 @@ public final class ModelPreferences PrefUtils.setDefaultPreferenceValue(store, SQL_NAMED_PARAMETERS_PREFIX, String.valueOf(SQLConstants.DEFAULT_PARAMETER_PREFIX)); PrefUtils.setDefaultPreferenceValue(store, SQL_CONTROL_COMMAND_PREFIX, String.valueOf(SQLConstants.DEFAULT_CONTROL_COMMAND_PREFIX)); - PrefUtils.setDefaultPreferenceValue(store, SQL_FORMAT_FORMATTER, SQLTokenizedFormatter.FORMATTER_ID); + PrefUtils.setDefaultPreferenceValue(store, SQL_FORMAT_FORMATTER, SQLFormatterTokenized.FORMATTER_ID); PrefUtils.setDefaultPreferenceValue(store, SQL_FORMAT_KEYWORD_CASE, ""); PrefUtils.setDefaultPreferenceValue(store, SQL_FORMAT_EXTERNAL_CMD, ""); PrefUtils.setDefaultPreferenceValue(store, SQL_FORMAT_EXTERNAL_FILE, false); diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/external/SQLExternalFormatter.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/external/SQLFormatterExternal.java similarity index 95% rename from plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/external/SQLExternalFormatter.java rename to plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/external/SQLFormatterExternal.java index ba38e6bd9bfbd487124158eb9e4ea829e5bb06b9..11fe0a45f4338159b6deeaf23bcc695ef42a087e 100644 --- a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/external/SQLExternalFormatter.java +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/external/SQLFormatterExternal.java @@ -37,11 +37,11 @@ import java.util.List; /** * External SQL formatter */ -public class SQLExternalFormatter implements SQLFormatter { +public class SQLFormatterExternal implements SQLFormatter { public static final String FORMATTER_ID = "EXTERNAL"; - private static final Log log = Log.getLog(SQLExternalFormatter.class); + private static final Log log = Log.getLog(SQLFormatterExternal.class); public static final String VAR_FILE = "file"; @Override @@ -50,6 +50,11 @@ public class SQLExternalFormatter implements SQLFormatter { final String command = store.getString(ModelPreferences.SQL_FORMAT_EXTERNAL_CMD); int timeout = store.getInt(ModelPreferences.SQL_FORMAT_EXTERNAL_TIMEOUT); boolean useFile = store.getBoolean(ModelPreferences.SQL_FORMAT_EXTERNAL_FILE); + if (CommonUtils.isEmpty(command)) { + // Nothing to format + return source; + } + try { final FormatJob formatJob = new FormatJob(configuration, command, source, useFile); formatJob.schedule(); @@ -79,7 +84,7 @@ public class SQLExternalFormatter implements SQLFormatter { public boolean finished; public FormatJob(SQLFormatterConfiguration configuration, String command, String source, boolean useFile) { - super("External format: " + command); + super("External format - " + command); this.command = command; this.configuration = configuration; this.source = source; diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLFormatterCompact.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLFormatterCompact.java new file mode 100644 index 0000000000000000000000000000000000000000..bee1fc593fa45bec6f1c870608243e90222ff202 --- /dev/null +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLFormatterCompact.java @@ -0,0 +1,28 @@ +/* + * 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.model.sql.format.tokenized; + +/** + * SQL formatter + */ +public class SQLFormatterCompact extends SQLFormatterTokenized { + + public SQLFormatterCompact() { + setCompact(true); + } +} \ No newline at end of file diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLTokenizedFormatter.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLFormatterTokenized.java similarity index 96% rename from plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLTokenizedFormatter.java rename to plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLFormatterTokenized.java index 2d34aa9e4d6a6f6215a3f78e57e1cd0c7ed000d8..81a8cf1f93a0a499bbcbd358510b37829e372e7a 100644 --- a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLTokenizedFormatter.java +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/sql/format/tokenized/SQLFormatterTokenized.java @@ -1,451 +1,466 @@ -/* - * 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.model.sql.format.tokenized; - -import org.jkiss.dbeaver.model.DBPIdentifierCase; -import org.jkiss.dbeaver.model.sql.SQLUtils; -import org.jkiss.dbeaver.model.sql.format.SQLFormatter; -import org.jkiss.dbeaver.model.sql.format.SQLFormatterConfiguration; -import org.jkiss.dbeaver.utils.GeneralUtils; -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.Locale; - -/** - * SQL formatter - */ -public class SQLTokenizedFormatter implements SQLFormatter { - - public static final String FORMATTER_ID = "DEFAULT"; - - private static final String[] JOIN_BEGIN = { "LEFT", "RIGHT", "INNER", "OUTER", "JOIN" }; - - private SQLFormatterConfiguration formatterCfg; - private List functionBracket = new ArrayList<>(); - private List statementDelimiters = new ArrayList<>(2); - private String delimiterRedefiner; - - @Override - public String format(final String argSql, SQLFormatterConfiguration configuration) - { - formatterCfg = configuration; - - for (String delim : formatterCfg.getSyntaxManager().getStatementDelimiters()) { - statementDelimiters.add(delim.toUpperCase(Locale.ENGLISH)); - } - delimiterRedefiner = formatterCfg.getSyntaxManager().getDialect().getScriptDelimiterRedefiner(); - if (delimiterRedefiner != null) { - delimiterRedefiner = delimiterRedefiner.toUpperCase(Locale.ENGLISH); - } - SQLTokensParser fParser = new SQLTokensParser(formatterCfg); - - functionBracket.clear(); - - boolean isSqlEndsWithNewLine = false; - if (argSql.endsWith("\n")) { //$NON-NLS-1$ - isSqlEndsWithNewLine = true; - } - - List list = fParser.parse(argSql); - list = format(list); - - StringBuilder after = new StringBuilder(argSql.length() + 20); - for (FormatterToken token : list) { - after.append(token.getString()); - } - - if (isSqlEndsWithNewLine) { - after.append(GeneralUtils.getDefaultLineSeparator()); - } - - return after.toString(); - } - - private List format(final List argList) { - if (argList.isEmpty()) { - return argList; - } - - FormatterToken token = argList.get(0); - if (token.getType() == TokenType.SPACE) { - argList.remove(0); - if (argList.isEmpty()) { - return argList; - } - } - - token = argList.get(argList.size() - 1); - if (token.getType() == TokenType.SPACE) { - argList.remove(argList.size() - 1); - if (argList.isEmpty()) { - return argList; - } - } - - final DBPIdentifierCase keywordCase = formatterCfg.getKeywordCase(); - for (int index = 0; index < argList.size(); index++) { - token = argList.get(index); - if (token.getType() == TokenType.KEYWORD) { - token.setString(keywordCase.transform(token.getString())); - } - } - - // Remove extra tokens (spaces, etc) - for (int index = argList.size() - 1; index >= 1; index--) { - token = argList.get(index); - FormatterToken prevToken = argList.get(index - 1); - if (token.getType() == TokenType.SPACE && (prevToken.getType() == TokenType.SYMBOL || prevToken.getType() == TokenType.COMMENT)) { - argList.remove(index); - } else if ((token.getType() == TokenType.SYMBOL || token.getType() == TokenType.COMMENT) && prevToken.getType() == TokenType.SPACE) { - argList.remove(index - 1); - } else if (token.getType() == TokenType.SPACE) { - token.setString(" "); //$NON-NLS-1$ - } - } - - for (int index = 0; index < argList.size() - 2; index++) { - FormatterToken t0 = argList.get(index); - FormatterToken t1 = argList.get(index + 1); - FormatterToken t2 = argList.get(index + 2); - - String tokenString = t0.getString().toUpperCase(Locale.ENGLISH); - String token2String = t2.getString().toUpperCase(Locale.ENGLISH);; - // Concatenate tokens - if (t0.getType() == TokenType.KEYWORD && t1.getType() == TokenType.SPACE && t2.getType() == TokenType.KEYWORD) { - if (((tokenString.equals("ORDER") || tokenString.equals("GROUP") || tokenString.equals("CONNECT")) && token2String.equals("BY")) || - ((tokenString.equals("START")) && token2String.equals("WITH"))) - { - t0.setString(t0.getString() + " " + t2.getString()); - argList.remove(index + 1); - argList.remove(index + 1); - } - } - - // Oracle style joins - if (tokenString.equals("(") && t1.getString().equals("+") && token2String.equals(")")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - t0.setString("(+)"); //$NON-NLS-1$ - argList.remove(index + 1); - argList.remove(index + 1); - } - } - - int indent = 0; - final List bracketIndent = new ArrayList<>(); - FormatterToken prev = new FormatterToken(TokenType.SPACE, " "); //$NON-NLS-1$ - boolean encounterBetween = false; - for (int index = 0; index < argList.size(); index++) { - token = argList.get(index); - String tokenString = token.getString().toUpperCase(Locale.ENGLISH); - if (token.getType() == TokenType.SYMBOL) { - if (tokenString.equals("(")) { //$NON-NLS-1$ - functionBracket.add(formatterCfg.isFunction(prev.getString()) ? Boolean.TRUE : Boolean.FALSE); - bracketIndent.add(indent); - indent++; - index += insertReturnAndIndent(argList, index + 1, indent); - } else if (tokenString.equals(")") && !bracketIndent.isEmpty() && !functionBracket.isEmpty()) { //$NON-NLS-1$ - indent = bracketIndent.remove(bracketIndent.size() - 1); - index += insertReturnAndIndent(argList, index, indent); - functionBracket.remove(functionBracket.size() - 1); - } else if (tokenString.equals(",")) { //$NON-NLS-1$ - index += insertReturnAndIndent(argList, index + 1, indent); - } else if (statementDelimiters.contains(tokenString)) { //$NON-NLS-1$ - indent = 0; - index += insertReturnAndIndent(argList, index, indent); - } - } else if (token.getType() == TokenType.KEYWORD) { - switch (tokenString) { - case "DELETE": - case "SELECT": - case "UPDATE": //$NON-NLS-1$ - case "INSERT": - case "INTO": - case "CREATE": - case "DROP": - case "TRUNCATE": - case "TABLE": - case "CASE": //$NON-NLS-1$ - indent++; - index += insertReturnAndIndent(argList, index + 1, indent); - break; - case "FROM": - case "WHERE": - case "SET": - case "START WITH": - case "CONNECT BY": - case "ORDER BY": - case "GROUP BY": - case "HAVING": //$NON-NLS-1$ - index += insertReturnAndIndent(argList, index, indent - 1); - index += insertReturnAndIndent(argList, index + 1, indent); - break; - case "LEFT": - case "RIGHT": - case "INNER": - case "OUTER": - case "JOIN": - if (isJoinStart(argList, index)) { - index += insertReturnAndIndent(argList, index, indent - 1); - } - if (tokenString.equals("JOIN")) { - //index += insertReturnAndIndent(argList, index + 1, indent); - } - break; - case "VALUES": //$NON-NLS-1$ - indent--; - index += insertReturnAndIndent(argList, index, indent); - break; - case "END": //$NON-NLS-1$ - indent--; - index += insertReturnAndIndent(argList, index, indent); - break; - case "OR": - case "WHEN": - case "ELSE": //$NON-NLS-1$ - index += insertReturnAndIndent(argList, index, indent); - break; - case "ON": - //indent++; - index += insertReturnAndIndent(argList, index + 1, indent); - break; - case "USING": //$NON-NLS-1$ //$NON-NLS-2$ - index += insertReturnAndIndent(argList, index, indent + 1); - break; - case "TOP": //$NON-NLS-1$ //$NON-NLS-2$ - // SQL Server specific - index += insertReturnAndIndent(argList, index, indent); - if (argList.size() < index + 3) { - index += insertReturnAndIndent(argList, index + 3, indent); - } - break; - case "UNION": - case "INTERSECT": - case "EXCEPT": //$NON-NLS-1$ - indent -= 2; - index += insertReturnAndIndent(argList, index, indent); - //index += insertReturnAndIndent(argList, index + 1, indent); - indent++; - break; - case "BETWEEN": //$NON-NLS-1$ - encounterBetween = true; - break; - case "AND": //$NON-NLS-1$ - if (!encounterBetween) { - index += insertReturnAndIndent(argList, index, indent); - } - encounterBetween = false; - break; - } - } else if (token.getType() == TokenType.COMMENT) { - boolean isComment = false; - String[] slComments = formatterCfg.getSyntaxManager().getDialect().getSingleLineComments(); - if (slComments != null) { - for (String slc : slComments) { - if (token.getString().startsWith(slc)) { - isComment = true; - break; - } - } - } - if (!isComment) { - Pair mlComments = formatterCfg.getSyntaxManager().getDialect().getMultiLineComments(); - if (mlComments != null) { - if (token.getString().startsWith(mlComments.getFirst())) { - index += insertReturnAndIndent(argList, index + 1, indent); - } - } - } - } else if (token.getType() == TokenType.COMMAND) { - indent = 0; - if (index > 0) { - index += insertReturnAndIndent(argList, index, 0); - } - index += insertReturnAndIndent(argList, index + 1, 0); - if (!CommonUtils.isEmpty(delimiterRedefiner) && token.getString().startsWith(delimiterRedefiner)) { - final String command = token.getString().trim().toUpperCase(Locale.ENGLISH); - final int divPos = command.lastIndexOf(' '); - if (divPos > 0) { - String delimiter = command.substring(divPos).trim(); - if (!CommonUtils.isEmpty(delimiter)) { - statementDelimiters.clear(); - statementDelimiters.add(delimiter); - } - } - } - } else { - if (statementDelimiters.contains(tokenString)) { - indent = 0; - index += insertReturnAndIndent(argList, index + 1, indent); - } - } - prev = token; - } - - for (int index = argList.size() - 1; index >= 4; index--) { - if (index >= argList.size()) { - continue; - } - - FormatterToken t0 = argList.get(index); - FormatterToken t1 = argList.get(index - 1); - FormatterToken t2 = argList.get(index - 2); - FormatterToken t3 = argList.get(index - 3); - FormatterToken t4 = argList.get(index - 4); - - if (t4.getString().equals("(") //$NON-NLS-1$ - && t3.getString().trim().isEmpty() - && t1.getString().trim().isEmpty() - && t0.getString().equalsIgnoreCase(")")) //$NON-NLS-1$ - { - t4.setString(t4.getString() + t2.getString() + t0.getString()); - argList.remove(index); - argList.remove(index - 1); - argList.remove(index - 2); - argList.remove(index - 3); - } - } - - for (int index = 1; index < argList.size(); index++) { - prev = argList.get(index - 1); - token = argList.get(index); - - if (prev.getType() != TokenType.SPACE && - token.getType() != TokenType.SPACE && - !token.getString().startsWith("(")) - { - if (token.getString().equals(",") || statementDelimiters.contains(token.getString())) { //$NON-NLS-1$ - continue; - } - if (formatterCfg.isFunction(prev.getString()) - && token.getString().equals("(")) { //$NON-NLS-1$ - continue; - } - if (token.getType() == TokenType.VALUE && prev.getType() == TokenType.NAME) { - // Do not add space between name and value [JDBC:MSSQL] - continue; - } - if (token.getType() == TokenType.SYMBOL && isEmbeddedToken(token) || - prev.getType() == TokenType.SYMBOL && isEmbeddedToken(prev)) - { - // Do not insert spaces around colons - continue; - } - if (token.getType() == TokenType.SYMBOL && prev.getType() == TokenType.SYMBOL) { - // Do not add space between symbols - continue; - } - argList.add(index, new FormatterToken(TokenType.SPACE, " ")); //$NON-NLS-1$ - } - } - - return argList; - } - - private static boolean isEmbeddedToken(FormatterToken token) { - return ":".equals(token.getString()) || ".".equals(token.getString()); - } - - private boolean isJoinStart(List argList, int index) { - // Keyword sequence must start from LEFT, RIGHT, INNER, OUTER or JOIN and must end with JOIN - // And we must be in the beginning of sequence - - // check current token - if (!ArrayUtils.contains(JOIN_BEGIN, argList.get(index).getString())) { - return false; - } - // check previous token - for (int i = index - 1; i >= 0; i--) { - FormatterToken token = argList.get(i); - if (token.getType() == TokenType.SPACE) { - continue; - } - if (ArrayUtils.contains(JOIN_BEGIN, token.getString())) { - // It is not the begin of sequence - return false; - } else { - break; - } - } - // check last token - for (int i = index; i < argList.size(); i++) { - FormatterToken token = argList.get(i); - if (token.getType() == TokenType.SPACE) { - continue; - } - if (token.getString().equals("JOIN")) { - return true; - } - if (!ArrayUtils.contains(JOIN_BEGIN, token.getString())) { - // It is not the begin of sequence - return false; - } - } - return false; - } - - private int insertReturnAndIndent(final List argList, final int argIndex, final int argIndent) - { - if (functionBracket.contains(Boolean.TRUE)) - return 0; - try { - String s = GeneralUtils.getDefaultLineSeparator(); - if (argIndex > 0) { - final FormatterToken prevToken = argList.get(argIndex - 1); - if (prevToken.getType() == TokenType.COMMENT && - SQLUtils.isCommentLine(formatterCfg.getSyntaxManager().getDialect(), prevToken.getString())) - { - s = ""; //$NON-NLS-1$ - } - } - for (int index = 0; index < argIndent; index++) { - s += formatterCfg.getIndentString(); - } - - FormatterToken token = argList.get(argIndex); - if (token.getType() == TokenType.SPACE) { - token.setString(s); - return 0; - } - boolean isDelimiter = statementDelimiters.contains(token.getString().toUpperCase()); - - if (!isDelimiter) { - token = argList.get(argIndex - 1); - if (token.getType() == TokenType.SPACE) { - token.setString(s); - return 0; - } - } - - if (isDelimiter) { - if (argList.size() > argIndex + 1) { - argList.add(argIndex + 1, new FormatterToken(TokenType.SPACE, s + s)); - } - } else { - argList.add(argIndex, new FormatterToken(TokenType.SPACE, s)); - } - return 1; - } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); - return 0; - } - } - +/* + * 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.model.sql.format.tokenized; + +import org.jkiss.dbeaver.model.DBPIdentifierCase; +import org.jkiss.dbeaver.model.sql.SQLUtils; +import org.jkiss.dbeaver.model.sql.format.SQLFormatter; +import org.jkiss.dbeaver.model.sql.format.SQLFormatterConfiguration; +import org.jkiss.dbeaver.utils.GeneralUtils; +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.Locale; + +/** + * SQL formatter + */ +public class SQLFormatterTokenized implements SQLFormatter { + + public static final String FORMATTER_ID = "DEFAULT"; + + private static final String[] JOIN_BEGIN = { "LEFT", "RIGHT", "INNER", "OUTER", "JOIN" }; + + private SQLFormatterConfiguration formatterCfg; + private List functionBracket = new ArrayList<>(); + private List statementDelimiters = new ArrayList<>(2); + private String delimiterRedefiner; + private boolean isCompact; + + @Override + public String format(final String argSql, SQLFormatterConfiguration configuration) + { + formatterCfg = configuration; + + for (String delim : formatterCfg.getSyntaxManager().getStatementDelimiters()) { + statementDelimiters.add(delim.toUpperCase(Locale.ENGLISH)); + } + delimiterRedefiner = formatterCfg.getSyntaxManager().getDialect().getScriptDelimiterRedefiner(); + if (delimiterRedefiner != null) { + delimiterRedefiner = delimiterRedefiner.toUpperCase(Locale.ENGLISH); + } + SQLTokensParser fParser = new SQLTokensParser(formatterCfg); + + functionBracket.clear(); + + boolean isSqlEndsWithNewLine = false; + if (argSql.endsWith("\n")) { //$NON-NLS-1$ + isSqlEndsWithNewLine = true; + } + + List list = fParser.parse(argSql); + list = format(list); + + StringBuilder after = new StringBuilder(argSql.length() + 20); + for (FormatterToken token : list) { + after.append(token.getString()); + } + + if (isSqlEndsWithNewLine) { + after.append(GeneralUtils.getDefaultLineSeparator()); + } + + return after.toString(); + } + + public boolean isCompact() { + return isCompact; + } + + public void setCompact(boolean compact) { + isCompact = compact; + } + + private List format(final List argList) { + if (argList.isEmpty()) { + return argList; + } + + FormatterToken token = argList.get(0); + if (token.getType() == TokenType.SPACE) { + argList.remove(0); + if (argList.isEmpty()) { + return argList; + } + } + + token = argList.get(argList.size() - 1); + if (token.getType() == TokenType.SPACE) { + argList.remove(argList.size() - 1); + if (argList.isEmpty()) { + return argList; + } + } + + final DBPIdentifierCase keywordCase = formatterCfg.getKeywordCase(); + for (int index = 0; index < argList.size(); index++) { + token = argList.get(index); + if (token.getType() == TokenType.KEYWORD) { + token.setString(keywordCase.transform(token.getString())); + } + } + + // Remove extra tokens (spaces, etc) + for (int index = argList.size() - 1; index >= 1; index--) { + token = argList.get(index); + FormatterToken prevToken = argList.get(index - 1); + if (token.getType() == TokenType.SPACE && (prevToken.getType() == TokenType.SYMBOL || prevToken.getType() == TokenType.COMMENT)) { + argList.remove(index); + } else if ((token.getType() == TokenType.SYMBOL || token.getType() == TokenType.COMMENT) && prevToken.getType() == TokenType.SPACE) { + argList.remove(index - 1); + } else if (token.getType() == TokenType.SPACE) { + token.setString(" "); //$NON-NLS-1$ + } + } + + for (int index = 0; index < argList.size() - 2; index++) { + FormatterToken t0 = argList.get(index); + FormatterToken t1 = argList.get(index + 1); + FormatterToken t2 = argList.get(index + 2); + + String tokenString = t0.getString().toUpperCase(Locale.ENGLISH); + String token2String = t2.getString().toUpperCase(Locale.ENGLISH);; + // Concatenate tokens + if (t0.getType() == TokenType.KEYWORD && t1.getType() == TokenType.SPACE && t2.getType() == TokenType.KEYWORD) { + if (((tokenString.equals("ORDER") || tokenString.equals("GROUP") || tokenString.equals("CONNECT")) && token2String.equals("BY")) || + ((tokenString.equals("START")) && token2String.equals("WITH"))) + { + t0.setString(t0.getString() + " " + t2.getString()); + argList.remove(index + 1); + argList.remove(index + 1); + } + } + + // Oracle style joins + if (tokenString.equals("(") && t1.getString().equals("+") && token2String.equals(")")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + t0.setString("(+)"); //$NON-NLS-1$ + argList.remove(index + 1); + argList.remove(index + 1); + } + } + + int indent = 0; + final List bracketIndent = new ArrayList<>(); + FormatterToken prev = new FormatterToken(TokenType.SPACE, " "); //$NON-NLS-1$ + boolean encounterBetween = false; + for (int index = 0; index < argList.size(); index++) { + token = argList.get(index); + String tokenString = token.getString().toUpperCase(Locale.ENGLISH); + if (token.getType() == TokenType.SYMBOL) { + if (tokenString.equals("(")) { //$NON-NLS-1$ + functionBracket.add(formatterCfg.isFunction(prev.getString()) ? Boolean.TRUE : Boolean.FALSE); + bracketIndent.add(indent); + indent++; + index += insertReturnAndIndent(argList, index + 1, indent); + } else if (tokenString.equals(")") && !bracketIndent.isEmpty() && !functionBracket.isEmpty()) { //$NON-NLS-1$ + indent = bracketIndent.remove(bracketIndent.size() - 1); + index += insertReturnAndIndent(argList, index, indent); + functionBracket.remove(functionBracket.size() - 1); + } else if (tokenString.equals(",")) { //$NON-NLS-1$ + if (!isCompact) { + index += insertReturnAndIndent(argList, index + 1, indent); + } + } else if (statementDelimiters.contains(tokenString)) { //$NON-NLS-1$ + indent = 0; + index += insertReturnAndIndent(argList, index, indent); + } + } else if (token.getType() == TokenType.KEYWORD) { + switch (tokenString) { + case "DELETE": + case "SELECT": + case "UPDATE": //$NON-NLS-1$ + case "INSERT": + case "INTO": + case "CREATE": + case "DROP": + case "TRUNCATE": + case "TABLE": + case "CASE": //$NON-NLS-1$ + if (!isCompact) { + indent++; + index += insertReturnAndIndent(argList, index + 1, indent); + } + break; + case "FROM": + case "WHERE": + case "SET": + case "START WITH": + case "CONNECT BY": + case "ORDER BY": + case "GROUP BY": + case "HAVING": //$NON-NLS-1$ + index += insertReturnAndIndent(argList, index, indent - 1); + if (!isCompact) { + index += insertReturnAndIndent(argList, index + 1, indent); + } + break; + case "LEFT": + case "RIGHT": + case "INNER": + case "OUTER": + case "JOIN": + if (isJoinStart(argList, index)) { + index += insertReturnAndIndent(argList, index, indent - 1); + } + if (tokenString.equals("JOIN")) { + //index += insertReturnAndIndent(argList, index + 1, indent); + } + break; + case "VALUES": //$NON-NLS-1$ + indent--; + index += insertReturnAndIndent(argList, index, indent); + break; + case "END": //$NON-NLS-1$ + indent--; + index += insertReturnAndIndent(argList, index, indent); + break; + case "OR": + case "WHEN": + case "ELSE": //$NON-NLS-1$ + index += insertReturnAndIndent(argList, index, indent); + break; + case "ON": + //indent++; + index += insertReturnAndIndent(argList, index + 1, indent); + break; + case "USING": //$NON-NLS-1$ //$NON-NLS-2$ + index += insertReturnAndIndent(argList, index, indent + 1); + break; + case "TOP": //$NON-NLS-1$ //$NON-NLS-2$ + // SQL Server specific + index += insertReturnAndIndent(argList, index, indent); + if (argList.size() < index + 3) { + index += insertReturnAndIndent(argList, index + 3, indent); + } + break; + case "UNION": + case "INTERSECT": + case "EXCEPT": //$NON-NLS-1$ + indent -= 2; + index += insertReturnAndIndent(argList, index, indent); + //index += insertReturnAndIndent(argList, index + 1, indent); + indent++; + break; + case "BETWEEN": //$NON-NLS-1$ + encounterBetween = true; + break; + case "AND": //$NON-NLS-1$ + if (!encounterBetween) { + index += insertReturnAndIndent(argList, index, indent); + } + encounterBetween = false; + break; + } + } else if (token.getType() == TokenType.COMMENT) { + boolean isComment = false; + String[] slComments = formatterCfg.getSyntaxManager().getDialect().getSingleLineComments(); + if (slComments != null) { + for (String slc : slComments) { + if (token.getString().startsWith(slc)) { + isComment = true; + break; + } + } + } + if (!isComment) { + Pair mlComments = formatterCfg.getSyntaxManager().getDialect().getMultiLineComments(); + if (mlComments != null) { + if (token.getString().startsWith(mlComments.getFirst())) { + index += insertReturnAndIndent(argList, index + 1, indent); + } + } + } + } else if (token.getType() == TokenType.COMMAND) { + indent = 0; + if (index > 0) { + index += insertReturnAndIndent(argList, index, 0); + } + index += insertReturnAndIndent(argList, index + 1, 0); + if (!CommonUtils.isEmpty(delimiterRedefiner) && token.getString().startsWith(delimiterRedefiner)) { + final String command = token.getString().trim().toUpperCase(Locale.ENGLISH); + final int divPos = command.lastIndexOf(' '); + if (divPos > 0) { + String delimiter = command.substring(divPos).trim(); + if (!CommonUtils.isEmpty(delimiter)) { + statementDelimiters.clear(); + statementDelimiters.add(delimiter); + } + } + } + } else { + if (statementDelimiters.contains(tokenString)) { + indent = 0; + index += insertReturnAndIndent(argList, index + 1, indent); + } + } + prev = token; + } + + for (int index = argList.size() - 1; index >= 4; index--) { + if (index >= argList.size()) { + continue; + } + + FormatterToken t0 = argList.get(index); + FormatterToken t1 = argList.get(index - 1); + FormatterToken t2 = argList.get(index - 2); + FormatterToken t3 = argList.get(index - 3); + FormatterToken t4 = argList.get(index - 4); + + if (t4.getString().equals("(") //$NON-NLS-1$ + && t3.getString().trim().isEmpty() + && t1.getString().trim().isEmpty() + && t0.getString().equalsIgnoreCase(")")) //$NON-NLS-1$ + { + t4.setString(t4.getString() + t2.getString() + t0.getString()); + argList.remove(index); + argList.remove(index - 1); + argList.remove(index - 2); + argList.remove(index - 3); + } + } + + for (int index = 1; index < argList.size(); index++) { + prev = argList.get(index - 1); + token = argList.get(index); + + if (prev.getType() != TokenType.SPACE && + token.getType() != TokenType.SPACE && + !token.getString().startsWith("(")) + { + if (token.getString().equals(",") || statementDelimiters.contains(token.getString())) { //$NON-NLS-1$ + continue; + } + if (formatterCfg.isFunction(prev.getString()) + && token.getString().equals("(")) { //$NON-NLS-1$ + continue; + } + if (token.getType() == TokenType.VALUE && prev.getType() == TokenType.NAME) { + // Do not add space between name and value [JDBC:MSSQL] + continue; + } + if (token.getType() == TokenType.SYMBOL && isEmbeddedToken(token) || + prev.getType() == TokenType.SYMBOL && isEmbeddedToken(prev)) + { + // Do not insert spaces around colons + continue; + } + if (token.getType() == TokenType.SYMBOL && prev.getType() == TokenType.SYMBOL) { + // Do not add space between symbols + continue; + } + argList.add(index, new FormatterToken(TokenType.SPACE, " ")); //$NON-NLS-1$ + } + } + + return argList; + } + + private static boolean isEmbeddedToken(FormatterToken token) { + return ":".equals(token.getString()) || ".".equals(token.getString()); + } + + private boolean isJoinStart(List argList, int index) { + // Keyword sequence must start from LEFT, RIGHT, INNER, OUTER or JOIN and must end with JOIN + // And we must be in the beginning of sequence + + // check current token + if (!ArrayUtils.contains(JOIN_BEGIN, argList.get(index).getString())) { + return false; + } + // check previous token + for (int i = index - 1; i >= 0; i--) { + FormatterToken token = argList.get(i); + if (token.getType() == TokenType.SPACE) { + continue; + } + if (ArrayUtils.contains(JOIN_BEGIN, token.getString())) { + // It is not the begin of sequence + return false; + } else { + break; + } + } + // check last token + for (int i = index; i < argList.size(); i++) { + FormatterToken token = argList.get(i); + if (token.getType() == TokenType.SPACE) { + continue; + } + if (token.getString().equals("JOIN")) { + return true; + } + if (!ArrayUtils.contains(JOIN_BEGIN, token.getString())) { + // It is not the begin of sequence + return false; + } + } + return false; + } + + private int insertReturnAndIndent(final List argList, final int argIndex, final int argIndent) + { + if (functionBracket.contains(Boolean.TRUE)) + return 0; + try { + String s = GeneralUtils.getDefaultLineSeparator(); + if (argIndex > 0) { + final FormatterToken prevToken = argList.get(argIndex - 1); + if (prevToken.getType() == TokenType.COMMENT && + SQLUtils.isCommentLine(formatterCfg.getSyntaxManager().getDialect(), prevToken.getString())) + { + s = ""; //$NON-NLS-1$ + } + } + for (int index = 0; index < argIndent; index++) { + s += formatterCfg.getIndentString(); + } + + FormatterToken token = argList.get(argIndex); + if (token.getType() == TokenType.SPACE) { + token.setString(s); + return 0; + } + boolean isDelimiter = statementDelimiters.contains(token.getString().toUpperCase()); + + if (!isDelimiter) { + token = argList.get(argIndex - 1); + if (token.getType() == TokenType.SPACE) { + token.setString(s); + return 0; + } + } + + if (isDelimiter) { + if (argList.size() > argIndex + 1) { + argList.add(argIndex + 1, new FormatterToken(TokenType.SPACE, s + s)); + } + } else { + argList.add(argIndex, new FormatterToken(TokenType.SPACE, s)); + } + return 1; + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + return 0; + } + } + } \ No newline at end of file