All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jn.sqlhelper.dialect.DialectRegistry Maven / Gradle / Ivy

/*
 * Copyright 2019 the original author or authors.
 *
 * Licensed under the LGPL, Version 3.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.gnu.org/licenses/lgpl-3.0.html
 *
 * 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 com.jn.sqlhelper.dialect;

import com.jn.langx.annotation.Name;
import com.jn.langx.annotation.NonNull;
import com.jn.langx.annotation.Nullable;
import com.jn.langx.text.StringTemplates;
import com.jn.langx.text.properties.Props;
import com.jn.langx.util.Emptys;
import com.jn.langx.util.Objs;
import com.jn.langx.util.Preconditions;
import com.jn.langx.util.Strings;
import com.jn.langx.util.collection.Pipeline;
import com.jn.langx.util.function.Consumer;
import com.jn.langx.util.function.Predicate;
import com.jn.langx.util.reflect.Reflects;
import com.jn.langx.util.struct.Holder;
import com.jn.sqlhelper.common.ddl.SQLSyntaxCompatTable;
import com.jn.sqlhelper.common.utils.Connections;
import com.jn.sqlhelper.dialect.annotation.Driver;
import com.jn.sqlhelper.dialect.annotation.SyntaxCompat;
import com.jn.sqlhelper.dialect.internal.*;
import com.jn.sqlhelper.dialect.urlparser.DatabaseInfo;
import com.jn.sqlhelper.dialect.urlparser.JdbcUrlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;

public class DialectRegistry {

    private static final Logger logger = LoggerFactory.getLogger(DialectRegistry.class);
    private static final Map nameToDialectMap = new TreeMap();
    private static final Map classNameToNameMap = new TreeMap();
    // key:DatabaseMetaData.getProduceName() + getDriver();
    private static final Map> dbToDialectMap = new HashMap>();
    private static final Properties vendorDatabaseNameMappings = new Properties();
    private static final DialectRegistry registry = new DialectRegistry();

    static {
        loadDatabaseNameMappings();
        registerBuiltinDialects();
        loadCustomDialects();
    }

    private DialectRegistry() {
    }

    public static DialectRegistry getInstance() {
        return registry;
    }

    private static String databaseIdStringLowerCase(DatabaseMetaData databaseMetaData) {
        return databaseIdString(databaseMetaData).toLowerCase();
    }

    public static String databaseIdString(DatabaseMetaData databaseMetaData) {
        try {
            return databaseMetaData.getDatabaseProductName();
        } catch (SQLException ex) {
            try {
                return databaseMetaData.getDriverName().toLowerCase() + " version: " + databaseMetaData.getDriverVersion().toLowerCase();
            } catch (SQLException ex1) {
                // ignore it
            }
        }
        try {
            return databaseMetaData.getURL();
        } catch (SQLException ex) {
            logger.warn(ex.getMessage(), ex);
        }
        return databaseMetaData.getClass().getCanonicalName();
    }

    private static void loadCustomDialects() {
        loadCustomDialects(DialectRegistry.class.getClassLoader());
    }

    public static void loadCustomDialects(ClassLoader classLoader) {
        ServiceLoader serviceLoader = ServiceLoader.load(AbstractDialect.class, classLoader);
        for (AbstractDialect dialect : serviceLoader) {
            registerDialectByClass(dialect.getClass(), dialect);
        }
    }

    private static void registerBuiltinDialects() {
        logger.info("Start to register builtin dialects");

        final Class[] dialects = (Class[]) new Class[]{

                AccessDialect.class,
                ActorDBDialect.class,
                AgensGraphDialect.class,
                AliSQLDialect.class,
                AltibaseDialect.class,
                AntDBDialect.class,
                AuroraDialect.class,
                ArgoDBDialect.class,
                AzureDialect.class,
                As400Dialect.class,

                BesMagicDataDialect.class,
                BigObjectDialect.class,
                BrytlytDialect.class,

                CacheDialect.class,
                CitusDialect.class,
                ClickHouseDialect.class,
                ClustrixDialect.class,
                CobolDialect.class,
                CockroachDialect.class,
                ComDB2Dialect.class,
                CovenantSQLDialect.class,
                CrateDialect.class,
                CTreeDialect.class,
                CubridDialect.class,

                DatabricksDialect.class,
                DB2Dialect.class,
                DbfDialect.class,
                DerbyDialect.class,
                DmDialect.class,
                DorisDialect.class,
                DrillDialect.class,
                DuckDBDialect.class,

                ElasticsearchDialect.class,
                EsgynDBDialect.class,
                ExasolDialect.class,

                FileMakerDialect.class,
                FirebirdDialect.class,

                GaussDbDialect.class,
                GBaseDialect.class,
                GBase8sDialect.class,
                GoldenDBDialect.class,
                GreenplumDialect.class,

                H2Dialect.class,
                HANADialect.class,
                HawqDialect.class,
                HerdDBDialect.class,
                HhDbDialect.class,
                HighGoDialect.class,
                HiveDialect.class,
                HSQLDialect.class,

                IgniteDialect.class,
                ImpalaDialect.class,
                InformixDialect.class,
                IngresDialect.class,
                InterbaseDialect.class,
                IrisDialect.class,

                JDataStoreDialect.class,

                KarelDBDialect.class,
                KDBDialect.class,
                KingbaseDialect.class,
                KingDBDialect.class,
                KineticaDialect.class,
                KognitioDialect.class,

                LeanXcaleDialect.class,
                LinterDialect.class,

                MariaDBDialect.class,
                MaxComputeDialect.class,
                MaxDBDialect.class,
                MckoiDialect.class,
                MemSQLDialect.class,
                MimerSQLDialect.class,
                MonetDialect.class,
                ModeShapeDialect.class,
                MSQLDialect.class,
                MySQLDialect.class,

                Neo4jDialect.class,
                NetezzaDialect.class,
                NexusDBDialect.class,
                NuodbDialect.class,

                OBaseDialect.class,
                OmnisciDialect.class,
                OpenbaseDialect.class,
                OpenEdgeDialect.class,
                OracleDialect.class,
                OrientDBDialect.class,
                OscarDialect.class,

                ParadoxDialect.class,
                PerconaMysqlDialect.class,
                PhoenixDialect.class,
                PointbaseDialect.class,
                PostgreSQLDialect.class,
                PrestoDialect.class,

                RadonDBDialect.class,
                RaimaDialect.class,
                RBaseDialect.class,
                RDMSOS2200Dialect.class,
                RedshiftDialect.class,

                SadasDialect.class,
                SequoiaDBDialect.class,
                SinoDBDialect.class,
                SmallDialect.class,
                SnappyDataDialect.class,
                SnowflakeDialect.class,
                SpliceMachineDialect.class,
                SQLiteDialect.class,
                SQLServerDialect.class,
                SQLServerDialect.SQLServer2000Dialect.class,
                SQLServerDialect.SQLServer2005Dialect.class,
                SQLServerDialect.SQLServer2008Dialect.class,
                SQLServerDialect.SQLServer2012Dialect.class,
                SQLServerDialect.SQLServer2014Dialect.class,
                SQLServerDialect.SQLServer2016Dialect.class,
                SQLServerDialect.SQLServer2017Dialect.class,
                SQLServerDialect.SQLServer2019Dialect.class,
                SQLServerDialect.SQLServer2022Dialect.class,
                SQReamDialect.class,

                TajoDialect.class,
                TeradataDialect.class,
                TiDBDialect.class,
                TimesTenDialect.class,
                TrafodionDialect.class,
                TransbaseDialect.class,
                TrinoDialect.class,

                UxDBDialect.class,

                ValentinaDialect.class,
                VerticaDialect.class,
                VirtuosoDialect.class,
                VistaDBDialect.class,
                VoltDBDialect.class,

                XtremeSQLDialect.class,
                XuguDialect.class,
                XCloudDBDialect.class,

                YaacomoDialect.class,
                YugabyteDBDialect.class
        };

        for (Class clazz : Arrays.asList(dialects)) {
            registerDialectByClass(clazz, null);
        }

        logger.info("Registered dialects: {}", nameToDialectMap.keySet());
    }

    private static void loadDatabaseNameMappings() {
        logger.info("Start to load database mappings (product or vendor name => dialect name)");

        try {
            Properties props = Props.loadFromClasspath("/sqlhelper-dialect-database.properties");
            vendorDatabaseNameMappings.putAll(props);
        } catch (Throwable ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    public static Properties getVendorDatabaseIdMappings() {
        return vendorDatabaseNameMappings;
    }

    public static void setDatabaseName(String keywordsInDriver, String databaseId) {
        setDatabaseNameIfAbsent(keywordsInDriver, databaseId);
    }

    @Deprecated
    public static void setDatabaseId(String keywordsInDriver, String databaseId) {
        setDatabaseName(keywordsInDriver, databaseId);
    }

    public static void setDatabaseNameIfAbsent(String keywordsInDriver, String databaseId) {
        if (!vendorDatabaseNameMappings.containsKey(keywordsInDriver)) {
            vendorDatabaseNameMappings.setProperty(keywordsInDriver, databaseId);
        }
    }

    /**
     * 由 setDatabaseNameIfAbsent 替代
     */
    @Deprecated
    public static void setDatabaseIdIfAbsent(String keywordsInDriver, String databaseId) {
        setDatabaseNameIfAbsent(keywordsInDriver, databaseId);
    }

    public static String guessDatabaseId(DataSource dataSource) {
        if (dataSource == null) {
            throw new NullPointerException("dataSource cannot be null");
        }
        try {
            return guessDatabaseId(Connections.getDatabaseProductName(dataSource));
        } catch (Exception e) {
            logger.error("Could not get a databaseId from dataSource", e);
        }
        return null;
    }

    /**
     * guess based productName, url, driver etc
     *
     * @return database id
     */
    public static String guessDatabaseName(final String productName) {
        return guessDatabaseId(productName);
    }

    @Deprecated
    public static String guessDatabaseId(final String productName) {

        if (productName == null) {
            return null;
        }
        String tmpProductName = productName.toLowerCase();
        // if arg is a url
        int urlProtocolIndex = tmpProductName.indexOf("://");
        if (urlProtocolIndex==-1){
            urlProtocolIndex = tmpProductName.indexOf(":@//");
        }
        if (urlProtocolIndex != -1) {
            DatabaseInfo databaseInfo = JdbcUrlParser.INSTANCE.parse(productName);
            if (databaseInfo != null && !DatabaseInfo.UNKNOWN.equals(databaseInfo.getVendor().toLowerCase())) {
                tmpProductName = databaseInfo.getVendor().toLowerCase();
            } else {
                tmpProductName = tmpProductName.substring(0, urlProtocolIndex);
            }
        }
        int urlPropertyFragmentIndex = tmpProductName.indexOf("?");
        if (urlPropertyFragmentIndex != -1) {
            DatabaseInfo databaseInfo = JdbcUrlParser.INSTANCE.parse(productName);
            if (databaseInfo != null && !DatabaseInfo.UNKNOWN.equals(databaseInfo.getVendor().toLowerCase())) {
                tmpProductName = databaseInfo.getVendor().toLowerCase();
            } else {
                tmpProductName = tmpProductName.substring(0, urlPropertyFragmentIndex);
            }
        }

        String[] tokens = Strings.split(tmpProductName, ":");

        final Set productKeywords = vendorDatabaseNameMappings.stringPropertyNames();
        final Holder matchedProductHolder = new Holder();
        Pipeline.of(tokens).filter(new Predicate() {
            @Override
            public boolean test(String token) {
                return !Strings.equalsIgnoreCase(token, "jdbc");
            }
        }).forEach(new Consumer() {
            @Override
            public void accept(final String token) {
                String matchedProductKeyword = Pipeline.of(productKeywords)
                        .findFirst(new Predicate() {
                            @Override
                            public boolean test(String productKeyword) {
                                return token.contains(productKeyword.toLowerCase());
                            }
                        });
                if (Strings.isNotBlank(matchedProductKeyword)) {
                    matchedProductHolder.set(matchedProductKeyword);
                }
            }
        }, new Predicate() {
            @Override
            public boolean test(String token) {
                return !matchedProductHolder.isEmpty();
            }
        });

        String bestProductKeyword = matchedProductHolder.get();

        if (Strings.isNotBlank(bestProductKeyword)) {
            return vendorDatabaseNameMappings.getProperty(bestProductKeyword);
        }

        if (classNameToNameMap.containsKey(tmpProductName)) {
            return classNameToNameMap.get(tmpProductName);
        }
        return null;
    }

    private static Class loadDialectClass(final String className) throws ClassNotFoundException {
        return (Class) loadImplClass(className, Dialect.class);
    }

    private static Class loadDriverClass(final String className) throws ClassNotFoundException {
        return (Class) loadImplClass(className, java.sql.Driver.class);
    }

    private static Class loadImplClass(final String className, final Class superClass) throws ClassNotFoundException {
        Class clazz = null;
        try {
            clazz = Class.forName(className, true, DialectRegistry.class.getClassLoader());
        } catch (ClassNotFoundException ex) {
            // NOOP
        }
        if (clazz == null) {
            clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
        }
        if (superClass.isAssignableFrom(clazz)) {
            return clazz;
        }
        final String error = "Class " + Reflects.getFQNClassName(clazz) + " is not cast to " + Reflects.getFQNClassName(superClass);
        throw new ClassCastException(error);
    }

    private static Dialect registerDialectByClass(final Class clazz) {
        return registerDialectByClass(clazz, null);
    }

    private static Dialect registerDialectByClass(@NonNull final Class dialectClass, @Nullable Dialect dialect) {
        Preconditions.checkNotNull(dialectClass);
        // step 1: 生成 database id
        final Name nameAnno = Reflects.getAnnotation(dialectClass, Name.class);
        String name;
        if (nameAnno != null) {
            name = nameAnno.value();
            if (Strings.isBlank(name)) {
                throw new IllegalStateException("@Name is empty in class" + Reflects.getFQNClassName(dialectClass));
            }
        } else {
            final String simpleClassName = dialectClass.getSimpleName().toLowerCase();
            name =  Strings.replace(simpleClassName, "dialect", "");
        }
        if (dialect == null) {
            final Driver driverAnno = Reflects.getAnnotation(dialectClass, Driver.class);
            Class driverClass = null;
            Constructor driverConstructor = null;
            if (driverAnno != null) {
                String[] driverClassNames = Pipeline.of(driverAnno.value())
                        .filter(new Predicate() {
                            @Override
                            public boolean test(String driverClassName) {
                                return Strings.isNotBlank(driverClassName);
                            }
                        }).toArray(String[].class);

                if (Objs.isEmpty(driverClassNames)) {
                    throw new IllegalStateException("@Driver is empty in class" + Reflects.getFQNClassName(dialectClass));
                }
                for (int i = 0; i < driverClassNames.length; i++) {
                    String driverClassName = driverClassNames[i];
                    try {
                        driverClass = loadDriverClass(driverClassName);
                        try {
                            driverConstructor = dialectClass.getDeclaredConstructor(java.sql.Driver.class);
                        } catch (Throwable ex) {
                            logger.info("Can't find the driver based constructor for dialect {}", name);
                        }
                    } catch (Throwable ex) {
                        logger.info("Can't find driver class {} for {} dialect", driverClassName, name);
                    }

                    if (driverClass != null) {
                        break;
                    }
                }
            }
            if (driverClass == null || driverConstructor == null) {
                try {
                    try {
                        dialect = dialectClass.newInstance();
                    } catch (InstantiationException e2) {
                        final String error = StringTemplates.formatWithPlaceholder("Class {}  need a ()", Reflects.getFQNClassName(dialectClass));
                        throw new ClassFormatError(error);
                    } catch (IllegalAccessException e3) {
                        final String error = StringTemplates.formatWithPlaceholder("Class {}  need a ()", Reflects.getFQNClassName(dialectClass));
                        throw new ClassFormatError(error);
                    }
                } catch (Throwable ex) {
                    logger.error("Register dialect {} fail: {}", name, ex.getMessage(), ex);
                }
            } else {
                try {
                    try {
                        final Class expectDriverClass = driverClass;
                        java.sql.Driver driver = Pipeline.of(DriverManager.getDrivers()).findFirst(new Predicate() {
                            @Override
                            public boolean test(java.sql.Driver d) {
                                return expectDriverClass.isInstance(d);
                            }
                        });
                        if (driver != null) {
                            driverConstructor.setAccessible(true);
                            dialect = driverConstructor.newInstance(driver);
                        }
                    } catch (InstantiationException e2) {
                        final String error = StringTemplates.formatWithPlaceholder("Class {}  need a (Driver)", Reflects.getFQNClassName(dialectClass));
                        throw new ClassFormatError(error);
                    } catch (IllegalAccessException e3) {
                        final String error = StringTemplates.formatWithPlaceholder("Class {} need a public (Driver", Reflects.getFQNClassName(dialectClass));
                        throw new ClassFormatError(error);
                    } catch (InvocationTargetException e) {
                        logger.error("Register dialect {} fail: {}", name, e.getMessage(), e);
                    }
                } catch (Throwable ex) {
                    logger.error("Register dialect {} fail: {}", name, ex.getMessage(), ex);
                }
            }
        }
        if (dialect != null) {
            DialectRegistry.nameToDialectMap.put(name, dialect);
            DialectRegistry.classNameToNameMap.put(dialectClass.getCanonicalName(), name);
            setDatabaseName(name, name);
        }

        // step 2: 扫描兼容性
        if (dialect != null) {
            SyntaxCompat syntaxCompat = Reflects.getAnnotation(dialectClass, SyntaxCompat.class);
            if (syntaxCompat != null) {
                if (Emptys.isNotEmpty(syntaxCompat.value())) {
                    SQLSyntaxCompatTable.getInstance().register(name, syntaxCompat.value());
                }
            }
        }

        return dialect;
    }

    public Collection getDialects() {
        return nameToDialectMap.values();
    }

    public Dialect getDialectByClassName(final String className) {
        final String dialectName = DialectRegistry.classNameToNameMap.get(className);
        if (dialectName != null) {
            return this.getDialectByName(dialectName);
        }
        return null;
    }

    public Dialect getDialectByName(final String databaseId) {
        return DialectRegistry.nameToDialectMap.get(databaseId);
    }

    /**
     * @since 5.0.5
     * @param databaseId dialect or name
     * @return the dialect object
     */
    public Dialect gaussDialect(final String databaseId) {
        Dialect dialect = getDialectByName(databaseId);
        if(dialect == null && Strings.isNotBlank(databaseId)){
            String guessedDatabaseId = guessDatabaseId(databaseId);
            if(Strings.isNotEmpty(guessedDatabaseId)){
                dialect = getDialectByName(guessedDatabaseId);
            }
        }
        return dialect;
    }

    public Dialect getDialectByDatabaseMetadata(final DatabaseMetaData databaseMetaData) {
        Dialect dialect = null;
        if (databaseMetaData != null) {
            dialect = getDialectByResolutionInfo(new DatabaseMetaDataDialectResolutionInfoAdapter(databaseMetaData));
        }
        return dialect;
    }

    public Dialect getDialectByResolutionInfo(DialectResolutionInfo resolutionInfo) {
        Dialect dialect = null;
        if (resolutionInfo != null) {
            String databaseIdString = resolutionInfo.getDatabaseProductName();
            if (databaseIdString != null) {
                databaseIdString = Strings.lowerCase(resolutionInfo.getDatabaseProductName(), Locale.ROOT);
                Holder dialectHolder = dbToDialectMap.get(databaseIdString);
                if (dialectHolder != null) {
                    dialect = dialectHolder.get();
                }
                if (dialect == null) {
                    Enumeration keys = (Enumeration) vendorDatabaseNameMappings.propertyNames();
                    while (keys.hasMoreElements()) {
                        String key = keys.nextElement();
                        if (databaseIdString.contains(key.toLowerCase())) {
                            dialect = getDialectByName(vendorDatabaseNameMappings.getProperty(key));
                            if (dialect != null) {
                                dbToDialectMap.put(databaseIdString, new Holder(dialect));
                                break;
                            }
                        }
                    }
                }

                // sqlserver
                if (dialect == null) {
                    if (Strings.containsAny(databaseIdString.toLowerCase(), "sql server") || Strings.containsAny(databaseIdString.toLowerCase(), "sqlserver")) {
                        try {
                            String productionVersion = resolutionInfo.getDatabaseProductVersion();
                            if (Strings.isBlank(productionVersion)) {
                                productionVersion = resolutionInfo.getDatabaseMajorVersion() + "";
                            }
                            String tmpDatabaseId = SQLServerDialect.guessDatabaseId(productionVersion);
                            if (Emptys.isNotEmpty(tmpDatabaseId)) {
                                dialect = getDialectByName(vendorDatabaseNameMappings.getProperty(tmpDatabaseId));
                                if (dialect != null) {
                                    dbToDialectMap.put(databaseIdString, new Holder(dialect));
                                }
                            }
                        } catch (Throwable ex) {
                            // ignore it
                        }
                    }
                }
            }
        }
        return dialect;
    }

    public void registerDialectByClassName(final String dialectClassName) throws ClassNotFoundException {
        this.registerDialect(null, dialectClassName);
    }

    public void registerDialect(final String dialectName, final String className) throws ClassNotFoundException {
        final Class clazz = loadDialectClass(className);
        try {
            final Dialect dialect = registerDialectByClass(clazz);
            if (!Strings.isBlank(dialectName) && dialect != null) {
                DialectRegistry.nameToDialectMap.put(dialectName, dialect);
            }
        } catch (Throwable ex) {
            DialectRegistry.logger.info(ex.getMessage(), ex);
        }
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy