寻求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
相关产品推荐
相关产品推荐

