{"record":{"id":"cae9817e180bcfa9","repo":"jd-opensource/joyagent-jdgenie","slug":"sql-cae981","errorCode":null,"errorMessage":"解析sql失败","messagePattern":"解析sql失败","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"genie-backend/src/main/java/com/jd/genie/data/sql/SqlParserUtils.java","lineNumber":424,"sourceCode":"            if (selectNode.getFetch() != null) {\n                sqlModel.setFetch(selectNode.getFetch().toString());\n            }\n        }\n        if (SqlKind.ORDER_BY.equals(sqlNode.getKind())) {\n            SqlOrderBy orderBy = (SqlOrderBy) sqlNode;\n            selectNode = (SqlSelect) orderBy.query;\n            List<DataOrderBy> orderByList = parseSelectOrderBy(orderBy.orderList, dialect);\n            sqlModel.setOrderByList(orderByList);\n            if (orderBy.fetch != null) {\n                sqlModel.setFetch(orderBy.fetch.toString());\n            }\n        }\n        if (SqlKind.UNION.equals(sqlNode.getKind())) {\n            SqlBasicCall unionNode = (SqlBasicCall) sqlNode;\n            selectNode = (SqlSelect) unionNode.getOperandList().get(unionNode.operandCount() - 1);\n        }\n        if (selectNode == null) {\n            throw new RuntimeException(\"解析sql失败\");\n        }\n        if (selectNode.getHaving() != null) {\n            sqlModel.setHaving(selectNode.getHaving().toString());\n        }\n\n        List<ModelColumn> modelColumns = parseSelectColumn(selectNode, dialect);\n\n        resetOrderByColumnKind(sqlModel.getOrderByList(), modelColumns);\n\n        sqlModel.setColumnList(modelColumns);\n        if (selectNode.getFrom() != null) {\n            FromTable fromTable = parseSelectFromTable(selectNode.getFrom());\n            sqlModel.setFromTable(fromTable);\n        }\n        if (selectNode.getWhere() != null) {\n            WhereCondition whereCondition = parseSelectWhere(selectNode.getWhere(), dialect);\n            List<WhereCondition> whereConditionList = flattenConditions(whereCondition, \"AND\".equalsIgnoreCase(whereCondition.getOperator()));\n            sqlModel.setWhereConditionList(whereConditionList);","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/jd-opensource/joyagent-jdgenie/blob/2417e0b8b636d941ad5fb14c59b20dddfef5375d/genie-backend/src/main/java/com/jd/genie/data/sql/SqlParserUtils.java#L406-L442","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif (selectNode == null) {\n    throw new RuntimeException(\"解析sql失败\");\n}\n// after\nif (selectNode == null) {\n    if (SqlKind.WITH.equals(sqlNode.getKind())) {\n        SqlWith withNode = (SqlWith) sqlNode;\n        selectNode = (SqlSelect) withNode.body;\n    } else {\n        throw new RuntimeException(\"解析sql失败, kind=\" + sqlNode.getKind());\n    }\n}","handlingStrategy":"validation","validationCode":"String upper = sql.trim().toUpperCase();\nif (upper.startsWith(\"WITH\") || upper.startsWith(\"EXPLAIN\") || upper.startsWith(\"VALUES\")) {\n    throw new IllegalArgumentException(\"Unsupported query shape for parser: \" + upper.split(\"\\\\s\")[0]);\n}","typeGuard":"boolean isSupportedQueryShape(String sql) {\n    if (sql == null) return false;\n    String s = sql.trim().toUpperCase();\n    return s.startsWith(\"SELECT\") || s.startsWith(\"ORDER BY\") || s.contains(\"UNION\");\n}","tryCatchPattern":"try {\n    SqlModel model = SqlParserUtils.parseSelectSql(sql, dialect);\n} catch (RuntimeException e) {\n    if (\"解析sql失败\".equals(e.getMessage())) {\n        log.warn(\"Unhandled AST kind for sql: {}\", sql);\n        // fallback: treat as opaque query without column extraction\n    }\n}","preventionTips":["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"],"tags":["sql","apache-calcite","parsing"],"backgroundTag":"sql-query-failed","analyzedSha":"2417e0b8b636d941ad5fb14c59b20dddfef5375d","analyzedAt":"2026-09-08T11:28:19.414Z","contentChangedAt":"2026-09-08T11:28:19.414Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}