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

  1. Log sqlNode.getKind() to see which AST kind was actually produced
  2. Normalize the SQL before parsing: rewrite WITH ... SELECT into a plain SELECT or unwrap CTEs
  3. Add a handler branch for the missing SqlKind (e.g. WITH / EXPLAIN) that extracts its inner SqlSelect
  4. 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

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)