目的:现已经写好了一个拦截器DmSqlRewriteInterceptor,可以实现将mysql的sql语句转换为达梦的sql语句。打包的目的是希望,在将mysql切换为达梦时,只需要导入我这个包以及修改数据库连接的配置,就能直接实现mysql数据库到达梦数据库的切换

打包项目的名称:dm-sql-rewrite-starter

项目目录的结构:

dm-sql-rewrite-starter/
 ├─ src/main/java/com/yourcompany/dm/
 │   ├─ interceptor/
 │   │   └─ DmSqlRewriteInterceptor.java        // 你的拦截器(稍作构造器调整以接收props)
 │   ├─ autoconfig/
 │   │   └─ DmSqlRewriteAutoConfiguration.java // 自动装配类(关键)
 │   └─ properties/
 │       └─ DmSqlRewriteProperties.java       // 可配置项(prefix=dm.sql.rewrite)
 ├─ src/main/resources/
 │   ├─ META-INF/spring.factories
 │   └─ spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
 └─ pom.xml

pom.xml文件:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zdsc</groupId>
    <artifactId>dm-sql-rewrite-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>dm-sql-rewrite-starter</name>
    <description>Starter for converting MySQL applications to Dameng Database</description>
    <url>http://www.zdsc.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <spring-boot.version>2.5.4</spring-boot.version>
        <mybatis.version>3.5.7</mybatis.version>
        <jsqlparser.version>4.0</jsqlparser.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring Boot AutoConfiguration -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>${spring-boot.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.3</version>
        </dependency>

        <!-- Configuration Processor -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>${spring-boot.version}</version>
            <optional>true</optional>
        </dependency>

        <!-- MyBatis Core -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- JSqlParser for SQL rewriting -->
        <!-- 注意,这里一定不能写<scope>provided</scope> -->
        <dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>${jsqlparser.version}</version>
        </dependency>

        <!-- SLF4J API -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.32</version>
            <scope>provided</scope>
        </dependency>

        <!--达梦数据驱动-->
        <!-- 注意,这里一定不能写<scope>provided</scope> 因为达梦的驱动包是自己手动导入的,所以打包的时候,必须也得带上DmJdbcDriver的jar包-->
        <dependency>
            <groupId>com.dm</groupId>
            <artifactId>DmJdbcDriver</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.4.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals><goal>shade</goal></goals>
                        <configuration>
                            <!-- 让生成的主构件就是 shaded 后的 jar(而不是额外的 -shaded.jar) -->
                            <shadedArtifactAttached>false</shadedArtifactAttached>
                            <!-- 生成 dependency-reduced-pom,避免使用方再拉同一个依赖形成重复 -->
                            <createDependencyReducedPom>true</createDependencyReducedPom>

                            <!-- 只把下面列出的依赖打进来,避免把整个依赖树都塞进 starter -->
                            <artifactSet>
                                <includes>
                                    <!-- 必选:达梦驱动 -->
                                    <include>com.dm:DmJdbcDriver</include>
                                    <!-- 可选:如果你也想“物理打包” jsqlparser,而不是仅做传递依赖,就保留下一行 -->
                                    <include>com.github.jsqlparser:jsqlparser</include>
                                </includes>
                            </artifactSet>

                            <!-- 仅对 jsqlparser 做 relocate,避免与使用方可能已有版本冲突。
                                 注意:千万不要 relocate 达梦驱动,否则 driver-class-name 就找不到了 -->
                            <relocations>
                                <relocation>
                                    <pattern>net.sf.jsqlparser</pattern>
                                    <shadedPattern>com.zdsc.shaded.net.sf.jsqlparser</shadedPattern>
                                </relocation>
                            </relocations>

                            <!-- 合并 SPI 文件,保证 META-INF/services 可正常工作(虽说 Hikari 用类名加载,但合并更稳妥) -->
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.20</version>
                        </path>
                        <path>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                            <version>${spring-boot.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>

            <!-- Maven Jar Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

            <!-- Spring Boot Maven Plugin (optional) -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <skip>true</skip> <!-- 不需要打包可执行JAR -->
                </configuration>
            </plugin>
        </plugins>
    </build>

    <!-- 发布到仓库的配置 -->
    <distributionManagement>
        <repository>
            <id>nexus-releases</id>
            <url>http://your-nexus/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>nexus-snapshots</id>
            <url>http://your-nexus/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>
</project>
DmSqlRewriteAutoConfiguration
/**
 * DM数据库SQL重写拦截器的自动配置类(兼容多版本的 Configuration 插件检查)
 */
@Configuration
@ConditionalOnClass(name = "org.apache.ibatis.session.SqlSessionFactory")
@EnableConfigurationProperties(DmSqlRewriteProperties.class)
@ConditionalOnProperty(name = "dm.sql.rewrite.enabled", havingValue = "true", matchIfMissing = true)
public class DmSqlRewriteAutoConfiguration {

    @Autowired
    private DmSqlRewriteProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public DmSqlRewriteInterceptor dmSqlRewriteInterceptor() {
        return new DmSqlRewriteInterceptor(properties.isQuoteAndToLower(), properties.isDebugLog());
    }

    @Bean
    @ConditionalOnMissingBean
    public ConfigurationCustomizer dmConfigurationCustomizer(DmSqlRewriteInterceptor interceptor) {
        return configuration -> {
            boolean exists = false;
            try {
                // 尝试多种可能的方法名(getPlugins / getInterceptors)
                Method m = null;
                try { m = configuration.getClass().getMethod("getPlugins"); } catch (NoSuchMethodException ignored) {}
                if (m == null) {
                    try { m = configuration.getClass().getMethod("getInterceptors"); } catch (NoSuchMethodException ignored) {}
                }
                if (m != null) {
                    Object res = m.invoke(configuration);
                    if (res != null) {
                        if (res.getClass().isArray()) {
                            Object[] arr = (Object[]) res;
                            for (Object p : arr) {
                                if (p != null && p.getClass().getName().equals(interceptor.getClass().getName())) {
                                    exists = true;
                                    break;
                                }
                            }
                        } else if (res instanceof Collection) {
                            for (Object p : (Collection<?>) res) {
                                if (p != null && p.getClass().getName().equals(interceptor.getClass().getName())) {
                                    exists = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                // 反射检查出错了,降级处理:不阻止注册(会在下方尝试注册)
                // 可选:记录日志
                // logger.warn("检查已注册 Interceptor 时出错,改为直接注册:{}", t.getMessage());
            }

            if (!exists) {
                try {
                    configuration.addInterceptor((Interceptor) interceptor);
                } catch (Throwable t) {
                    // 如果强制 addInterceptor 也失败(极少见),可以记录日志
                    // logger.error("将 DmSqlRewriteInterceptor 注册到 MyBatis Configuration 失败", t);
                }
            }
        };
    }
}
DmSqlRewriteProperties
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "dm.sql.rewrite")
public class DmSqlRewriteProperties {
    /**
     * 是否启用自动改写拦截器
     */
    private boolean enabled = true;

    /**
     * 原来拦截器你的构造参数:是否把标识符转小写并加引号
     */
    private boolean quoteAndToLower = true;

    /**
     * 是否打印原 SQL -> 改写后 SQL 的 debug 日志
     */
    private boolean debugLog = false;

    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }

    public boolean isQuoteAndToLower() { return quoteAndToLower; }
    public void setQuoteAndToLower(boolean quoteAndToLower) { this.quoteAndToLower = quoteAndToLower; }

    public boolean isDebugLog() { return debugLog; }
    public void setDebugLog(boolean debugLog) { this.debugLog = debugLog; }
}
DmSqlRewriteInterceptor
package com.zdsc.dm.interceptor;

import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * DmSqlRewriteInterceptor
 * <p>
 * 用途:在 StatementHandler.prepare 时,对 SQL 做简单的兼容处理 ——
 * 当前版本仅支持“去掉反引号`”的操作(可配置开关 removeBackticks)。
 * <p>
 * 说明:为了最小化对现有逻辑的改动,保留了原先基于 JSqlParser 的方法(以便未来扩展),
 * 但拦截器的实际行为现在是:当 removeBackticks=true 且数据库为达梦时,
 * 在把 SQL 写回到 BoundSql 之前,对 SQL 字符串做一次稳健的反引号删除(不会删除字符串字面量内的反引号)。
 */


/**
 * @Intercepts / @Signature:告诉 MyBatis:把这个拦截器织入到 StatementHandler.prepare(Connection,Integer) 调用点。
 * 也就是说,当 MyBatis 在准备 JDBC PreparedStatement 时,会调用此拦截器的 intercept。
 * <p>
 * implements Interceptor:实现 MyBatis 的插件接口,需要实现 intercept、plugin、setProperties。
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DmSqlRewriteInterceptor implements Interceptor {
    //日志记录器
    private static final Logger logger = LoggerFactory.getLogger(DmSqlRewriteInterceptor.class);


    //sqlCache:缓存原 SQL → 改写后 SQL。避免每次请求都解析 SQL,性能关键。用 ConcurrentHashMap 线程安全。
    private final ConcurrentHashMap<String, String> sqlCache = new ConcurrentHashMap<>();

    // 类成员:记录别名被重命名的映射 oldAlias -> newAlias 每个线程都有自己独立的 Map
    private final ThreadLocal<Map<String, String>> aliasRenameMap = ThreadLocal.withInitial(HashMap::new);

    // 类成员:保留字集合(大写),可根据需要扩展/从配置加载
    private static final Set<String> RESERVED_WORDS = new HashSet<>(Arrays.asList(
            "USER", "GROUP", "ORDER", "SELECT", "FROM", "WHERE", "JOIN", "TABLE", "INDEX", "PRIMARY",
            "VALUES", "LIMIT", "LIKE", "HAVING", "KEY", "TYPE", "DESC", "ASC", "CURRENT_DATE",
            "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "GRANT", "REVOKE"
            // 按需扩展或从配置加载
    ));

    // 新增:是否移除反引号(`)
    private final boolean removeBackticks;

    // 是否打印改写日志(生产建议 false,开发可 true)
    private final boolean debugLog;

    //提供默认构造器(默认启用改写、关闭详细日志)以及可配置构造器,便于在 Spring 注册时传入不同参数
    public DmSqlRewriteInterceptor() {
        this(true, false);
    }

    public DmSqlRewriteInterceptor(boolean removeBackticks, boolean debugLog) {
        this.removeBackticks = removeBackticks;
        this.debugLog = debugLog;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 取 Connection(prepare 的第一个参数)
        //去JDBC Connection prepare (Connection,int) 的第一个参数就是 JDBC Connection。我们拿到它用于判断数据库类型(只对 DM 生效)。
        Connection connection = (Connection) invocation.getArgs()[0];

        // 2. 仅在达梦 DB 时生效
        try {
            if (connection == null || connection.getMetaData() == null) {
                return invocation.proceed();
            }
            String productName = connection.getMetaData().getDatabaseProductName();
            logger.info("当前数据库连接的产品是: {}", productName);
            if (productName == null || !productName.toLowerCase().contains("dm")) {
                return invocation.proceed();
            }
        } catch (Exception e) {
            logger.debug("无法获取数据库产品名称,跳过 SQL 重写.", e);
            return invocation.proceed();
        }

        // MyBatis 在运行时会把 StatementHandler 包多层代理(插件链),因此需要“解包”(unwrap)才能安全访问内部字段。
        Object target = invocation.getTarget();
        //SystemMetaObject.forObject(target):把对象包装成 MetaObject,可以通过字符串路径读取内部属性(例如 delegate.boundSql)。
        MetaObject metaObject = SystemMetaObject.forObject(target);

        /**
         * 先连续 hasGetter("h")、hasGetter("target") 的解包循环,常见于 JDK 动态代理(h),和 MyBatis 的 Plugin(target)。
         * 保证我们最终能访问到真正的 StatementHandler 内部 delegate 对象。
         */
        // 常见解包:JDK 动态代理 handler 字段为 "h"
        while (metaObject.hasGetter("h")) {
            Object h = metaObject.getValue("h");
            metaObject = SystemMetaObject.forObject(h);
        }
        // MyBatis Plugin 包装时,内部对象通常叫 "target"
        while (metaObject.hasGetter("target")) {
            Object inner = metaObject.getValue("target");
            metaObject = SystemMetaObject.forObject(inner);
        }

        BoundSql boundSql;
        try {
            boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        } catch (Exception ex) {
            // 兜底:若无法通过 metaObject 取到,尝试直接 cast
            try {
                //如果以上方法都失败,尝试 target instanceof StatementHandler 强转取 getBoundSql()(另一个兜底路径)。
                //要尽量通用地获取 BoundSql,避免因为代理层次不同而抛异常(这是之前遇到“no getter for delegate”的原因)。
                if (target instanceof StatementHandler) {
                    boundSql = ((StatementHandler) target).getBoundSql();
                } else {
                    logger.debug("无法获取 BoundSql,跳过重写。", ex);
                    return invocation.proceed();
                }
            } catch (Exception ex2) {
                logger.debug("无法获取 BoundSql,跳过重写。", ex2);
                return invocation.proceed();
            }
        }

        //若 SQL 为空(可能不是 DML/查询),则不处理直接执行。
        String originalSql = boundSql == null ? null : boundSql.getSql();
        if (originalSql == null || originalSql.trim().isEmpty()) {
            return invocation.proceed();
        }

        logger.info("原始 SQL语句为: {}", originalSql);

        // 只做去反引号(带缓存),不再使用 JSqlParser 解析或转小写等操作
        String rewritten = rewriteSqlCached(originalSql);

        logger.info("改造后的 SQL语句为: {}", rewritten);


        logger.debug("原始的 SQL语句为: {}", originalSql);

        /**
         * 如果改写后 SQL 与原 SQL 不同,用 metaObject.setValue("delegate.boundSql.sql", rewritten)
         * 把改写后的 SQL 写回 MyBatis 的 BoundSql 中。这样 MyBatis 后续会使用改写后的 SQL 去 prepare statement 并执行
         * ,不影响参数绑定。
         */
        if (!originalSql.equals(rewritten)) {
            try {
                metaObject.setValue("delegate.boundSql.sql", rewritten);
                if (debugLog) {
                    logger.info("原始的sql-->改写后的sql:\n{}\n--> {}\n", originalSql, rewritten);
                } else {
                    // 采样日志:只记录少量示例以便排错(这里简单记录)
                    logger.debug("重写SQL(示例): {}", rewritten.length() > 200 ? rewritten.substring(0, 200) + "..." : rewritten);
                }
            } catch (Exception e) {
                // 写回失败不影响执行(回退)
                logger.warn("设置重写的SQL失败,继续原始操作。原因: {}", e.getMessage());
                //写回失败(反射或路径变化)会 catch 并回退(保证业务不被中断)。
                return invocation.proceed();
            }
        }

        return invocation.proceed();
    }

    /**
     *
     * @param target
     * @return plugin:MyBatis 要求返回包装后的对象(通常直接 Plugin.wrap)。
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * setProperties:允许通过配置文件传入 properties(这里留空,可扩展为从 properties 中读取 quoteAndToLower / debugLog)。
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        // 可通过 properties 读取开关
    }

    /**
     * 带缓存的改写(线程安全)
     * 现在的实现仅做去反引号并使用缓存。
     */
    private String rewriteSqlCached(String sql) {
        // 若不开启 removeBackticks,直接返回原 SQL(不走解析)
        if (!removeBackticks) return sql;
        if (sql == null || sql.isEmpty()) return sql;

        // 缓存 key 带上标识,避免未来策略变化冲突
        String key = "R:" + sql;
        String cached = sqlCache.get(key);
        if (cached != null) return cached;

        String rewritten = sql;
        try {
            // 使用 JSqlParser 的 AST 路径做逐节点处理(保证你后续修改函数时有位置可扩展)
            rewritten = rewriteSqlWithJSqlParser(sql, removeBackticks /* 参数仅作占位: 使用 removeBackticks 字段控制实际行为 */);
        } catch (Throwable t) {
            logger.debug("JSqlParser解析SQL失败,返回原始SQL。原因: {}", t.getMessage());
            rewritten = sql;
        }

        sqlCache.putIfAbsent(key, rewritten);
        return sqlCache.get(key);
    }

    /**
     * 用 JSqlParser 把传入的 SQL 字符串解析成 AST,然后针对常见 DML(SELECT/INSERT/UPDATE/DELETE)
     * <p>
     * 说明:此方法保留以便未来需要,但当前拦截逻辑并不调用它。
     */
    private String rewriteSqlWithJSqlParser(String sql, boolean removeBackticks) throws Exception {
        //新增:每条语句开始前清空映射,避免串台
        clearAliasRenameMap();
        // 解析 SQL 为 AST
        Statement stmt = CCJSqlParserUtil.parse(sql);
        // 在一次语句处理过程中复用同一个 visitor,保证 aliasRenameMap 的一致性
        ColumnExpressionVisitor visitor = new ColumnExpressionVisitor(removeBackticks);

        if (stmt instanceof Select) {
            Select select = (Select) stmt;
            SelectBody body = select.getSelectBody();
            processSelectBody(body, removeBackticks);
            return select.toString();
        } else if (stmt instanceof Insert) {
            Insert insert = (Insert) stmt;
            // 处理表名
            if (insert.getTable() != null) {
                insert.getTable().accept(visitor);
            }
            // 处理列列表
            if (insert.getColumns() != null) {
                insert.getColumns().forEach(c -> c.accept(visitor));
            }
            // 处理 itemsList:支持 SubSelect、ExpressionList、MultiExpressionList(VALUES 多行)
            ItemsList itemsList = insert.getItemsList();
            // 插入值中可能包含 subselect,若有需要可以递归处理(JSqlParser 的 Insert 支持 getItemsList)
            if (itemsList instanceof SubSelect) {
                SubSelect ss = (SubSelect) itemsList;
                if (ss.getSelectBody() != null) processSelectBody(ss.getSelectBody(), removeBackticks);
            } else if (itemsList instanceof ExpressionList) {
                ExpressionList el = (ExpressionList) itemsList;
                if (el.getExpressions() != null) {
                    el.getExpressions().forEach(e -> {
                        if (e != null) e.accept(visitor);
                    });
                }
            } else if (itemsList instanceof MultiExpressionList) {
                MultiExpressionList mel = (MultiExpressionList) itemsList;
                mel.getExprList().forEach(exprList -> {
                    if (exprList.getExpressions() != null) {
                        exprList.getExpressions().forEach(e -> {
                            if (e != null) e.accept(visitor);
                        });
                    }
                });
            }
            return insert.toString();
        } else if (stmt instanceof Update) {
            Update up = (Update) stmt;
            // 处理表名
            if (up.getTable() != null) {
                up.getTable().accept(visitor);
            }
            // 处理要更新的列
            if (up.getColumns() != null) {
                up.getColumns().forEach(c -> {
                    if (c != null) c.accept(visitor);
                });
            }
            // 处理 WHERE 条件
            if (up.getWhere() != null) {
                up.getWhere().accept(visitor);
            }
            return up.toString();
        } else if (stmt instanceof Delete) {
            Delete del = (Delete) stmt;
            if (del.getTable() != null) {
                del.getTable().accept(visitor);
            }
            if (del.getWhere() != null) {
                del.getWhere().accept(visitor);
            }
            return del.toString();
        }

        // 未处理的语句类型回退原 SQL
        return sql;
    }


    /* --------------------- 辅助 AST 处理方法 --------------------- */

    /**
     * 这是一个私有方法(只在当前类内使用),输入是一个 SelectBody(JSqlParser 表示 SELECT 语句主体的抽象),和一个布尔开关 removeBackticks(表示在处理节点时是否执行去除反引号的操作)。
     * <p>
     * 设计意图:统一入口,递归/分发处理各种 SELECT 的具体变体(普通 SELECT、UNION/INTERSECT/...、CTE/ WITH 子句等)。
     *
     * @param body
     * @param removeBackticks
     */
    private void processSelectBody(SelectBody body, boolean removeBackticks) {
        if (body == null) return;
        if (body instanceof PlainSelect) {
            /**
             * 检查 body 是否为 PlainSelect(最常见的一类:SELECT ... FROM ... [WHERE ...] [GROUP BY ...] [ORDER BY ...])。
             * 通常包含 FROM、JOIN、SELECT 列表、WHERE、GROUP BY、HAVING、ORDER BY 等子结构。
             */
            plainSelectHandle((PlainSelect) body, removeBackticks);
        } else if (body instanceof SetOperationList) {
            /**
             * 处理集合操作的 SELECT,例如 UNION / UNION ALL / INTERSECT / EXCEPT 等。
             */
            SetOperationList sol = (SetOperationList) body;
            List<SelectBody> selects = sol.getSelects();
            //嵌套语句,对每个分支递归调用 processSelectBody,以保证 UNION 的每一部分都能被同样改写。
            if (selects != null) selects.forEach(sb -> processSelectBody(sb, removeBackticks));
        } else if (body instanceof WithItem) {
            /**
             *处理 CTE(Common Table Expression,公用表表达式),即 WITH name AS (SELECT ...) SELECT ... 形式。
             */
            WithItem wi = (WithItem) body;
            processSelectBody(wi.getSelectBody(), removeBackticks);
        }
    }

    /**
     * 遍历并处理一个 SQL 语句中的各个部分(FROM、JOIN、SELECT、WHERE、GROUP BY、ORDER BY、HAVING),统一做别名/字段的引号或反引号处理。
     *
     * @param ps
     * @param removeBackticks
     */
    private void plainSelectHandle(PlainSelect ps, boolean removeBackticks) {
        ColumnExpressionVisitor visitor = new ColumnExpressionVisitor(removeBackticks);
        //如果 FROM 部分有表或子查询,就调用 handleFromItem 去处理它。
        if (ps.getFromItem() != null) {
//            handleFromItem(ps.getFromItem(), removeBackticks);
//            ps.getFromItem().accept(new ColumnExpressionVisitor(removeBackticks));
            handleFromItemWithVisitor(ps.getFromItem(), visitor);
        }
        ;

        //遍历所有 JOIN 子句
        if (ps.getJoins() != null) {
            ps.getJoins().forEach(j -> {
                //处理 JOIN 的 FROM 部分
                if (j.getRightItem() != null) {
//                    handleFromItem(j.getRightItem(), removeBackticks);
//                    j.getRightItem().accept(new ColumnExpressionVisitor(removeBackticks));
                    handleFromItemWithVisitor(j.getRightItem(), visitor);
                }
                //处理 JOIN 的 ON 部分
                if (j.getOnExpression() != null) {
                    j.getOnExpression().accept(visitor);
                }
                //处理 JOIN 的 USING 部分
                if (j.getUsingColumns() != null) {
//                    j.getUsingColumns().forEach(c -> quoteColumn(c, removeBackticks));
                    j.getUsingColumns().forEach(c -> c.accept(visitor));
                }
            });
        }

        //遍历 SELECT 里面的字段
        if (ps.getSelectItems() != null) {
            ps.getSelectItems().forEach(si -> {
                //普通表达式 (SelectExpressionItem),
                if (si instanceof SelectExpressionItem) {
                    SelectExpressionItem sei = (SelectExpressionItem) si;
                    //处理字段或函数里的列(比如 SUM(u.price))。
                    if (sei.getExpression() != null) {
                        sei.getExpression().accept(visitor);
                    }
                    //如果有别名(AS total),就调用 rewriteAlias 来处理别名是否加/去引号。
                    Alias alias = sei.getAlias();
                    if (alias != null) {
                        sei.setAlias(rewriteAlias(alias, removeBackticks));
                    }
                } else if (si instanceof AllTableColumns) {
                    //AllTableColumns (table.*):如果是 t.* 这种形式,就调用 quoteTable 处理表名。
                    AllTableColumns atc = (AllTableColumns) si;
                    Table t = atc.getTable();
                    if (t != null) {
//                        quoteTable(t, removeBackticks);
                        t.accept(visitor);
                    }
                }
            });
        }
        //处理 WHERE,用 ColumnExpressionVisitor 处理。
        if (ps.getWhere() != null) ps.getWhere().accept(visitor);
        //遍历 GROUP BY 的字段或表达式,调用 ColumnExpressionVisitor。
        if (ps.getGroupBy() != null && ps.getGroupBy().getGroupByExpressions() != null) {
            ps.getGroupBy().getGroupByExpressions().forEach(expr -> expr.accept(visitor));
        }
        //遍历 ORDER BY 的字段/表达式,处理列名。
        if (ps.getOrderByElements() != null) {
            ps.getOrderByElements().forEach(o -> {
                if (o.getExpression() != null) o.getExpression().accept(visitor);
            });
        }
        //处理 HAVING
        if (ps.getHaving() != null) ps.getHaving().accept(visitor);
        //处理 GROUP BY 的别名
//        fixGroupByAliases(ps);
    }


    // 新增:统一处理 FromItem(先处理 alias,再 accept 递归)
    private void handleFromItemWithVisitor(FromItem fromItem, ColumnExpressionVisitor visitor) {
        if (fromItem == null) return;

        // 若 fromItem 是 Table,那么 Table.visit 会处理 alias(已有逻辑)
        // 对于 SubSelect / ParenthesisFromItem 等,visitor 的 visit 方法中已做 alias 处理(上面添加)
        fromItem.accept(visitor);
    }


    // 清空映射:在开始处理每条 SQL 前调用
    private void clearAliasRenameMap() {
        aliasRenameMap.get().clear();
    }

    // ---------- 生成安全别名(局部方法) ----------
    private String generateSafeAlias(String original) {
        String base = original == null ? "alias" : original.replaceAll("[^A-Za-z0-9_]", "");
        if (base.isEmpty()) base = "alias";
        // 保证以字母或下划线开头
        if (!base.matches("^[A-Za-z_].*")) base = "_" + base;
        String candidate = base + "_a";
        int i = 0;
        // 保证不与已有生成的别名冲突(检查 value)
        while (aliasRenameMap.get().containsValue(candidate)) {
            i++;
            candidate = base + "_a" + i;
        }
        return candidate;
    }

    /**
     * 配合 JSqlParser 的访问者模式,在遍历表达式树(表达式 AST)时,专门处理“列(Column)”相关的节点。
     */
    private class ColumnExpressionVisitor extends ExpressionVisitorAdapter implements FromItemVisitor {
        private final boolean removeBackticks;

        ColumnExpressionVisitor(boolean removeBackticks) {
            this.removeBackticks = removeBackticks;
        }

        /**
         * 配合 JSqlParser 的访问者模式,在遍历表达式树(表达式 AST)时,专门处理“列(Column)”相关的节点。
         *
         * @param column
         */
        @Override
        public void visit(Column column) {
            if (column == null) return;
            String columnName = column.getColumnName();
            if (columnName != null) {
                if ((columnName.startsWith("`") && columnName.endsWith("`"))
                        || (columnName.startsWith("[") && columnName.endsWith("]"))) {
                    columnName = columnName.substring(1, columnName.length() - 1);
                }
                if (removeBackticks) {
                    column.setColumnName(columnName);
                }
            }
            //处理列名前面的表名
            Table tbl = column.getTable();
            if (tbl != null) {
                String tblName = tbl.getName();
                if (tblName != null) {
                    String norm = normalizeIdent(tblName);
                    if (removeBackticks) {
                        tbl.setName(norm);
                    }
                    String mapped = aliasRenameMap.get().get(norm.toUpperCase());
                    if (mapped != null) {
                        tbl.setName(mapped);
                    }
                }
            }
        }


        /**
         * 当访问表达式树(AST)中遇到一个 Function 节点(例如 IFNULL(a,b)、DATE_FORMAT(d,fmt))时,会执行这个方法来处理该函数节点。
         *
         * @param function
         */
        @Override
        public void visit(Function function) {
            if (function == null) return;
            // Debug entry
            logger.debug("visit(Function) called: {}", function.toString());

            /* ------------------ 0) 先处理参数中可能嵌套的 GROUP_CONCAT(...)(优先级高) ------------------ */
            try {
                ExpressionList params = function.getParameters();
                if (params != null && params.getExpressions() != null && !params.getExpressions().isEmpty()) {
                    List<Expression> exprs = params.getExpressions();
                    boolean anyReplaced = false;

                    // 遍历参数列表(拷贝索引用于安全修改)
                    for (int i = 0; i < exprs.size(); i++) {
                        Expression e = exprs.get(i);
                        if (e == null) continue;

                        // 递归先 visit 子表达式,保证列/别名等已被规范化
                        try {
                            e.accept(this);
                        } catch (Throwable ex) {
                            // 容错:子节点处理失败不影响整体流程
                            logger.debug("递归 accept 子表达式失败(忽略): {}", ex.getMessage());
                        }

                        // 大写文本用于简单检测(兼容不同 parser 表示差异)
                        String eTextUp = e.toString() == null ? "" : e.toString().toUpperCase(Locale.ROOT);

                        // 如果参数文本包含 GROUP_CONCAT,进入替换逻辑
                        if (eTextUp.contains("GROUP_CONCAT")) {
                            // 判断是否是复杂用法(DISTINCT / ORDER BY / SEPARATOR)
                            boolean complex = eTextUp.contains("DISTINCT") || eTextUp.contains("ORDER BY") || eTextUp.contains("SEPARATOR");

                            if (!complex) {
                        /* 简单场景:GROUP_CONCAT(arg) -> WM_CONCAT(arg)
                           这一步关键:要把 inner function 的参数正确复制给 WM_CONCAT,
                           若 AST 中没有参数(某些 parser 变体),用字符串解析回退。 */
                                Function wm = new Function();
                                wm.setName("WM_CONCAT");

                                if (e instanceof Function) {
                                    // 常见且首选:内层确实是 Function 且带 parameters,直接 shallow-copy ExpressionList(不共享对象引用会更安全)
                                    Function innerF = (Function) e;
                                    ExpressionList innerParams = innerF.getParameters();
                                    if (innerParams != null && innerParams.getExpressions() != null) {
                                        List<Expression> copied = new ArrayList<>();
                                        for (Expression pe : innerParams.getExpressions()) {
                                            // 深拷贝策略:尝试 parse 表示形式得到独立 AST
                                            try {
                                                Expression parsed = CCJSqlParserUtil.parseExpression(pe.toString());
                                                if (parsed != null) {
                                                    copied.add(parsed);
                                                } else {
                                                    copied.add(pe);
                                                }
                                            } catch (Throwable parseEx) {
                                                // 解析失败时退回原表达式节点(较安全)
                                                copied.add(pe);
                                            }
                                        }
                                        ExpressionList newPl = new ExpressionList();
                                        newPl.setExpressions(copied);
                                        wm.setParameters(newPl);
                                    } else {
                                        // innerF 没有参数(极少见),回退到文本解析
                                        List<Expression> parsed = parseExpressionsFromCommaTopLevel(innerF.toString());
                                        ExpressionList newPl = new ExpressionList();
                                        newPl.setExpressions(parsed);
                                        wm.setParameters(newPl);
                                    }
                                } else {
                                    // fallback:e 不是 Function(奇怪情形),尝试从文本中解析参数
                                    List<Expression> parsed = parseExpressionsFromCommaTopLevel(e.toString());
                                    ExpressionList newPl = new ExpressionList();
                                    newPl.setExpressions(parsed);
                                    wm.setParameters(newPl);
                                }

                                // 把参数位置替换为 WM_CONCAT(...) AST
                                exprs.set(i, wm);
                                anyReplaced = true;
                                logger.debug("Replaced nested GROUP_CONCAT -> WM_CONCAT: {}", wm);
                            } else {
                        /* 复杂场景:包含 DISTINCT/ORDER BY/SEPARATOR 等。我们尝试构造等价的 LISTAGG 写法并保留 NULL 语义:
                           CASE WHEN LISTAGG(...) IS NULL THEN NULL ELSE CONCAT(',', LISTAGG(...), ',') END
                           若构造/解析失败,保留原表达式(避免破坏语义)。 */

                                // 尝试识别聚合表达式和分隔符(尽可能保守)
                                Expression aggExpr = null;
                                String sep = ",";

                                if (e instanceof Function) {
                                    Function innerF = (Function) e;
                                    ExpressionList innerParams = innerF.getParameters();
                                    if (innerParams != null && innerParams.getExpressions() != null && !innerParams.getExpressions().isEmpty()) {
                                        for (Expression cand : innerParams.getExpressions()) {
                                            if (cand == null) continue;
                                            String cs = cand.toString().trim();
                                            if (cs.toUpperCase(Locale.ROOT).startsWith("DISTINCT ")) {
                                                String trimmed = cs.substring(8).trim();
                                                try {
                                                    aggExpr = CCJSqlParserUtil.parseExpression(trimmed);
                                                    break;
                                                } catch (Exception ex) {
                                                    aggExpr = cand;
                                                    break;
                                                }
                                            } else {
                                                aggExpr = cand;
                                                break;
                                            }
                                        }
                                    }
                                    // 如果文本里有 SEPARATOR 'x',尝试提取
                                    if (eTextUp.contains("SEPARATOR")) {
                                        Pattern p = Pattern.compile("SEPARATOR\\s+('([^']*)'|\\\"([^\\\"]*)\\\")",
                                                Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
                                        Matcher m = p.matcher(e.toString());
                                        if (m.find()) {
                                            String g = m.group(2) == null ? m.group(3) : m.group(2);
                                            if (g != null) sep = g;
                                        }
                                    }
                                }

                                if (aggExpr == null) {
                                    // 兜底从文本里尽量解析第一个参数
                                    List<Expression> parsed = parseExpressionsFromCommaTopLevel(e.toString());
                                    if (!parsed.isEmpty()) aggExpr = parsed.get(0);
                                }

                                if (aggExpr == null) aggExpr = new StringValue("''"); // 最终兜底

                                // 构建 LISTAGG-case SQL 文本(字符串),以保证 NULL 传播:
                                // CASE WHEN LISTAGG(agg, 'sep') WITHIN GROUP (ORDER BY agg) IS NULL THEN NULL ELSE CONCAT(',', LISTAGG(...), ',') END
                                String aggText = aggExpr.toString();
                                String listaggSql = "CASE WHEN LISTAGG(" + aggText + ", '" + sep + "') WITHIN GROUP (ORDER BY " + aggText + ") IS NULL THEN NULL ELSE CONCAT(',', LISTAGG(" + aggText + ", '" + sep + "') WITHIN GROUP (ORDER BY " + aggText + "), ',') END";

                                try {
                                    Expression parsed = CCJSqlParserUtil.parseExpression(listaggSql);
                                    exprs.set(i, parsed);
                                    anyReplaced = true;
                                    logger.debug("Replaced complex GROUP_CONCAT -> LISTAGG-case: {}", listaggSql);
                                } catch (Throwable ex) {
                                    // 解析失败:记录并保留原始表达式(不做破坏性改写)
                                    logger.warn("复杂 GROUP_CONCAT 转换为 LISTAGG 失败,保留原表达式: {} , error: {}", e, ex.getMessage());
                                }
                            }
                        }
                    } // end for parameters

                    // 写回参数(若有替换)
                    if (anyReplaced) {
                        function.setParameters(params);
                    }
                }
            } catch (Throwable ex) {
                // 顶层容错:不让解析错误影响整体 SQL 执行
                logger.debug("处理嵌套 GROUP_CONCAT 时发生异常(忽略): {}", ex.getMessage());
            }

            String name = function.getName();
            //转换函数名,将其全部转化为大写
            String nameUp = name == null ? "" : name.toUpperCase();

            // 1) 把常见 MySQL 函数名映射为达梦等价
            if ("IFNULL".equals(nameUp)) {
                //IFNULL(x,y) → NVL(x,y)
                function.setName("NVL");
            } else if ("DATE_FORMAT".equals(nameUp)) {
                //DATE_FORMAT(date, fmt) → TO_CHAR(date, fmt)(格式字符串另作转换)
                function.setName("TO_CHAR");
            } else if ("LEFT".equals(nameUp)) {
                //LEFT(s,n) → SUBSTR(s,1,n)(会把参数结构改为三参数形式)
                function.setName("SUBSTR");
            } else if ("DATE_SUB".equals(nameUp) || "SUBDATE".equals(nameUp)) {
                // 尝试把整个 DATE_SUB(...) 转为 ADD_MONTHS(...)
                Expression converted = tryConvertToAddMonths(function);
                if (converted instanceof Function) {
                    Function addMonths = (Function) converted;
                    function.setName(addMonths.getName());
                    function.setParameters(addMonths.getParameters());
                    // 递归处理参数(确保 dateExpr 被进一步处理,如 CURDATE())
                    if (function.getParameters() != null && function.getParameters().getExpressions() != null) {
                        for (Expression e : new ArrayList<>(function.getParameters().getExpressions())) {
                            if (e != null) e.accept(this);
                        }
                    }
                    return; // 已完成 DATE_SUB 处理
                }
            } else if ("CURDATE".equals(nameUp) || "NOW".equals(nameUp)) {
                // CURDATE() / NOW() → SYSDATE(并移除参数,因为这些无参函数在 DM 中通常用 SYSDATE)
                function.setName("SYSDATE");
                function.setParameters(null);
            }


            /**
             仅当函数带参数时进入。exprs 是参数表达式列表(可能包含 Column、Function、BinaryExpression、StringValue 等各种 Expression)。
             // 2) 处理参数(对 DATE_FORMAT、LEFT、以及可能的 INTERVAL/DATE_SUB 情形)
             */
            ExpressionList parameters = function.getParameters();
            if (parameters != null && parameters.getExpressions() != null) {
                //得到参数的列表
                List<Expression> paramsList = parameters.getExpressions();
                //把 SUBSTR(源自 LEFT)的 2 参数形式改成 3 参数形式
                // LEFT -> SUBSTR: 参数由 (str, n) -> (str, 1, n)
                if ("SUBSTR".equalsIgnoreCase(function.getName())) {
                    if (paramsList.size() == 2) {
                        //获取参数
                        Expression strExpr = paramsList.get(0);
                        Expression nExpr = paramsList.get(1);
                        //构造新参数
                        List<Expression> newExprs = new ArrayList<>();
                        newExprs.add(strExpr);
                        newExprs.add(new LongValue(1));
                        newExprs.add(nExpr);
                        function.getParameters().setExpressions(newExprs);
                        // 如果参数里面还有函数,则递归处理新参数
                        for (Expression e : new ArrayList<>(function.getParameters().getExpressions())) {
                            if (e != null) e.accept(this);
                        }
                        return;
                    }
                }

                //DATE_FORMAT -> TO_CHAR(复杂处理)
                // TO_CHAR (原 DATE_FORMAT):处理格式字符串和可能嵌套的 INTERVAL 模式
                //把 expr 中的 CURDATE() - INTERVAL N MONTH 之类写法(通常是 BinaryExpression)转换成 ADD_MONTHS(expr, -N)。
                //把 MySQL 的格式字符串(例如 '%Y-%m')转换成 DM 的格式 'yyyy-mm'。
                if ("TO_CHAR".equalsIgnoreCase(function.getName())) {
                    // 安全取出参数列表
                    Expression first = paramsList.size() > 0 ? paramsList.get(0) : null;
                    Expression second = paramsList.size() > 1 ? paramsList.get(1) : null;

                    // 尝试把 first 转为 ADD_MONTHS(...)(若匹配 INTERVAL / DATE_SUB / (expr - INTERVAL n MONTH))
                    Expression convertedFirst = tryConvertToAddMonths(first);
                    //如果 convertedFirst 是 Function 且函数名是 ADD_MONTHS,就把参数列表的第 0 个元素替换为这个 ADD_MONTHS 节点
                    if (convertedFirst != null && convertedFirst instanceof Function
                            && "ADD_MONTHS".equalsIgnoreCase(((Function) convertedFirst).getName())) {
                        paramsList.set(0, convertedFirst);
                    } else if (convertedFirst != null && convertedFirst != first) {
                        //如果 helper 返回了不同的表达式(不是 ADD_MONTHS 但确实是被修改过的表达式,例如去掉了一层 Parenthesis、或作了其他变形),也把它写回去。
                        // helper 返回了不同的表达式(例如解开了括号并转换)
                        //确保 TO_CHAR 的第一个参数始终是已转换/规范化后的表达式,便于后续数据库兼容
                        paramsList.set(0, convertedFirst);
                    }

                    // 处理格式字符串:'%Y-%m' -> 'yyyy-mm'(只在格式是 StringValue 时处理)
                    if (second instanceof StringValue) {
                        StringValue sv = (StringValue) second;
                        sv.setValue(toDmDateFormat(sv.getValue()));
                    }
                    // 递归处理参数(包含可能新构造的 ADD_MONTHS 的参数)
                    for (Expression e : new ArrayList<>(paramsList)) {
                        if (e != null) e.accept(this);
                    }

                    // 写回参数(通常无需,但保持一致)
                    function.getParameters().setExpressions(paramsList);
                    return;
                }


                // 对其它函数参数做递归访问(能捕获嵌套的 CURDATE()/INTERVAL)
                for (Expression e : new ArrayList<>(paramsList)) {
                    if (e != null) {
                        try {
                            e.accept(this);
                        } catch (Throwable ex) {
                            logger.debug("参数递归 accept 出错(忽略): {}", ex.getMessage());
                        }
                    }
                }
            }

        }

        //处理和关键字冲突的表名和别名
        @Override
        public void visit(Table table) {
            if (table == null) return;

            // 如果你已有去反引号/方括号的逻辑,可以保留;这里演示保留原有名称的清理
            // 表名/模式名也做规范化(可选,但推荐)
            if (table.getName() != null) {
                table.setName(normalizeIdent(table.getName()));
            }
            if (table.getSchemaName() != null) {
                table.setSchemaName(normalizeIdent(table.getSchemaName()));
            }

            // ---- 新增:若 table.name 实际上是之前记录在 aliasRenameMap 的别名(子查询别名/表别名),则把它替换成映射值 ----
            Map<String, String> map = aliasRenameMap.get();
            if (table.getName() != null) {
                String normalized = normalizeIdent(table.getName());
                String key = normalized == null ? null : normalized.toUpperCase(Locale.ROOT);
                if (key != null) {
                    String mapped = map.get(key);
                    if (mapped != null) {
                        // 把 table 名替换为映射值(安全别名)
                        table.setName(mapped);
                        // 这个 table 本身没有 alias 对象(因为它本来就是一个别名引用,如 user.*),
                        // 所以我们不创建 alias,这样 toString 会输出 user_a.*(期望的形式)
                    }
                }
            }

            Alias alias = table.getAlias();
            if (alias != null) {
                String raw = alias.getName();
                String norm = normalizeIdent(raw);
                alias.setName(norm);           // 写回规范化别名(无包裹、无内空格)
                table.setAlias(alias);

                String key = norm.toUpperCase();
                // 若别名是保留字,则生成安全别名并记录映射
                Map<String, String> map2 = aliasRenameMap.get();
                if (RESERVED_WORDS.contains(key)) {
                    if (map2.containsKey(key)) {
                        table.setAlias(new Alias(map2.get(key), false));
                    } else {
                        String newAlias = generateSafeAlias(norm);
                        table.setAlias(new Alias(newAlias, false));

                        // 注意:记录的是规范化后的大写
                        map2.putIfAbsent(key, newAlias);
                    }
                } else {
                    // 即使不是保留字,也最好把别名清理一遍(去掉反引号/空格)
                    // 非保留字,使用规范化名(并且如果已有映射也不覆盖)
                    if (map2.containsKey(key)) {
                        table.setAlias(new Alias(map2.get(key), false));
                    } else {
                        table.setAlias(new Alias(norm, false));
                    }
                }
            }
            // 如果需要,继续对 table 名本身(非别名)做其他处理
        }


        //————————————————————————————————————————————————处理表达式值为双引号的问题————————————————————————————————————————————————————————————
        // 新增:专门处理比较表达式中的值部分
        @Override
        public void visit(Between expr) {
            // 左右两端分别修复
            if (expr.getBetweenExpressionStart() instanceof Column) {
                Column c = (Column) expr.getBetweenExpressionStart();
                String name = c.getColumnName();
                if (isDoubleQuotedValue(name) && isProbablyLiteralName(name)) {
                    expr.setBetweenExpressionStart(new StringValue(unquoteAndToSqlLiteral(name)));
                }
            }
            if (expr.getBetweenExpressionEnd() instanceof Column) {
                Column c = (Column) expr.getBetweenExpressionEnd();
                String name = c.getColumnName();
                if (isDoubleQuotedValue(name) && isProbablyLiteralName(name)) {
                    expr.setBetweenExpressionEnd(new StringValue(unquoteAndToSqlLiteral(name)));
                }
            }
        }


        @Override
        public void visit(EqualsTo expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(NotEqualsTo expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(LikeExpression expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(GreaterThan expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(GreaterThanEquals expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(MinorThan expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(MinorThanEquals expr) {
            fixValueExpression(expr);
        }

        @Override
        public void visit(InExpression expr) {
            if (expr == null) return;
            // 先访问左侧表达式(例如 user.dept_id),以便对表名/别名做替换
            if (expr.getLeftExpression() != null) {
                expr.getLeftExpression().accept(this);
            }
            // 再处理右侧的值列表
            fixInExpressionValues(expr);
        }

        // 处理比较表达式中的值部分
        private void fixValueExpression(BinaryExpression expr) {
            // 先处理子节点
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);

            // 用在 fixValueExpression 中替换右侧 / 左侧 double-quoted column
            if (expr.getRightExpression() instanceof Column) {
                Column col = (Column) expr.getRightExpression();
                String name = col.getColumnName();
                if (isDoubleQuotedValue(name) && isProbablyLiteralName(name)) {
                    expr.setRightExpression(new StringValue(unquoteAndToSqlLiteral(name))); // 注意:传入带单引号的字面量
                }
            }

            if (expr.getLeftExpression() instanceof Column) {
                Column col = (Column) expr.getLeftExpression();
                String name = col.getColumnName();
                if (isDoubleQuotedValue(name) && isProbablyLiteralName(name)) {
                    expr.setLeftExpression(new StringValue(unquoteAndToSqlLiteral(name)));
                }
            }
        }

        // 处理IN表达式中的值部分
        private void fixInExpressionValues(InExpression expr) {
            if (expr.getRightItemsList() instanceof ExpressionList) {
                ExpressionList list = (ExpressionList) expr.getRightItemsList();
                List<Expression> newExprs = new ArrayList<>();

                for (Expression e : list.getExpressions()) {
                    if (e instanceof Column) {
                        Column col = (Column) e;
                        String name = col.getColumnName();
                        if (isDoubleQuotedValue(name) && isProbablyLiteralName(name)) {
                            newExprs.add(new StringValue(unquoteAndToSqlLiteral(name)));
                        } else {
                            newExprs.add(e);
                        }
                    } else {
                        newExprs.add(e);
                    }
                }

                list.setExpressions(newExprs);
            }
        }

        private boolean isDoubleQuotedValue(String value) {
            return value != null && value.length() > 1 && value.startsWith("\"") && value.endsWith("\"");
        }

        /**
         * 判断一个双引号包裹的名称是否更像“字面量值”而非合法标识符。
         * 若返回 true,表示我们应把它转换为 StringValue(即把 "xxx" -> 'xxx')
         * <p>
         * 启发式规则(可根据项目特点调整):
         * - 包含空格、中文、特殊标点(如:-、:、/、\)或非 ASCII 字符 -> 很可能是字面量
         * - 或以数字开头或包含小括号、逗号等 -> 很可能不是普通标识符
         * - 否则若匹配合法标识符正则 ^[A-Za-z_][A-Za-z0-9_\\$]*$ 则更像标识符 -> 返回 false
         */
        private boolean isProbablyLiteralName(String quoted) {
            if (!isDoubleQuotedValue(quoted)) return false;
            String inner = quoted.substring(1, quoted.length() - 1);

            // 若内含中文或空格等,认为是值:
            if (inner.matches(".*[\\p{IsHan}\\s].*")) return true; // 含中文或空格
            // 若含有明显的非标识符字符:
            if (inner.matches(".*[^A-Za-z0-9_\\$].*")) return true;
            // 若以数字开头也更可能是值
            if (inner.matches("^[0-9].*")) return true;

            // 若能匹配常规标识符规则,则视作标识符(不要替换)
            if (inner.matches("^[A-Za-z_][A-Za-z0-9_\\$]*$")) return false;

            // 兜底当作字面量
            return true;
        }

        // 注意:返回值是带单引号包裹并且已转义的 SQL 字面量字符串,适合直接用在 new StringValue(...)
        private String unquoteAndToSqlLiteral(String value) {
            // value 形如: "张三" 或 """" 内含转义等
            if (value == null || value.length() < 2) return "''";
            String inner = value.substring(1, value.length() - 1); // 去掉首尾双引号

            // 1) 处理双引号内部的 SQL 转义:SQL 中双引号内部要表示一个双引号通常是两个双引号("" -> ")
            //    把成单个双引号(这是对原始双引号字面量的反转义)
            inner = inner.replace("\"\"", "\"");

            // 2) 为了把它变为单引号字符串字面量,要对单引号进行转义(' -> '')
            inner = inner.replace("'", "''");

            // 3) 最终以单引号包裹返回(StringValue 的构造通常期望带引号的字面量)
            return "'" + inner + "'";
        }
        //————————————————————————————————————————————————处理表达式值为双引号的问题————————————————————————————————————————————————————————————
        //处理select的子查询
        @Override
        public void visit(SubSelect subSelect) {
            if (subSelect == null) return;

            // --- 处理子查询的 alias(若存在): 规范化、去引号、保留字替换并写入映射 ---
            Alias alias = subSelect.getAlias();
            if (alias != null) {
                // 先剥离包裹符并写回(与 rewriteAlias 行为一致)
                Alias rewritten = rewriteAlias(alias, removeBackticks);
                subSelect.setAlias(rewritten);

                // 规范化名称并检查保留字
                String norm = normalizeIdent(rewritten.getName());
                if (norm != null) {
                    String key = norm.toUpperCase(Locale.ROOT);
                    Map<String, String> map = aliasRenameMap.get();
                    if (RESERVED_WORDS.contains(key)) {
                        if (map.containsKey(key)) {
                            // 已有生成的安全别名,复用它
                            subSelect.setAlias(new Alias(map.get(key), false));
                        } else {
                            String newAlias = generateSafeAlias(norm);
                            subSelect.setAlias(new Alias(newAlias, false));
                            map.putIfAbsent(key, newAlias);
                        }
                    } else {
                        // 非保留字,使用规范化名(并且如果已有映射也不覆盖)
                        if (map.containsKey(key)) {
                            subSelect.setAlias(new Alias(map.get(key), false));
                        } else {
                            subSelect.setAlias(new Alias(norm, false));
                        }
                    }
                }
            }

            // 递归处理子查询体
            if (subSelect.getSelectBody() != null) {
                processSelectBody(subSelect.getSelectBody(), removeBackticks);
            }
        }


        @Override
        public void visit(SubJoin subJoin) {
            if (subJoin == null) return;

            if (subJoin.getLeft() != null) {
                subJoin.getLeft().accept(this); // 递归处理左侧 from item
            }

            if (subJoin.getJoinList() != null) {
                for (Join j : subJoin.getJoinList()) {
                    if (j.getRightItem() != null) {
                        j.getRightItem().accept(this);
                    }
                }
            }
        }

        @Override
        public void visit(LateralSubSelect lateralSubSelect) {
            if (lateralSubSelect == null) return;
            SubSelect ss = lateralSubSelect.getSubSelect();
            if (ss != null) {
                // 如果 lateral 本身有 alias(部分实现会把 alias 放在 SubSelect),也会在 SubSelect 的 visit 中被处理
                ss.accept((FromItemVisitor) this);
            }
        }

        @Override
        public void visit(ValuesList valuesList) {
            // 如果你不需要特殊处理 VALUES 列表,可空实现
        }

        @Override
        public void visit(TableFunction tableFunction) {
            if (tableFunction == null) return;
            // 若 TableFunction 内有 Function,可按需处理其函数参数
            if (tableFunction.getFunction() != null) {
                tableFunction.getFunction().accept(this);
            }
        }

        @Override
        public void visit(ParenthesisFromItem parenthesisFromItem) {
            if (parenthesisFromItem == null) return;

            // 规范化并处理 alias(若存在)
            Alias alias = parenthesisFromItem.getAlias();
            if (alias != null) {
                Alias rewritten = rewriteAlias(alias, removeBackticks);
                parenthesisFromItem.setAlias(rewritten);
                String norm = normalizeIdent(rewritten.getName());
                if (norm != null && RESERVED_WORDS.contains(norm.toUpperCase(Locale.ROOT))) {
                    String newAlias = generateSafeAlias(norm);
                    parenthesisFromItem.setAlias(new Alias(newAlias, false));
                    aliasRenameMap.get().putIfAbsent(norm.toUpperCase(Locale.ROOT), newAlias);
                } else if (norm != null) {
                    parenthesisFromItem.setAlias(new Alias(norm, false));
                }
            }

            if (parenthesisFromItem.getFromItem() != null) {
                parenthesisFromItem.getFromItem().accept(this);
            }
        }


    }


    /**
     * 从 INTERVAL ... MONTH 这类表达式中提取数字 N(多少月)。
     *
     * @param intervalExpr
     * @return
     */
    private int extractIntervalMonths(Expression intervalExpr) {
        if (intervalExpr == null) return 0;
        try {
            // 优先针对常见节点类型
            // 数字节点
            if (intervalExpr instanceof LongValue) {
                return (int) ((LongValue) intervalExpr).getValue();
            }
            // 字符串节点
            if (intervalExpr instanceof StringValue) {
                String s = ((StringValue) intervalExpr).getValue();
                return Integer.parseInt(s.trim());
            }
            // 回退:从文本中抽取第一个数字(兼容性保底)
            String s = intervalExpr.toString();
            Matcher m = Pattern.compile("(\\d+)").matcher(s);
            if (m.find()) {
                return Integer.parseInt(m.group(1));
            }
        } catch (Throwable ignored) {
        }
        return 0;
    }


    //解决转换成DM时,转换成add_months()

    /**
     * 把 MySQL 的 DATE_SUB(..., INTERVAL n MONTH) 或者 date - INTERVAL n MONTH 表达式,转换成达梦数据库的 ADD_MONTHS(date, -n)。
     * 在 AST 遍历过程中,当遇到 DATE_SUB 或者类似 date - INTERVAL 表达式时,调用此方法尝试转换。
     *
     * @param expr
     * @return
     */
    private Expression tryConvertToAddMonths(Expression expr) {
        if (expr == null) return null;

        // 1) Parenthesis: 解开括号再处理
        /**
         * 如果表达式是括号包裹的(例如 (DATE_SUB(...))),则直接取里面的表达式 p.getExpression() 再递归调用本方法。
         * 忽略无意义的括号,确保能处理到真实的表达式。
         */
        if (expr instanceof Parenthesis) {
            Parenthesis p = (Parenthesis) expr;
            return tryConvertToAddMonths(p.getExpression());
        }

        // 2) BinaryExpression: left - INTERVAL N MONTH
        /**
         * 检查是否是二元表达式(BinaryExpression),即形如 date - INTERVAL 2 MONTH 这种 SQL 结构。
         * 取出左右子表达式:left 通常是日期字段/函数,right 通常是 INTERVAL n MONTH。
         */
        if (expr instanceof BinaryExpression) {
            BinaryExpression be = (BinaryExpression) expr;
            //left 通常是日期字段/函数
            Expression left = be.getLeftExpression();
            //right 通常是 INTERVAL n MONTH
            Expression right = be.getRightExpression();
            //确保右边表达式不为空。
            if (right != null) {
                //将右边表达式转成字符串并大写,方便做关键字匹配(比如 "INTERVAL 2 MONTH")
                String rightText = right.toString().toUpperCase();
                //判断右边是不是包含 INTERVAL 和 MONTH,即是不是 INTERVAL n MONTH 结构
                if (rightText.contains("INTERVAL") && rightText.contains("MONTH")) {
                    //调用辅助方法 extractIntervalMonths(right),从右侧表达式中解析出月份数(整数值 n)。
                    //例如 INTERVAL 2 MONTH → 2。
                    int months = extractIntervalMonths(right);
                    //新建一个 Function 对象,表示目标数据库的 ADD_MONTHS(...) 函数
                    Function addMonths = new Function();
                    addMonths.setName("ADD_MONTHS");
                    //创建一个参数列表容器 ExpressionList,里面维护一个 List<Expression>,用于存放函数参数。
                    ExpressionList el = new ExpressionList();
                    List<Expression> params = new ArrayList<>();
                    //把 left(即原来的日期部分)作为第一个参数。
                    params.add(left);
                    //把负的月份数作为第二个参数(DATE_SUB 的语义是“往前减去 n 个月”,而 ADD_MONTHS 正常是“往后加 n 个月”,所以要用负号来抵消差异)
                    params.add(new LongValue(-months)); // 关键:负整数
                    //把参数放入 ExpressionList,再设置到函数对象上
                    el.setExpressions(params);
                    addMonths.setParameters(el);
                    return addMonths;
                }
            }
        }

        // 3) Function: DATE_SUB(date, INTERVAL n MONTH) 或 SUBDATE(...)
        //如果表达式本身是一个函数调用(Function),就取函数名。
        if (expr instanceof Function) {
            Function f = (Function) expr;
            //转大写
            String fname = f.getName() == null ? "" : f.getName().toUpperCase();
            //判断函数名是否是 DATE_SUB 或 SUBDATE
            if ("DATE_SUB".equals(fname) || "SUBDATE".equals(fname)) {
                //确认函数有参数列表,并且参数数目至少有两个(第一个是日期,第二个是 INTERVAL n MONTH)
                if (f.getParameters() != null && f.getParameters().getExpressions() != null) {
                    //取出参数列表
                    List<Expression> p = f.getParameters().getExpressions();
                    if (p.size() >= 2) {
                        //取第一个参数(日期部分)
                        Expression dateExpr = p.get(0);
                        //第二个参数(间隔部分)
                        Expression intervalExpr = p.get(1);
                        //调用辅助方法解析间隔里的月份数
                        int months = extractIntervalMonths(intervalExpr);
                        //新建一个 Function 函数对象,表示目标数据库的 ADD_MONTHS(...) 函数
                        Function addMonths = new Function();
                        addMonths.setName("ADD_MONTHS");
                        //创建一个参数列表容器 ExpressionList,里面维护一个 List<Expression>,用于存放函数参数。
                        ExpressionList el = new ExpressionList();
                        List<Expression> params = new ArrayList<>();
                        //把日期部分作为第一个参数
                        params.add(dateExpr);
                        //负的月份数作为第二个参数
                        params.add(new LongValue(-months));
                        el.setExpressions(params);
                        addMonths.setParameters(el);
                        return addMonths;
                    }
                }
            }
        }

        // 4) 无法识别,返回原表达式(上层继续递归)
        return expr;
    }


    private String toDmDateFormat(String mysqlFmt) {
        if (mysqlFmt == null) return null;
        String f = mysqlFmt.trim();
        if ((f.startsWith("'") && f.endsWith("'")) || (f.startsWith("\"") && f.endsWith("\""))) {
            f = f.substring(1, f.length() - 1);
        }
        // 常见替换:%Y -> yyyy, %m -> mm, %d -> dd
        f = f.replaceAll("%Y", "yyyy")
                .replaceAll("%y", "yy")
                .replaceAll("%m", "mm")
                .replaceAll("%d", "dd");
        return f;
    }


    private Alias rewriteAlias(Alias alias, boolean removeBackticks) {
        if (alias == null) return null;
        String aName = alias.getName();
        if (aName == null) return alias;

        // 去掉左右两端的空白
        aName = aName.trim();

        // 循环剥离最外层的包裹符,直到不再匹配。
        // 支持的包裹符对包括:`...`, "..." , '...' , [...]
        while (aName.length() >= 2) {
            char first = aName.charAt(0);
            char last = aName.charAt(aName.length() - 1);
            boolean matched = false;

            if (first == '`' && last == '`') {
                aName = aName.substring(1, aName.length() - 1);
                matched = true;
            } else if (first == '"' && last == '"') {
                aName = aName.substring(1, aName.length() - 1);
                matched = true;
            } else if (first == '\'' && last == '\'') {
                aName = aName.substring(1, aName.length() - 1);
                matched = true;
            } else if (first == '[' && last == ']') {
                aName = aName.substring(1, aName.length() - 1);
                matched = true;
            }

            if (!matched) break;
            aName = aName.trim(); // 去掉剥离后可能残留的空白
        }

        if (removeBackticks) {
            // 将剥离后的 alias 写回 Alias 对象
            alias.setName(aName);
        }
        return alias;
    }

    //统一的“标识符规范化”工具
    private static String normalizeIdent(String s) {
        if (s == null) return null;
        String x = s.trim();
        while (x.length() >= 2) {
            char f = x.charAt(0), l = x.charAt(x.length() - 1);
            boolean wrapped =
                    (f == '`' && l == '`') ||
                            (f == '"' && l == '"') ||
                            (f == '[' && l == ']') ||
                            (f == '\'' && l == '\'');
            if (!wrapped) break;
            x = x.substring(1, x.length() - 1).trim();
        }
        return x;
    }
    /* ------------------ 辅助方法:从文本中解析函数括号内部的顶层逗号分割表达式列表 ------------------ */

    /**
     * 输入类似 "GROUP_CONCAT(col1, col2)" 或 "GROUP_CONCAT( some_complex(expr), '|' )",
     * 返回解析后的 Expression 列表(尽量解析为 AST;解析失败时以 StringValue 退回)。
     * <p>
     * 设计要点:
     * - 使用正则非贪婪抓取括号内部文本(DOTALL 支持跨行)
     * - 然后按“顶层逗号”分割(跳过嵌套括号/字符串内部的逗号)
     * - 最后对每个片段尝试 CCJSqlParserUtil.parseExpression(...)
     */
    private List<Expression> parseExpressionsFromCommaTopLevel(String funcText) {
        List<Expression> out = new ArrayList<>();
        if (funcText == null) return out;

        // 1) 提取第一个匹配的括号内容:GROUP_CONCAT\s*\((.*?)\)
        Pattern p = Pattern.compile("GROUP_CONCAT\\s*\\((.*?)\\)",
                Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
        Matcher m = p.matcher(funcText);
        String inside;
        if (m.find()) {
            inside = m.group(1);
        } else {
            // 若不是 GROUP_CONCAT 字样(回退取整个 funcText 的括号内)
            Pattern p2 = Pattern.compile("\\((.*?)\\)",
                    Pattern.DOTALL);
            Matcher m2 = p2.matcher(funcText);
            inside = m2.find() ? m2.group(1) : null;
        }
        if (inside == null) return out;

        // 2) 按顶层逗号分割成片段(跳过括号/单双引号内的逗号)
        List<String> parts = splitTopLevelComma(inside);

        // 3) 对每个片段尝试解析为 Expression(优先),失败时退回 StringValue(带原样)
        for (String part : parts) {
            String tok = part == null ? "" : part.trim();
            if (tok.isEmpty()) continue;
            try {
                Expression parsed = CCJSqlParserUtil.parseExpression(tok);
                out.add(parsed);
            } catch (Throwable ex) {
                // 解析失败则把原文本作为 StringValue(注意需要以 SQL 字面量形式决定是否带引号)
                out.add(new StringValue(tok));
            }
        }
        return out;
    }

    /**
     * 按“顶层逗号”把字符串分割成片段,跳过在括号或引号内部的逗号。
     * 这是一个小型状态机:跟踪 parentheses depth、单双引号状态。
     */
    private List<String> splitTopLevelComma(String s) {
        List<String> parts = new ArrayList<>();
        if (s == null) return parts;
        StringBuilder cur = new StringBuilder();
        int depth = 0;
        boolean inSingle = false;
        boolean inDouble = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 处理引号状态(注意 SQL 双引号/单引号可能用法不同,但这里做一个通用处理)
            if (c == '\'' && !inDouble) {
                inSingle = !inSingle;
                cur.append(c);
                continue;
            } else if (c == '\"' && !inSingle) {
                inDouble = !inDouble;
                cur.append(c);
                continue;
            }
            if (!inSingle && !inDouble) {
                if (c == '(') {
                    depth++;
                } else if (c == ')') {
                    if (depth > 0) depth--;
                } else if (c == ',' && depth == 0) {
                    // 顶层逗号:切分
                    parts.add(cur.toString());
                    cur.setLength(0);
                    continue;
                }
            }
            cur.append(c);
        }
        // 剩余部分
        if (cur.length() > 0) parts.add(cur.toString());
        return parts;
    }


    /* --------------------- 工具方法 --------------------- */

    /**
     * 清空缓存(测试或运维用)
     */
    public void clearCache() {
        sqlCache.clear();
    }

}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zdsc.dm.autoconfig.DmSqlRewriteAutoConfiguration

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐