You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求MS SQL查询的有效校验方案(附问题代码)

MS SQL 查询校验问题与解决方案

问题场景

我尝试用以下代码调用sp_describe_first_result_set存储过程,校验MS SQL查询的有效性,结果不管查询对错,都返回“有效”响应。原因是无效查询执行时,错误会和成功标识一同返回,导致onErrorResume根本捕获不到异常:

public Mono<String> queryValidation(String connectionId, String query) {
    return connectionConfigRepository.findByConnectionId(connectionId)
        .switchIfEmpty(Mono.error(new RuntimeException("Connection not found: " + connectionId)))
        .flatMap(config -> {
            DatabaseConnectionDetails details = config.getDatabaseConnectionDetails();

            return createConnectionFactory(details)
                .flatMap(factory -> Mono.from(factory.create())
                    .flatMap(conn -> {
                        String escaped = query.replace("'", "''");
                        String validationQuery = "EXEC sp_describe_first_result_set N'" + escaped + "', NULL, 1;";

                        return Flux.from(conn.createStatement(validationQuery).execute())
                            .flatMap(result -> Flux.from(result.map((row, meta) -> "ok"))) // force row parsing
                            .then(Mono.just("Valid Query"))
                            .onErrorResume(e -> Mono.just("Invalid Query: " + e.getMessage()))
                            .doFinally(signal -> conn.close());
                    })
                );
        });
}

我需要高效的MS SQL查询校验方案,手动校验太耗资源,之前试过JSQLParser没成功,求可行的解决思路。

核心问题分析

sp_describe_first_result_set遇到无效查询时,不会抛出JDBC层面的异常,而是返回一个包含错误信息的结果集,所以execute()会正常返回信号,后续的then(Mono.just("Valid Query"))总会执行,错误处理逻辑根本触发不了。

可行解决方案

方案1:解析sp_describe_first_result_set的结果集

既然存储过程会返回错误结果集,那就直接解析结果集里的错误列。MS SQL的这个存储过程出错时,结果集会包含error_message字段,我们可以通过检查这个字段判断查询是否有效:

public Mono<String> queryValidation(String connectionId, String query) {
    return connectionConfigRepository.findByConnectionId(connectionId)
        .switchIfEmpty(Mono.error(new RuntimeException("Connection not found: " + connectionId)))
        .flatMap(config -> {
            DatabaseConnectionDetails details = config.getDatabaseConnectionDetails();

            return createConnectionFactory(details)
                .flatMap(factory -> Mono.from(factory.create())
                    .flatMap(conn -> {
                        String escaped = query.replace("'", "''");
                        String validationQuery = "EXEC sp_describe_first_result_set N'" + escaped + "', NULL, 1;";

                        return Flux.from(conn.createStatement(validationQuery).execute())
                            .flatMap(result -> {
                                // 检查结果集是否包含错误列
                                boolean hasErrorColumn = Arrays.stream(result.getMetadata().getColumnNames())
                                    .anyMatch(col -> col.equalsIgnoreCase("error_message"));
                                
                                if (hasErrorColumn) {
                                    // 读取错误信息返回无效标识
                                    return Flux.from(result.map((row, meta) -> row.getString("error_message")))
                                        .next()
                                        .flatMap(errorMsg -> Mono.just("Invalid Query: " + errorMsg));
                                } else {
                                    // 无错误,读取一行确保结果集解析完成
                                    return Flux.from(result.map((row, meta) -> "ok"))
                                        .next()
                                        .then(Mono.just("Valid Query"));
                                }
                            })
                            .onErrorResume(e -> Mono.just("Invalid Query: " + e.getMessage()))
                            .doFinally(signal -> conn.close());
                    })
                );
        });
}

方案2:用SET NOEXEC ON编译查询(推荐)

更直接的方式是开启SET NOEXEC ON,让SQL Server只编译查询不执行,编译失败会直接抛出JDBC异常,刚好能被onErrorResume捕获:

public Mono<String> queryValidation(String connectionId, String query) {
    return connectionConfigRepository.findByConnectionId(connectionId)
        .switchIfEmpty(Mono.error(new RuntimeException("Connection not found: " + connectionId)))
        .flatMap(config -> {
            DatabaseConnectionDetails details = config.getDatabaseConnectionDetails();

            return createConnectionFactory(details)
                .flatMap(factory -> Mono.from(factory.create())
                    .flatMap(conn -> {
                        // 开启NOEXEC,编译查询后关闭
                        String validationQuery = "SET NOEXEC ON;\n" + query + "\nSET NOEXEC OFF;";

                        return Flux.from(conn.createStatement(validationQuery).execute())
                            .then(Mono.just("Valid Query"))
                            .onErrorResume(e -> Mono.just("Invalid Query: " + e.getMessage()))
                            .doFinally(signal -> conn.close());
                    })
                );
        });
}

这个方案能覆盖语法错误、对象不存在等所有编译阶段的问题,逻辑简单且校验准确。

方案3:修复JSQLParser的离线校验

如果想不依赖数据库连接做语法校验,可以重新调试JSQLParser的使用,注意适配MS SQL的语法特性:

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.util.validation.Validation;
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
import net.sf.jsqlparser.util.validation.metadata.NamedObjectLookup;

public boolean validateQueryWithJSQLParser(String query) {
    try {
        // 解析查询语句
        var statement = CCJSqlParserUtil.parse(query);
        // 指定针对MS SQL做校验
        var validation = Validation.forDbms(Validation.DBMS.SQLSERVER);
        // 如果需要校验表/列是否存在,这里可以对接数据库元数据实现lookup逻辑
        var lookup = new NamedObjectLookup() {
            @Override
            public boolean lookup(NamedObject namedObject) {
                // 示例:默认认为对象存在,实际可替换为数据库元数据查询逻辑
                return true;
            }
        };
        validation.setNamedObjectLookup(lookup);
        // 获取校验错误列表
        var errors = validation.validate(statement);
        return errors.isEmpty();
    } catch (JSQLParserException e) {
        // 解析失败直接返回无效
        return false;
    }
}

JSQLParser适合快速语法校验,但无法校验数据库中实际存在的对象,若需要完整校验,还是得结合数据库连接的方案。

内容的提问来源于stack exchange,提问作者apramit roy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 10:17:04