/*
* 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.impl.sql;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPIdentifierCase;
import org.jkiss.dbeaver.model.DBPKeywordType;
import org.jkiss.dbeaver.model.data.DBDBinaryFormatter;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.impl.data.formatters.BinaryFormatterHexNative;
import org.jkiss.dbeaver.model.sql.SQLConstants;
import org.jkiss.dbeaver.model.sql.SQLDialect;
import org.jkiss.dbeaver.model.sql.SQLStateType;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.sql.parser.SQLSemanticProcessor;
import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.Pair;
import java.util.*;
/**
* Basic SQL Dialect
*/
public class BasicSQLDialect implements SQLDialect {
private static final String[] DEFAULT_LINE_COMMENTS = {SQLConstants.SL_COMMENT};
private static final String[] EXEC_KEYWORDS = new String[0];
private static final String[] DDL_KEYWORDS = new String[] {
"CREATE", "ALTER", "DROP"
};
private static final String[][] DEFAULT_BEGIN_END_BLOCK = new String[][]{
{SQLConstants.BLOCK_BEGIN, SQLConstants.BLOCK_END}
};
protected static final String[] NON_TRANSACTIONAL_KEYWORDS = new String[]{
//SQLConstants.KEYWORD_SELECT, "WITH",
"EXPLAIN", "DESCRIBE", "DESC", "USE", "SET", "COMMIT", "ROLLBACK" };
private static final String[] CORE_NON_TRANSACTIONAL_KEYWORDS = new String[]{
SQLConstants.KEYWORD_SELECT,
};
// Keywords
private TreeMap<String, DBPKeywordType> allKeywords = new TreeMap<>();
private final TreeSet<String> reservedWords = new TreeSet<>();
private final TreeSet<String> functions = new TreeSet<>();
protected final TreeSet<String> types = new TreeSet<>();
protected final TreeSet<String> tableQueryWords = new TreeSet<>();
private final TreeSet<String> columnQueryWords = new TreeSet<>();
// Comments
private Pair<String, String> multiLineComments = new Pair<>(SQLConstants.ML_COMMENT_START, SQLConstants.ML_COMMENT_END);
public static final BasicSQLDialect INSTANCE = new BasicSQLDialect();
protected BasicSQLDialect()
{
loadStandardKeywords();
}
@NotNull
@Override
public String getDialectName() {
return "SQL";
}
@Nullable
@Override
public String getIdentifierQuoteString()
{
return "\"";
}
@NotNull
@Override
public String[] getExecuteKeywords() {
return EXEC_KEYWORDS;
}
@NotNull
@Override
public String[] getDDLKeywords() {
return DDL_KEYWORDS;
}
protected void addSQLKeyword(String keyword)
{
reservedWords.add(keyword);
allKeywords.put(keyword, DBPKeywordType.KEYWORD);
}
protected void removeSQLKeyword(String keyword)
{
reservedWords.remove(keyword);
allKeywords.remove(keyword);
}
protected void addFunctions(Collection<String> allFunctions) {
functions.addAll(allFunctions);
addKeywords(allFunctions, DBPKeywordType.FUNCTION);
}
protected void addDataTypes(Collection<String> allTypes) {
for (String type : allTypes) {
types.add(type.toUpperCase(Locale.ENGLISH));
}
addKeywords(allTypes, DBPKeywordType.TYPE);
}
/**
* Add keywords.
* @param set keywords. Must be in upper case.
* @param type keyword type
*/
protected void addKeywords(Collection<String> set, DBPKeywordType type)
{
if (set != null) {
for (String keyword : set) {
keyword = keyword.toUpperCase(Locale.ENGLISH);
reservedWords.add(keyword);
DBPKeywordType oldType = allKeywords.get(keyword);
if (oldType != DBPKeywordType.KEYWORD) {
// We can't mark keywords as functions or types because keywords are reserved and
// if some identifier conflicts with keyword it must be quoted.
allKeywords.put(keyword, type);
}
}
}
}
@NotNull
@Override
public Set<String> getReservedWords()
{
return reservedWords;
}
@NotNull
@Override
public Set<String> getFunctions(@NotNull DBPDataSource dataSource)
{
return functions;
}
@NotNull
@Override
public TreeSet<String> getDataTypes(@NotNull DBPDataSource dataSource)
{
return types;
}
@Override
public DBPKeywordType getKeywordType(@NotNull String word)
{
return allKeywords.get(word.toUpperCase(Locale.ENGLISH));
}
@NotNull
@Override
public List<String> getMatchedKeywords(@NotNull String word)
{
word = word.toUpperCase(Locale.ENGLISH);
List<String> result = new ArrayList<>();
for (String keyword : allKeywords.tailMap(word).keySet()) {
if (keyword.startsWith(word)) {
result.add(keyword);
} else {
break;
}
}
return result;
}
@Override
public boolean isKeywordStart(@NotNull String word)
{
SortedMap<String, DBPKeywordType> map = allKeywords.tailMap(word.toUpperCase(Locale.ENGLISH));
return !map.isEmpty() && map.firstKey().startsWith(word);
}
@Override
public boolean isEntityQueryWord(@NotNull String word)
{
return tableQueryWords.contains(word.toUpperCase(Locale.ENGLISH));
}
@Override
public boolean isAttributeQueryWord(@NotNull String word)
{
return columnQueryWords.contains(word.toUpperCase(Locale.ENGLISH));
}
@NotNull
@Override
public String getSearchStringEscape()
{
return "%";
}
@Override
public int getCatalogUsage()
{
return USAGE_NONE;
}
@Override
public int getSchemaUsage()
{
return USAGE_NONE;
}
@NotNull
@Override
public String getCatalogSeparator()
{
return String.valueOf(SQLConstants.STRUCT_SEPARATOR);
}
@Override
public char getStructSeparator()
{
return SQLConstants.STRUCT_SEPARATOR;
}
@Override
public boolean isCatalogAtStart()
{
return true;
}
@NotNull
@Override
public SQLStateType getSQLStateType()
{
return SQLStateType.SQL99;
}
@NotNull
@Override
public String getScriptDelimiter()
{
return ";"; //$NON-NLS-1$
}
@Nullable
@Override
public String getScriptDelimiterRedefiner() {
return null;
}
@Override
public String[][] getBlockBoundStrings() {
return DEFAULT_BEGIN_END_BLOCK;
}
@Nullable
@Override
public String getBlockHeaderString() {
return null;
}
@Nullable
@Override
public String getBlockToggleString() {
return null;
}
@Override
public boolean validUnquotedCharacter(char c)
{
return Character.isLetter(c) || Character.isDigit(c) || c == '_';
}
@Override
public boolean supportsUnquotedMixedCase()
{
return true;
}
@Override
public boolean supportsQuotedMixedCase()
{
return true;
}
@NotNull
@Override
public DBPIdentifierCase storesUnquotedCase()
{
return DBPIdentifierCase.UPPER;
}
@NotNull
@Override
public DBPIdentifierCase storesQuotedCase()
{
return DBPIdentifierCase.MIXED;
}
@NotNull
@Override
public String escapeString(String string) {
return string.replace("'", "''");
}
@NotNull
@Override
public String escapeScriptValue(DBSAttributeBase attribute, @NotNull Object value, @NotNull String strValue) {
if (value instanceof UUID) {
return '\'' + escapeString(strValue) + '\'';
}
return strValue;
}
@NotNull
@Override
public MultiValueInsertMode getMultiValueInsertMode() {
return MultiValueInsertMode.NOT_SUPPORTED;
}
@Override
public String addFiltersToQuery(DBPDataSource dataSource, String query, DBDDataFilter filter) {
return SQLSemanticProcessor.addFiltersToQuery(dataSource, query, filter);
}
@Override
public boolean supportsSubqueries()
{
return true;
}
@Override
public boolean supportsAliasInSelect() {
return false;
}
@Override
public boolean supportsAliasInUpdate() {
return false;
}
@Override
public boolean supportsCommentQuery() {
return false;
}
@Override
public Pair<String, String> getMultiLineComments()
{
return multiLineComments;
}
@Override
public String[] getSingleLineComments()
{
return DEFAULT_LINE_COMMENTS;
}
@Override
public boolean isDelimiterAfterQuery() {
return false;
}
@Override
public boolean isDelimiterAfterBlock() {
return false;
}
@NotNull
@Override
public DBDBinaryFormatter getNativeBinaryFormatter() {
return BinaryFormatterHexNative.INSTANCE;
}
@Override
public String getTestSQL() {
return null;
}
@Nullable
@Override
public String getDualTableName() {
return null;
}
@Override
public boolean isTransactionModifyingQuery(String queryString) {
queryString = SQLUtils.stripComments(this, queryString.toUpperCase(Locale.ENGLISH)).trim();
if (queryString.isEmpty()) {
// Empty query - must be some metadata reading or something
// anyhow it shouldn't be transactional
return false;
}
String[] ntk = getNonTransactionKeywords();
for (int i = 0; i < ntk.length; i++) {
if (queryString.startsWith(ntk[i])) {
return false;
}
}
return true;
}
@NotNull
protected String[] getNonTransactionKeywords() {
return isStandardSQL() ? NON_TRANSACTIONAL_KEYWORDS : CORE_NON_TRANSACTIONAL_KEYWORDS;
}
@Override
public boolean isQuoteReservedWords() {
return true;
}
protected boolean isStandardSQL() {
return true;
}
private void loadStandardKeywords()
{
// Add default set of keywords
Set<String> all = new HashSet<>();
if (isStandardSQL()) {
Collections.addAll(all, SQLConstants.SQL2003_RESERVED_KEYWORDS);
//Collections.addAll(reservedWords, SQLConstants.SQL2003_NON_RESERVED_KEYWORDS);
Collections.addAll(all, SQLConstants.SQL_EX_KEYWORDS);
Collections.addAll(functions, SQLConstants.SQL2003_FUNCTIONS);
Collections.addAll(tableQueryWords, SQLConstants.TABLE_KEYWORDS);
Collections.addAll(columnQueryWords, SQLConstants.COLUMN_KEYWORDS);
}
for (String executeKeyword : ArrayUtils.safeArray(getExecuteKeywords())) {
addSQLKeyword(executeKeyword);
}
if (isStandardSQL()) {
// Add default types
Collections.addAll(types, SQLConstants.DEFAULT_TYPES);
addKeywords(all, DBPKeywordType.KEYWORD);
addKeywords(types, DBPKeywordType.TYPE);
addKeywords(functions, DBPKeywordType.FUNCTION);
}
}
}