1 /*
2  * Copyright 2010-2020 Redgate Software Ltd
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *         http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.flywaydb.core.internal.jdbc;
17
18 import org.flywaydb.core.api.FlywayException;
19
20 import java.sql.Connection;
21 import java.sql.DatabaseMetaData;
22 import java.sql.PreparedStatement;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Types;
26
27 /**
28  * The various types of databases Flyway supports.
29  */

30 @SuppressWarnings("SqlDialectInspection")
31 public enum DatabaseType {
32     COCKROACHDB("CockroachDB", Types.NULL, false),
33     DB2("DB2", Types.VARCHAR, true),
34
35
36
37     DERBY("Derby", Types.VARCHAR, true),
38     FIREBIRD("Firebird", Types.NULL, true), // TODO does it support read only transactions?
39     H2("H2", Types.VARCHAR, true),
40     HSQLDB("HSQLDB", Types.VARCHAR, true),
41     INFORMIX("Informix", Types.VARCHAR, true),
42     MARIADB("MariaDB", Types.VARCHAR, true),
43     MYSQL("MySQL", Types.VARCHAR, true),
44     ORACLE("Oracle", Types.VARCHAR, true),
45     POSTGRESQL("PostgreSQL", Types.NULL, true),
46     REDSHIFT("Redshift", Types.VARCHAR, true),
47     SQLITE("SQLite", Types.VARCHAR, false),
48     SQLSERVER("SQL Server", Types.VARCHAR, true),
49     SYBASEASE_JTDS("Sybase ASE", Types.NULL, true),
50     SYBASEASE_JCONNECT("Sybase ASE", Types.VARCHAR, true),
51     SAPHANA("SAP HANA", Types.VARCHAR, true),
52     SNOWFLAKE("Snowflake", Types.VARCHAR, false);
53
54     private final String name;
55
56     private final int nullType;
57
58     private final boolean supportsReadOnlyTransactions;
59
60     DatabaseType(String name, int nullType, boolean supportsReadOnlyTransactions) {
61         this.name = name;
62         this.nullType = nullType;
63         this.supportsReadOnlyTransactions = supportsReadOnlyTransactions;
64     }
65
66     public static DatabaseType fromJdbcConnection(Connection connection) {
67         DatabaseMetaData databaseMetaData = JdbcUtils.getDatabaseMetaData(connection);
68         String databaseProductName = JdbcUtils.getDatabaseProductName(databaseMetaData);
69         String databaseProductVersion = JdbcUtils.getDatabaseProductVersion(databaseMetaData);
70
71         return fromDatabaseProductNameAndVersion(databaseProductName, databaseProductVersion, connection);
72     }
73
74     private static DatabaseType fromDatabaseProductNameAndVersion(String databaseProductName,
75                                                                   String databaseProductVersion,
76                                                                   Connection connection) {
77         if (databaseProductName.startsWith("Apache Derby")) {
78             return DERBY;
79         }
80         if (databaseProductName.startsWith("SQLite")) {
81             return SQLITE;
82         }
83         if (databaseProductName.startsWith("H2")) {
84             return H2;
85         }
86         if (databaseProductName.contains("HSQL Database Engine")) {
87             return HSQLDB;
88         }
89         if (databaseProductName.startsWith("Microsoft SQL Server")) {
90             return SQLSERVER;
91         }
92
93         // #2289: MariaDB JDBC driver 2.4.0 and newer report MariaDB as "MariaDB"
94         if (databaseProductName.startsWith("MariaDB")
95                 // Older versions of the driver report MariaDB as "MySQL"
96                 || (databaseProductName.contains("MySQL") && databaseProductVersion.contains("MariaDB"))
97                 // Azure Database For MariaDB reports as "MySQL"
98                 || (databaseProductName.contains("MySQL") && getSelectVersionOutput(connection).contains("MariaDB"))) {
99             return MARIADB;
100         }
101
102         if (databaseProductName.contains("MySQL")) {
103             // Google Cloud SQL returns different names depending on the environment and the SDK version.
104             //   ex.: Google SQL Service/MySQL
105             return MYSQL;
106         }
107         if (databaseProductName.startsWith("Oracle")) {
108             return ORACLE;
109         }
110         if (databaseProductName.startsWith("PostgreSQL")) {
111             String selectVersionQueryOutput = getSelectVersionOutput(connection);
112             if (databaseProductName.startsWith("PostgreSQL 8") && selectVersionQueryOutput.contains("Redshift")) {
113                 return REDSHIFT;
114             }
115             if (selectVersionQueryOutput.contains("CockroachDB")) {
116                 return COCKROACHDB;
117             }
118             return POSTGRESQL;
119         }
120         if (databaseProductName.startsWith("DB2")) {
121
122
123
124
125
126             return DB2;
127         }
128         if (databaseProductName.startsWith("ASE")) {
129             return SYBASEASE_JTDS;
130         }
131         if (databaseProductName.startsWith("Adaptive Server Enterprise")) {
132             return SYBASEASE_JCONNECT;
133         }
134         if (databaseProductName.startsWith("HDB")) {
135             return SAPHANA;
136         }
137         if (databaseProductName.startsWith("Informix")) {
138             return INFORMIX;
139         }
140         if (databaseProductName.startsWith("Firebird")) {
141             return FIREBIRD;
142         }
143         if (databaseProductName.startsWith("Snowflake")) {
144             return SNOWFLAKE;
145         }
146         throw new FlywayException("Unsupported Database: " + databaseProductName);
147     }
148
149     /**
150      * Retrieves the version string for this connection as described by SELECT VERSION(), which may differ
151      * from the connection metadata.
152      *
153      * @param connection The connection to use.
154      * @return The version string.
155      */

156     public static String getSelectVersionOutput(Connection connection) {
157         PreparedStatement statement = null;
158         ResultSet resultSet = null;
159
160         String result;
161         try {
162             statement = connection.prepareStatement("SELECT version()");
163             resultSet = statement.executeQuery();
164             result = null;
165             if (resultSet.next()) {
166                 result = resultSet.getString(1);
167             }
168         } catch (SQLException e) {
169             return "";
170         } finally {
171             JdbcUtils.closeResultSet(resultSet);
172             JdbcUtils.closeStatement(statement);
173         }
174
175         return result;
176     }
177
178     /**
179      * @return The human-readable name for this database.
180      */

181     public String getName() {
182         return name;
183     }
184
185     /**
186      * @return The JDBC type used to represent {@code null} in prepared statements.
187      */

188     public int getNullType() {
189         return nullType;
190     }
191
192     @Override
193     public String toString() {
194         return name;
195     }
196
197
198
199
200
201
202
203
204
205
206
207 }