jd-opensource/joyagent-jdgenie · error · RuntimeException
解析sql失败
Error message
解析sql失败
What it means
After the initial parse, parseSelectSql only extracts a SqlSelect node for three top-level kinds: SELECT, ORDER_BY, and UNION. If the parsed AST is any other kind (e.g. EXPLAIN, WITH/CTE, VALUES, or a UNION whose last operand is not a SqlSelect), selectNode stays null and this RuntimeException is thrown.
Solutions
- Log sqlNode.getKind() to see which AST kind was actually produced
- Normalize the SQL before parsing: rewrite WITH ... SELECT into a plain SELECT or unwrap CTEs
- Add a handler branch for the missing SqlKind (e.g. WITH / EXPLAIN) that extracts its inner SqlSelect
- Ensure the dialect passed to SqlParser.create matches the SQL so the expected AST shape is produced
Example fix
// before
if (selectNode == null) {
throw new RuntimeException("解析sql失败");
}
// after
if (selectNode == null) {
if (SqlKind.WITH.equals(sqlNode.getKind())) {
SqlWith withNode = (SqlWith) sqlNode;
selectNode = (SqlSelect) withNode.body;
} else {
throw new RuntimeException("解析sql失败, kind=" + sqlNode.getKind());
}
} Defensive patterns
Strategy: validation
Validate before calling
String upper = sql.trim().toUpperCase();
if (upper.startsWith("WITH") || upper.startsWith("EXPLAIN") || upper.startsWith("VALUES")) {
throw new IllegalArgumentException("Unsupported query shape for parser: " + upper.split("\\s")[0]);
} Type guard
boolean isSupportedQueryShape(String sql) {
if (sql == null) return false;
String s = sql.trim().toUpperCase();
return s.startsWith("SELECT") || s.startsWith("ORDER BY") || s.contains("UNION");
} Try / catch
try {
SqlModel model = SqlParserUtils.parseSelectSql(sql, dialect);
} catch (RuntimeException e) {
if ("解析sql失败".equals(e.getMessage())) {
log.warn("Unhandled AST kind for sql: {}", sql);
// fallback: treat as opaque query without column extraction
}
} Prevention
- Avoid feeding CTE/WITH queries until the parser supports that SqlKind
- Check for EXPLAIN/VALUES prefixes before parsing
- Keep the dialect consistent between validation and SqlParser.create
- Log sqlNode.getKind() when this error occurs to identify unhandled kinds
When it happens
Trigger: Calling parseSelectSql with a statement whose Calcite AST kind is not SELECT, ORDER_BY, or UNION — e.g. a WITH ... SELECT CTE parsed as a different kind, EXPLAIN statements, VALUES clauses, or a UNION whose last operand fails to cast to SqlSelect.
Common situations: LLM generates a CTE query (WITH x AS (...) SELECT ...) and Calcite represents it with an unhandled kind; EXPLAIN-prefixed queries; dialect differences causing the parser to wrap the query differently than expected.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
AI-assisted analysis of jd-opensource/joyagent-jdgenie@2417e0b8b6 (2026-09-08).
Data as JSON: /api/errors/cae9817e180bcfa9.
Report an issue: GitHub.
Appendix: source
Thrown at genie-backend/src/main/java/com/jd/genie/data/sql/SqlParserUtils.java:424
if (selectNode.getFetch() != null) {
sqlModel.setFetch(selectNode.getFetch().toString());
}
}
if (SqlKind.ORDER_BY.equals(sqlNode.getKind())) {
SqlOrderBy orderBy = (SqlOrderBy) sqlNode;
selectNode = (SqlSelect) orderBy.query;
List<DataOrderBy> orderByList = parseSelectOrderBy(orderBy.orderList, dialect);
sqlModel.setOrderByList(orderByList);
if (orderBy.fetch != null) {
sqlModel.setFetch(orderBy.fetch.toString());
}
}
if (SqlKind.UNION.equals(sqlNode.getKind())) {
SqlBasicCall unionNode = (SqlBasicCall) sqlNode;
selectNode = (SqlSelect) unionNode.getOperandList().get(unionNode.operandCount() - 1);
}
if (selectNode == null) {
throw new RuntimeException("解析sql失败");
}
if (selectNode.getHaving() != null) {
sqlModel.setHaving(selectNode.getHaving().toString());
}
List<ModelColumn> modelColumns = parseSelectColumn(selectNode, dialect);
resetOrderByColumnKind(sqlModel.getOrderByList(), modelColumns);
sqlModel.setColumnList(modelColumns);
if (selectNode.getFrom() != null) {
FromTable fromTable = parseSelectFromTable(selectNode.getFrom());
sqlModel.setFromTable(fromTable);
}
if (selectNode.getWhere() != null) {
WhereCondition whereCondition = parseSelectWhere(selectNode.getWhere(), dialect);
List<WhereCondition> whereConditionList = flattenConditions(whereCondition, "AND".equalsIgnoreCase(whereCondition.getOperator()));
sqlModel.setWhereConditionList(whereConditionList);View on GitHub (pinned to 2417e0b8b6)