/* * Copyright 2021 lbruun.net. * * 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 net.lbruun.jdbc; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Locale; import java.util.Objects; import javax.sql.DataSource; /** * Database RDBMS engines. * *

* Provides enums for most widely uses RDBMSs and methods to detect * these from their {@link java.sql.DatabaseMetaData} signature. * * @author lbruun */ public enum DatabaseEngine { /** * PostgreSQL database */ POSTGRESQL(Codes.POSTGRESQL_CODE), /** * Microsoft SQL Server database */ MSSQL(Codes.MSSQL_CODE), /** * Oracle RDBMS database */ ORACLE(Codes.ORACLE_CODE), /** * MySQL database */ MYSQL(Codes.MYSQL_CODE), /** * MariaDB database */ MARIADB(Codes.MARIADB_CODE), /** * Db2 on Linux, Unix and Windows systems */ DB2_LUW(Codes.DB2_LUW_CODE), /** * Db2 on z/OS */ DB2_ZOS(Codes.DB2_ZOS_CODE), /** * H2 database */ H2(Codes.H2_CODE), /** * Amazon Redshift database */ REDSHIFT(Codes.REDSHIFT_CODE), /** * Apache Derby database */ DERBY(Codes.DERBY_CODE), /** * HyperSQL database */ HSQLDB(Codes.HSQLDB_CODE); /** * Unique string codes for database engines. *

* By convention these codes are in lower-case. */ public static class Codes { /** * PostgreSQL database */ public static final String POSTGRESQL_CODE = "postgresql"; /** * Microsoft SQL Server database */ public static final String MSSQL_CODE = "mssql"; /** * Oracle RDBMS database */ public static final String ORACLE_CODE = "oracle"; /** * MySQL database */ public static final String MYSQL_CODE = "mysql"; /** * MariaDB database */ public static final String MARIADB_CODE = "mariadb"; /** * Db2 on Linux, Unix and Windows systems */ public static final String DB2_LUW_CODE = "db2"; /** * Db2 on z/OS */ public static final String DB2_ZOS_CODE = "db2z"; /** * H2 database */ public static final String H2_CODE = "h2"; /** * Amazon Redshift */ public static final String REDSHIFT_CODE = "redshift"; /** * Apache Derby */ public static final String DERBY_CODE = "derby"; /** * HyperSQL database */ public static final String HSQLDB_CODE = "hsqldb"; } private String code; DatabaseEngine(String code) { this.code = code; } /** * Gets the string code for the Database Engine. * @return */ public String getCode() { return code; } /** * Gets the {@code DatabaseEngine} which corresponds to * a code value such as "mysql". * @param code string code representing a unique value for a * database engine. * @return the DatabaseEngine corresponding to the code value * or {@code null} if no match for the code value was found. */ public static DatabaseEngine getDatabaseEngineFromCode(String code) { if (code == null) { return null; } for(DatabaseEngine dbEngine : DatabaseEngine.values()) { if (dbEngine.getCode().equals(code)) { return dbEngine; } } return null; } /** * Find {@code DatabaseEngine} from a JDBC DatabaseMetaData * object. * * @return the database engine or {@code null} if the database cannot * be determined from the input. * @throws SQLException if an error occurs while retrieving data from the * {@code databaseMetaData} object. */ public static DatabaseEngine getDatabaseEngine(DatabaseMetaData databaseMetaData) throws SQLException { // Sanity check Objects.requireNonNull(databaseMetaData, "databaseProductName cannot be null"); String databaseProductName = databaseMetaData.getDatabaseProductName(); String databaseProductVersion= databaseMetaData.getDatabaseProductVersion(); String databaseProductNameLower = databaseProductName.toLowerCase(Locale.US); // Detect MySQL // Note: the MySQL JDBC driver (known as Connector/J) is open-sourced // and available here: https://github.com/mysql/mysql-connector-j // so pretty easy to see what the driver returns for ProductName. if (databaseProductName.equals("MySQL")) { return MYSQL; } // Detect MariaDB if (databaseProductName.equals("MariaDB")) { return MARIADB; } // Detect MS SQL Server // Note: The MS SQL Server has been open-sourced and is now available // here: https://github.com/microsoft/mssql-jdbc so it is pretty easy // to see what the driver returns for ProductName. if (databaseProductName.equals("Microsoft SQL Server")) { return MSSQL; } // Detect Oracle RDBMS if (databaseProductNameLower.startsWith("oracle")) { return ORACLE; } // Detect PostgreSQL // According to documentation on the PgDatabaseMetaData class the // ProductName will always be "PostgreSQL". if (databaseProductName.equals("PostgreSQL")) { return POSTGRESQL; } // Detect H2 // ProductName will always be "H2". if (databaseProductName.equals("H2")) { return H2; } // Detect Amazon Redshift // ProductName will always be "Redshift". // See https://github.com/aws/amazon-redshift-jdbc-driver/blob/master/src/main/java/com/amazon/redshift/jdbc/RedshiftDatabaseMetaData.java // (Amazon Redshift used to ask users to use the PostgreSQL JDBC driver // - which will always return "PostgreSQL" - but nowadays they have their // own dedicated JDBC driver) if (databaseProductName.equals("Redshift")) { return REDSHIFT; } // Detect Apache Derby if (databaseProductName.equals("Apache Derby")) { return DERBY; } // Detect HyperSQL // The HSQLDB JDBC driver always returns the value of // org.hsqldb.persist.HsqlDatabaseProperties#PRODUCT_NAME if (databaseProductNameLower.equals("hsql database engine")) { return HSQLDB; } // Detect IBM Db2 // According to IBM's documentation the database engine must be detected // primarily from the ProductVersion value whereas IBM makes no real // promise on the value of the ProductName. This is why we use the ProductName // with some caution below. // See : https://www.ibm.com/docs/en/db2-for-zos/11?topic=methods-databasemetadata-identifying-type-data-server if ( databaseProductNameLower.equals("db2") | databaseProductNameLower.matches("db2[.-/ ].*") ) { if (databaseProductVersion != null && databaseProductVersion.length() > 3) { String ppp = databaseProductVersion.substring(0, 3); switch (ppp) { case "DSN": return DB2_ZOS; case "SQL": return DB2_LUW; default: } } } // Cannot determine return null; } /** * Find {@code DatabaseEngine} from a JDBC Connection. * The connection is left untouched (not closed by this method). * * @return the database engine or {@code null} if the database cannot * be determined from the input. * @throws SQLException if an error occurs while retrieving data from the * {@code connection} object. */ public static DatabaseEngine getDatabaseEngine(Connection connection) throws SQLException { Objects.requireNonNull(connection, "connection cannot be null"); return getDatabaseEngine(connection.getMetaData()); } /** * Find {@code DatabaseEngine} from a JDBC DataSource. * * @param dataSource opens a connection from this DataSource. The connection * is closed again before the the method returns. * @param username username for connecting to database engine. May be * {@code null} in which case a connection is obtained from the DataSource * without explicitly specifying username/password. * @param password password for connecting to database engine. May be * {@code null} in which case a connection is obtained from the DataSource * without explicitly specifying username/password. * @return the database engine or {@code null} if the database cannot * be determined from the input. * @throws SQLException if an error occurs while retrieving data from the * {@code dataSource} object. */ public static DatabaseEngine getDatabaseEngine(DataSource dataSource, String username, String password) throws SQLException { Objects.requireNonNull(dataSource, "dataSource cannot be null"); try (Connection connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password)) { return getDatabaseEngine(connection.getMetaData()); } } /** * Find {@code DatabaseEngine} from a JDBC DataSource. * *

* Same as calling {@link #getDatabaseEngine(javax.sql.DataSource, java.lang.String, java.lang.String) * getDatabaseEngine(dataSource, null, null)}. * @param dataSource opens a connection from this DataSource. The connection * is closed again before the the method returns. * @return the database engine or {@code null} if the database cannot * be determined from the input. * @throws SQLException if an error occurs while retrieving data from the * {@code dataSource} object. * @see #getDatabaseEngine(javax.sql.DataSource, java.lang.String, java.lang.String) */ public static DatabaseEngine getDatabaseEngine(DataSource dataSource) throws SQLException { return getDatabaseEngine(dataSource, null, null); } }