Skip to content

Instantly share code, notes, and snippets.

@lbruun
Last active August 27, 2022 20:17
Show Gist options
  • Save lbruun/9a7775b0bc3aca868c9500bfa2df42a0 to your computer and use it in GitHub Desktop.
Save lbruun/9a7775b0bc3aca868c9500bfa2df42a0 to your computer and use it in GitHub Desktop.
DatabaseEngine enum and detection of database engine
/*
* 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.
*
* <p>
* 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.
* <p>
* 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.
*
* <p>
* 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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment