/* * 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.jdbc; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.model.DBPIdentifierCase; import org.jkiss.dbeaver.model.DBPKeywordType; import org.jkiss.dbeaver.model.exec.jdbc.JDBCDatabaseMetaData; import org.jkiss.dbeaver.model.impl.sql.BasicSQLDialect; 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.struct.DBSDataType; import org.jkiss.utils.CommonUtils; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.*; /** * SQL Dialect JDBC API implementation */ public class JDBCSQLDialect extends BasicSQLDialect { private static final Log log = Log.getLog(JDBCSQLDialect.class); private String name; private String identifierQuoteString; private SQLStateType sqlStateType; private String searchStringEscape; private String catalogSeparator; private boolean isCatalogAtStart; private int catalogUsage; protected int schemaUsage; private String validCharacters; private boolean supportsUnquotedMixedCase; private boolean supportsQuotedMixedCase; private DBPIdentifierCase unquotedIdentCase; private DBPIdentifierCase quotedIdentCase; private boolean supportsSubqueries = false; private transient boolean typesLoaded = false; public JDBCSQLDialect(String name) { this.name = name; } public void initDriverSettings(JDBCDataSource dataSource, JDBCDatabaseMetaData metaData) { try { this.identifierQuoteString = metaData.getIdentifierQuoteString(); } catch (Throwable e) { log.debug("Error getting identifierQuoteString: " + e.getMessage()); this.identifierQuoteString = SQLConstants.DEFAULT_IDENTIFIER_QUOTE; } if (identifierQuoteString != null) { identifierQuoteString = identifierQuoteString.trim(); if (identifierQuoteString.isEmpty()) { identifierQuoteString = null; } } try { switch (metaData.getSQLStateType()) { case DatabaseMetaData.sqlStateXOpen: this.sqlStateType = SQLStateType.XOPEN; break; case DatabaseMetaData.sqlStateSQL99: this.sqlStateType = SQLStateType.SQL99; break; default: this.sqlStateType = SQLStateType.UNKNOWN; break; } } catch (Throwable e) { log.debug("Error getting sqlStateType: " + e.getMessage()); this.sqlStateType = SQLStateType.UNKNOWN; } try { supportsSubqueries = metaData.supportsCorrelatedSubqueries(); } catch (SQLException e) { log.debug("Error getting supportsSubqueries: " + e.getMessage()); } try { this.supportsUnquotedMixedCase = metaData.supportsMixedCaseIdentifiers(); } catch (SQLException e) { log.debug("Error getting supportsUnquotedMixedCase:" + e.getMessage()); this.supportsUnquotedMixedCase = false; } try { this.supportsQuotedMixedCase = metaData.supportsMixedCaseQuotedIdentifiers(); } catch (SQLException e) { log.debug("Error getting supportsQuotedMixedCase: " + e.getMessage()); this.supportsQuotedMixedCase = false; } try { if (metaData.storesUpperCaseIdentifiers()) { this.unquotedIdentCase = DBPIdentifierCase.UPPER; } else if (metaData.storesLowerCaseIdentifiers()) { this.unquotedIdentCase = DBPIdentifierCase.LOWER; } else { this.unquotedIdentCase = DBPIdentifierCase.MIXED; } } catch (SQLException e) { log.debug("Error getting unquotedIdentCase:" + e.getMessage()); this.unquotedIdentCase = DBPIdentifierCase.MIXED; } try { if (metaData.storesUpperCaseQuotedIdentifiers()) { this.quotedIdentCase = DBPIdentifierCase.UPPER; } else if (metaData.storesLowerCaseQuotedIdentifiers()) { this.quotedIdentCase = DBPIdentifierCase.LOWER; } else { this.quotedIdentCase = DBPIdentifierCase.MIXED; } } catch (SQLException e) { log.debug("Error getting quotedIdentCase:" + e.getMessage()); this.quotedIdentCase = DBPIdentifierCase.MIXED; } try { this.searchStringEscape = metaData.getSearchStringEscape(); } catch (Throwable e) { log.debug("Error getting searchStringEscape:" + e.getMessage()); } if (this.searchStringEscape == null) { this.searchStringEscape = ""; //$NON-NLS-1$ } try { this.catalogSeparator = metaData.getCatalogSeparator(); if (CommonUtils.isEmpty(this.catalogSeparator)) { this.catalogSeparator = String.valueOf(SQLConstants.STRUCT_SEPARATOR); } } catch (Throwable e) { log.debug("Error getting catalogSeparator:" + e.getMessage()); this.catalogSeparator = String.valueOf(SQLConstants.STRUCT_SEPARATOR); } try { catalogUsage = (metaData.supportsCatalogsInDataManipulation() ? SQLDialect.USAGE_DML : 0) | (metaData.supportsCatalogsInTableDefinitions() ? SQLDialect.USAGE_DDL : 0) | (metaData.supportsCatalogsInProcedureCalls() ? SQLDialect.USAGE_PROC : 0) | (metaData.supportsCatalogsInIndexDefinitions() ? SQLDialect.USAGE_INDEX : 0) | (metaData.supportsCatalogsInPrivilegeDefinitions() ? SQLDialect.USAGE_PRIV : 0); } catch (SQLException e) { log.debug("Error getting catalogUsage:" + e.getMessage()); catalogUsage = SQLDialect.USAGE_NONE; } try { schemaUsage = (metaData.supportsSchemasInDataManipulation() ? SQLDialect.USAGE_DML : 0) | (metaData.supportsSchemasInTableDefinitions() ? SQLDialect.USAGE_DDL : 0) | (metaData.supportsSchemasInProcedureCalls() ? SQLDialect.USAGE_PROC : 0) | (metaData.supportsSchemasInIndexDefinitions() ? SQLDialect.USAGE_INDEX : 0) | (metaData.supportsSchemasInPrivilegeDefinitions() ? SQLDialect.USAGE_PRIV : 0); } catch (SQLException e) { log.debug("Error getting schemaUsage:" + e.getMessage()); schemaUsage = SQLDialect.USAGE_DDL | SQLDialect.USAGE_DML; } try { validCharacters = metaData.getExtraNameCharacters(); } catch (SQLException e) { log.debug("Error getting validCharacters:" + e.getMessage()); validCharacters = ""; //$NON-NLS-1$ } try { this.isCatalogAtStart = metaData.isCatalogAtStart(); } catch (Throwable e) { log.debug("Error getting isCatalogAtStart:" + e.getMessage()); this.isCatalogAtStart = true; } loadDriverKeywords(metaData); } @NotNull @Override public String getDialectName() { return name; } @Nullable @Override public String getIdentifierQuoteString() { return identifierQuoteString; } @NotNull @Override public String[] getExecuteKeywords() { return new String[0]; } @NotNull @Override public String getSearchStringEscape() { return searchStringEscape; } @Override public int getCatalogUsage() { return catalogUsage; } @Override public int getSchemaUsage() { return schemaUsage; } @NotNull @Override public String getCatalogSeparator() { return catalogSeparator; } @Override public char getStructSeparator() { return SQLConstants.STRUCT_SEPARATOR; } @Override public boolean isCatalogAtStart() { return isCatalogAtStart; } @NotNull @Override public SQLStateType getSQLStateType() { return sqlStateType; } @NotNull @Override public String getScriptDelimiter() { return ";"; //$NON-NLS-1$ } @Override public boolean validUnquotedCharacter(char c) { return Character.isLetter(c) || Character.isDigit(c) || c == '_' || validCharacters.indexOf(c) != -1; } @Override public boolean supportsUnquotedMixedCase() { return supportsUnquotedMixedCase; } @Override public boolean supportsQuotedMixedCase() { return supportsQuotedMixedCase; } @NotNull @Override public DBPIdentifierCase storesUnquotedCase() { return unquotedIdentCase; } @NotNull @Override public DBPIdentifierCase storesQuotedCase() { return quotedIdentCase; } @Override public boolean supportsSubqueries() { return supportsSubqueries; } public void setSupportsSubqueries(boolean supportsSubqueries) { this.supportsSubqueries = supportsSubqueries; } public boolean supportsUpsertStatement() { return false; } @NotNull @Override public TreeSet<String> getDataTypes(@NotNull DBPDataSource dataSource) { if (!typesLoaded && dataSource instanceof JDBCDataSource) { types.clear(); loadDataTypesFromDatabase((JDBCDataSource) dataSource); typesLoaded = true; } return types; } protected void loadDataTypesFromDatabase(JDBCDataSource dataSource) { Collection<? extends DBSDataType> supportedDataTypes = dataSource.getLocalDataTypes(); if (supportedDataTypes != null) { for (DBSDataType dataType : supportedDataTypes) { if (!dataType.getDataKind().isComplex()) { types.add(dataType.getName().toUpperCase(Locale.ENGLISH)); } } } if (types.isEmpty()) { // Add default types Collections.addAll(types, SQLConstants.DEFAULT_TYPES); } addKeywords(types, DBPKeywordType.TYPE); } private void loadDriverKeywords(JDBCDatabaseMetaData metaData) { try { // Keywords Collection<String> sqlKeywords = makeStringList(metaData.getSQLKeywords()); if (!CommonUtils.isEmpty(sqlKeywords)) { for (String keyword : sqlKeywords) { addSQLKeyword(keyword.toUpperCase()); } } } catch (SQLException e) { log.debug("Error reading SQL keywords: " + e.getMessage()); } try { // Functions Set<String> allFunctions = new HashSet<>(); for (String func : makeStringList(metaData.getNumericFunctions())) { allFunctions.add(func.toUpperCase()); } for (String func : makeStringList(metaData.getStringFunctions())) { allFunctions.add(func.toUpperCase()); } for (String func : makeStringList(metaData.getSystemFunctions())) { allFunctions.add(func.toUpperCase()); } for (String func : makeStringList(metaData.getTimeDateFunctions())) { allFunctions.add(func.toUpperCase()); } // Remove functions which clashes with keywords for (Iterator<String> fIter = allFunctions.iterator(); fIter.hasNext(); ) { if (getKeywordType(fIter.next())== DBPKeywordType.KEYWORD) { fIter.remove(); } } addFunctions(allFunctions); } catch (Throwable e) { log.debug("Error reading SQL functions: " + e.getMessage()); } } private static List<String> makeStringList(String source) { List<String> result = new ArrayList<>(); if (source != null && source.length() > 0) { StringTokenizer st = new StringTokenizer(source, ";,"); //$NON-NLS-1$ while (st.hasMoreTokens()) { result.add(st.nextToken().trim()); } } return result; } }