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.exception.FlexExceptions;
020import com.mybatisflex.core.exception.locale.LocalizedFormats;
021import com.mybatisflex.core.util.StringUtil;
022import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
023import org.apache.ibatis.logging.LogFactory;
024
025import javax.sql.DataSource;
026import java.lang.reflect.Method;
027import java.sql.Connection;
028import java.sql.PreparedStatement;
029import java.sql.ResultSet;
030import java.util.regex.Pattern;
031
032/**
033 * DbType 解析 工具类
034 */
035public class DbTypeUtil {
036
037    private DbTypeUtil() {
038    }
039
040    /**
041     * 获取当前配置的 DbType
042     */
043    public static DbType getDbType(DataSource dataSource) {
044        String jdbcUrl = getJdbcUrl(dataSource);
045        if (StringUtil.hasText(jdbcUrl)) {
046            //FIX [Bug]: sqlserver2022下方言识别不对,手动set也无效  https://gitee.com/mybatis-flex/mybatis-flex/issues/IBIHW3
047            if (jdbcUrl.contains(":sqlserver:")) {
048                DbType sqlserverDbType = getSqlserverDbType(dataSource);
049                if (sqlserverDbType != null) {
050                    return sqlserverDbType;
051                }
052            }
053            return parseDbType(jdbcUrl);
054        }
055
056        throw new IllegalStateException("Can not get dataSource jdbcUrl: " + dataSource.getClass().getName());
057    }
058
059    /**
060     * 通过数据源获取 SQLserver 版本
061     *
062     * @return DbType
063     */
064    private static DbType getSqlserverDbType(DataSource dataSource) {
065        try (Connection connection = dataSource.getConnection();
066             PreparedStatement preparedStatement = connection.prepareStatement("SELECT @@VERSION");
067             ResultSet resultSet = preparedStatement.executeQuery()) {
068            //SELECT @@VERSION 查询返回信息:
069            /*
070             Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64)
071             Sep 24 2019 13:48:23
072             Copyright (C) 2019 Microsoft Corporation
073             Enterprise Edition (64-bit) on Windows Server 2019 Datacenter 10.0 <X64> (Build 17763: ) (Hypervisor)
074             */
075            if (resultSet.next()) {
076                String version = resultSet.getString(1);
077                if (StringUtil.hasText(version)) {
078                    String year = version.substring(21, 25);
079                    if (StringUtil.hasText(year) && year.compareTo("2005") <= 0) {
080                        return DbType.SQLSERVER_2005;
081                    }
082                }
083            }
084            return DbType.SQLSERVER;
085        } catch (Exception e) {
086            LogFactory.getLog(DbTypeUtil.class).warn("Failed to get SQLServer version. parse by url. " + e);
087            return null;
088        }
089    }
090
091    /**
092     * 通过数据源中获取 jdbc 的 url 配置
093     * 符合 HikariCP, druid, c3p0, DBCP, beecp 数据源框架 以及 MyBatis UnpooledDataSource 的获取规则
094     * UnpooledDataSource 参考 @{@link UnpooledDataSource#getUrl()}
095     *
096     * @return jdbc url 配置
097     */
098    public static String getJdbcUrl(DataSource dataSource) {
099        String[] methodNames = new String[]{"getUrl", "getJdbcUrl"};
100        for (String methodName : methodNames) {
101            try {
102                Method method = dataSource.getClass().getMethod(methodName);
103                return (String) method.invoke(dataSource);
104            } catch (Exception e) {
105                //ignore
106            }
107        }
108
109        try (Connection connection = dataSource.getConnection()) {
110            return connection.getMetaData().getURL();
111        } catch (Exception e) {
112            throw FlexExceptions.wrap(e, LocalizedFormats.DATASOURCE_JDBC_URL);
113        }
114    }
115
116
117    /**
118     * 参考 druid  和 MyBatis-plus 的 JdbcUtils
119     * {@link com.alibaba.druid.util.JdbcUtils#getDbType(String, String)}
120     * {@link com.baomidou.mybatisplus.extension.toolkit.JdbcUtils#getDbType(String)}
121     *
122     * @param jdbcUrl jdbcURL
123     * @return 返回数据库类型
124     */
125    public static DbType parseDbType(String jdbcUrl) {
126        jdbcUrl = jdbcUrl.toLowerCase();
127        if (jdbcUrl.contains(":mysql:") || jdbcUrl.contains(":cobar:")) {
128            return DbType.MYSQL;
129        } else if (jdbcUrl.contains(":mariadb:")) {
130            return DbType.MARIADB;
131        } else if (jdbcUrl.contains(":oracle:")) {
132            return DbType.ORACLE;
133        } else if (jdbcUrl.contains(":sqlserver2012:")) {
134            return DbType.SQLSERVER;
135        } else if (jdbcUrl.contains(":sqlserver:") || jdbcUrl.contains(":microsoft:")) {
136            return DbType.SQLSERVER_2005;
137        } else if (jdbcUrl.contains(":postgresql:")) {
138            return DbType.POSTGRE_SQL;
139        } else if (jdbcUrl.contains(":hsqldb:")) {
140            return DbType.HSQL;
141        } else if (jdbcUrl.contains(":db2:")) {
142            return DbType.DB2;
143        } else if (jdbcUrl.contains(":sqlite:")) {
144            return DbType.SQLITE;
145        } else if (jdbcUrl.contains(":h2:")) {
146            return DbType.H2;
147        } else if (isMatchedRegex(":dm\\d*:", jdbcUrl)) {
148            return DbType.DM;
149        } else if (jdbcUrl.contains(":xugu:")) {
150            return DbType.XUGU;
151        } else if (isMatchedRegex(":kingbase\\d*:", jdbcUrl)) {
152            return DbType.KINGBASE_ES;
153        } else if (jdbcUrl.contains(":phoenix:")) {
154            return DbType.PHOENIX;
155        } else if (jdbcUrl.contains(":zenith:")) {
156            return DbType.GAUSS;
157        } else if (jdbcUrl.contains(":gbase:")) {
158            return DbType.GBASE;
159        } else if (jdbcUrl.contains(":gbasedbt-sqli:") || jdbcUrl.contains(":informix-sqli:")) {
160            return DbType.GBASE_8S;
161        } else if (jdbcUrl.contains(":ch:") || jdbcUrl.contains(":clickhouse:")) {
162            return DbType.CLICK_HOUSE;
163        } else if (jdbcUrl.contains(":oscar:")) {
164            return DbType.OSCAR;
165        } else if (jdbcUrl.contains(":sybase:")) {
166            return DbType.SYBASE;
167        } else if (jdbcUrl.contains(":oceanbase:")) {
168            return DbType.OCEAN_BASE;
169        } else if (jdbcUrl.contains(":highgo:")) {
170            return DbType.HIGH_GO;
171        } else if (jdbcUrl.contains(":cubrid:")) {
172            return DbType.CUBRID;
173        } else if (jdbcUrl.contains(":goldilocks:")) {
174            return DbType.GOLDILOCKS;
175        } else if (jdbcUrl.contains(":csiidb:")) {
176            return DbType.CSIIDB;
177        } else if (jdbcUrl.contains(":sap:")) {
178            return DbType.SAP_HANA;
179        } else if (jdbcUrl.contains(":impala:")) {
180            return DbType.IMPALA;
181        } else if (jdbcUrl.contains(":vertica:")) {
182            return DbType.VERTICA;
183        } else if (jdbcUrl.contains(":xcloud:")) {
184            return DbType.XCloud;
185        } else if (jdbcUrl.contains(":firebirdsql:")) {
186            return DbType.FIREBIRD;
187        } else if (jdbcUrl.contains(":redshift:")) {
188            return DbType.REDSHIFT;
189        } else if (jdbcUrl.contains(":opengauss:")) {
190            return DbType.OPENGAUSS;
191        } else if (jdbcUrl.contains(":taos:") || jdbcUrl.contains(":taos-rs:")) {
192            return DbType.TDENGINE;
193        } else if (jdbcUrl.contains(":informix")) {
194            return DbType.INFORMIX;
195        } else if (jdbcUrl.contains(":sinodb")) {
196            return DbType.SINODB;
197        } else if (jdbcUrl.contains(":uxdb:")) {
198            return DbType.UXDB;
199        } else if (jdbcUrl.contains(":greenplum:")) {
200            return DbType.GREENPLUM;
201        } else if (jdbcUrl.contains(":lealone:")) {
202            return DbType.LEALONE;
203        } else if (jdbcUrl.contains(":hive2:")) {
204            return DbType.HIVE;
205        } else if (jdbcUrl.contains(":duckdb:")) {
206            return DbType.DUCKDB;
207        } else {
208            return DbType.OTHER;
209        }
210    }
211
212    /**
213     * 正则匹配,验证成功返回 true,验证失败返回 false
214     */
215    public static boolean isMatchedRegex(String regex, String jdbcUrl) {
216        if (null == jdbcUrl) {
217            return false;
218        }
219        return Pattern.compile(regex).matcher(jdbcUrl).find();
220    }
221
222}