001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.dialect;
017
018
019import com.mybatisflex.core.FlexGlobalConfig;
020import com.mybatisflex.core.exception.FlexExceptions;
021import com.mybatisflex.core.exception.locale.LocalizedFormats;
022import com.mybatisflex.core.util.StringUtil;
023import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
024import org.apache.ibatis.logging.LogFactory;
025
026import javax.sql.DataSource;
027import java.lang.reflect.Method;
028import java.sql.Connection;
029import java.sql.PreparedStatement;
030import java.sql.ResultSet;
031import java.util.regex.Pattern;
032
033/**
034 * DbType 解析 工具类
035 */
036public class DbTypeUtil {
037
038    private DbTypeUtil() {
039    }
040
041    /**
042     * 获取当前数据库类型
043     * <p>首先从全局配置中获取数据库类型,如果全局配置中未设置,则尝试从方言工厂中获取线程局部变量设置的数据库类型
044     *
045     * @return 当前数据库类型,可能为null
046     */
047    public static DbType getCurrentDbType() {
048        DbType dbType = FlexGlobalConfig.getDefaultConfig().getDbType();
049        if (dbType == null) {
050            dbType = DialectFactory.getHintDbType();
051        }
052        return dbType;
053    }
054
055    /**
056     * 获取当前配置的 DbType
057     */
058    public static DbType getDbType(DataSource dataSource) {
059        String jdbcUrl = getJdbcUrl(dataSource);
060        if (StringUtil.hasText(jdbcUrl)) {
061            //FIX [Bug]: sqlserver2022下方言识别不对,手动set也无效  https://gitee.com/mybatis-flex/mybatis-flex/issues/IBIHW3
062            if (jdbcUrl.contains(":sqlserver:")) {
063                DbType sqlserverDbType = getSqlserverDbType(dataSource);
064                if (sqlserverDbType != null) {
065                    return sqlserverDbType;
066                }
067            }
068            return parseDbType(jdbcUrl);
069        }
070
071        throw new IllegalStateException("Can not get dataSource jdbcUrl: " + dataSource.getClass().getName());
072    }
073
074    /**
075     * 通过数据源获取 SQLserver 版本
076     *
077     * @return DbType
078     */
079    private static DbType getSqlserverDbType(DataSource dataSource) {
080        try (Connection connection = dataSource.getConnection();
081                PreparedStatement preparedStatement = connection.prepareStatement("SELECT @@VERSION");
082                ResultSet resultSet = preparedStatement.executeQuery()) {
083            //SELECT @@VERSION 查询返回信息:
084            /*
085            Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64)
086            Sep 24 2019 13:48:23
087            Copyright (C) 2019 Microsoft Corporation
088            Enterprise Edition (64-bit) on Windows Server 2019 Datacenter 10.0 <X64> (Build 17763: ) (Hypervisor)
089            */
090            if (resultSet.next()) {
091                String version = resultSet.getString(1);
092                if (StringUtil.hasText(version)) {
093                    String year = version.substring(21, 25);
094                    if (StringUtil.hasText(year) && year.compareTo("2008") <= 0) {
095                        return DbType.SQLSERVER_2005;
096                    }
097                }
098            }
099            return DbType.SQLSERVER;
100        } catch (Exception e) {
101            LogFactory.getLog(DbTypeUtil.class).warn("Failed to get SQLServer version. parse by url. " + e);
102            return null;
103        }
104    }
105
106    /**
107     * 通过数据源中获取 jdbc 的 url 配置
108     * 符合 HikariCP, druid, c3p0, DBCP, beecp 数据源框架 以及 MyBatis UnpooledDataSource 的获取规则
109     * UnpooledDataSource 参考 @{@link UnpooledDataSource#getUrl()}
110     *
111     * @return jdbc url 配置
112     */
113    public static String getJdbcUrl(DataSource dataSource) {
114        String[] methodNames = new String[]{"getUrl", "getJdbcUrl"};
115        for (String methodName : methodNames) {
116            try {
117                Method method = dataSource.getClass().getMethod(methodName);
118                return (String) method.invoke(dataSource);
119            } catch (Exception e) {
120                //ignore
121            }
122        }
123
124        try (Connection connection = dataSource.getConnection()) {
125            return connection.getMetaData().getURL();
126        } catch (Exception e) {
127            throw FlexExceptions.wrap(e, LocalizedFormats.DATASOURCE_JDBC_URL);
128        }
129    }
130
131
132    /**
133     * 参考 druid  和 MyBatis-plus 的 JdbcUtils
134     * {@link com.alibaba.druid.util.JdbcUtils#getDbType(String, String)}
135     * {@link com.baomidou.mybatisplus.extension.toolkit.JdbcUtils#getDbType(String)}
136     *
137     * @param jdbcUrl jdbcURL
138     * @return 返回数据库类型
139     */
140    public static DbType parseDbType(String jdbcUrl) {
141        jdbcUrl = jdbcUrl.toLowerCase();
142        if (jdbcUrl.contains(":ch:") || jdbcUrl.contains(":clickhouse:")) {
143            return DbType.CLICK_HOUSE;
144        } else if (jdbcUrl.contains(":cobar:")) {
145            return DbType.MYSQL;
146        } else if (jdbcUrl.contains(":csiidb:")) {
147            return DbType.CSIIDB;
148        } else if (jdbcUrl.contains(":cubrid:")) {
149            return DbType.CUBRID;
150        } else if (jdbcUrl.contains(":db2:")) {
151            return DbType.DB2;
152        } else if (jdbcUrl.contains(":derby:")) {
153            return DbType.DERBY;
154        } else if (isMatchedRegex(":dm\\d*:", jdbcUrl)) {
155            return DbType.DM;
156        } else if (jdbcUrl.contains(":duckdb:")) {
157            return DbType.DUCKDB;
158        } else if (jdbcUrl.contains(":firebirdsql:")) {
159            return DbType.FIREBIRD;
160        } else if (jdbcUrl.contains(":gaussdb:") || jdbcUrl.contains(":zenith:")) {
161            return DbType.GAUSS;
162        } else if (jdbcUrl.contains(":gbase:")) {
163            return DbType.GBASE;
164        } else if (jdbcUrl.contains(":gbase8c:")) {
165            return DbType.GBASE_8C;
166        } else if (jdbcUrl.contains(":gbase8s-pg:")) {
167            return DbType.GBASE_8S_PG;
168        } else if (jdbcUrl.contains(":gbasedbt-sqli:") || jdbcUrl.contains(":informix-sqli:")) {
169            return DbType.GBASE_8S;
170        } else if (jdbcUrl.contains(":goldendb:")) {
171            return DbType.GOLDENDB;
172        } else if (jdbcUrl.contains(":goldilocks:")) {
173            return DbType.GOLDILOCKS;
174        } else if (jdbcUrl.contains(":greenplum:")) {
175            return DbType.GREENPLUM;
176        } else if (jdbcUrl.contains(":h2:")) {
177            return DbType.H2;
178        } else if (jdbcUrl.contains(":highgo:")) {
179            return DbType.HIGH_GO;
180        } else if (jdbcUrl.contains(":hive2:") || jdbcUrl.contains(":inceptor2:")) {
181            return DbType.HIVE;
182        } else if (jdbcUrl.contains(":hsqldb:")) {
183            return DbType.HSQL;
184        } else if (jdbcUrl.contains(":impala:")) {
185            return DbType.IMPALA;
186        } else if (jdbcUrl.contains(":informix")) {
187            return DbType.INFORMIX;
188        } else if (isMatchedRegex(":kingbase\\d*:", jdbcUrl)) {
189            return DbType.KINGBASE_ES;
190        } else if (jdbcUrl.contains(":lealone:")) {
191            return DbType.LEALONE;
192        } else if (jdbcUrl.contains(":mariadb:")) {
193            return DbType.MARIADB;
194        } else if (jdbcUrl.contains(":mysql:")) {
195            return DbType.MYSQL;
196        } else if (jdbcUrl.contains(":oceanbase:")) {
197            return DbType.OCEAN_BASE;
198        } else if (jdbcUrl.contains(":opengauss:")) {
199            return DbType.OPENGAUSS;
200        } else if (jdbcUrl.contains(":oracle:")) {
201            return DbType.ORACLE;
202        } else if (jdbcUrl.contains(":oscar:")) {
203            return DbType.OSCAR;
204        } else if (jdbcUrl.contains(":phoenix:")) {
205            return DbType.PHOENIX;
206        } else if (jdbcUrl.contains(":postgresql:")) {
207            return DbType.POSTGRE_SQL;
208        } else if (jdbcUrl.contains(":presto:")) {
209            return DbType.PRESTO;
210        } else if (jdbcUrl.contains(":redshift:")) {
211            return DbType.REDSHIFT;
212        } else if (jdbcUrl.contains(":sap:")) {
213            return DbType.SAP_HANA;
214        } else if (jdbcUrl.contains(":sinodb")) {
215            return DbType.SINODB;
216        } else if (jdbcUrl.contains(":sqlite:")) {
217            return DbType.SQLITE;
218        } else if (jdbcUrl.contains(":sqlserver:")) {
219            return DbType.SQLSERVER_2005;
220        } else if (jdbcUrl.contains(":sqlserver2012:")) {
221            return DbType.SQLSERVER;
222        } else if (jdbcUrl.contains(":sundb:")) {
223            return DbType.SUNDB;
224        } else if (jdbcUrl.contains(":sybase:")) {
225            return DbType.SYBASE;
226        } else if (jdbcUrl.contains(":taos:") || jdbcUrl.contains(":taos-rs:")) {
227            return DbType.TDENGINE;
228        } else if (jdbcUrl.contains(":trino:")) {
229            return DbType.TRINO;
230        } else if (jdbcUrl.contains(":uxdb:")) {
231            return DbType.UXDB;
232        } else if (jdbcUrl.contains(":vastbase:")) {
233            return DbType.VASTBASE;
234        } else if (jdbcUrl.contains(":vertica:")) {
235            return DbType.VERTICA;
236        } else if (jdbcUrl.contains(":xcloud:")) {
237            return DbType.XCloud;
238        } else if (jdbcUrl.contains(":xugu:")) {
239            return DbType.XUGU;
240        } else if (jdbcUrl.contains(":yasdb:")) {
241            return DbType.YASDB;
242        } else {
243            return DbType.OTHER;
244        }
245    }
246
247    /**
248     * 正则匹配,验证成功返回 true,验证失败返回 false
249     */
250    public static boolean isMatchedRegex(String regex, String jdbcUrl) {
251        if (null == jdbcUrl) {
252            return false;
253        }
254        return Pattern.compile(regex).matcher(jdbcUrl).find();
255    }
256
257}