{"record":{"id":"10c45ea872ac1721","repo":"jd-opensource/joyagent-jdgenie","slug":"sql","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":396,"sourceCode":"    private static String hintParse(SqlModel sqlModel, String sql) {\n        String regex = \"(/\\\\*\\\\+)(.*?)(\\\\*/)\";\n        Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);\n        Matcher matcher = pattern.matcher(sql);\n        if (matcher.find()) {\n            sqlModel.setHint(matcher.group(2));\n            return matcher.replaceAll(\"$1\" + SqlModel.HINT_TAG + \"$3\");\n        }\n        return sql;\n    }\n\n    public static SqlModel parseSelectSql(String sql, String dialect) throws SqlParseException {\n        log.debug(\"待解析sql:{}\", sql);\n        sql = cleanSql(sql, dialect);\n        SqlModel sqlModel = new SqlModel();\n        sql = hintParse(sqlModel, sql);\n\n        if (!isSelectSql(sql, dialect)) {\n            throw new RuntimeException(\"请检查sql是否正确\");\n        }\n\n\n        sqlModel.setDialect(dialect);\n        SqlNode sqlNode = SqlParser.create(sql, parserConfigWithoutQuoted(dialect)).parseQuery();\n        SqlSelect selectNode = null;\n        sqlModel.setSelectType(sqlNode.getKind().name());\n        if (SqlKind.SELECT.equals(sqlNode.getKind())) {\n            selectNode = (SqlSelect) sqlNode;\n            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);","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/jd-opensource/joyagent-jdgenie/blob/2417e0b8b636d941ad5fb14c59b20dddfef5375d/genie-backend/src/main/java/com/jd/genie/data/sql/SqlParserUtils.java#L378-L414","documentation":"parseSelectSql pre-validates the input with isSelectSql(sql, dialect) after cleaning the SQL and extracting hints; if the statement is not recognized as a SELECT for the given dialect, it throws this RuntimeException, meaning the SQL text is not a query the parser can process.","triggerScenarios":"Calling SqlParserUtils.parseSelectSql with a non-SELECT statement (INSERT/UPDATE/DELETE/DDL/DROP), empty or null SQL after cleanSql strips it, a dialect mismatch that makes isSelectSql's keyword check fail, or a leading comment/CTE/EXPLAIN prefix the isSelectSql check does not recognize.","commonSituations":"Users paste a write statement into a natural-language-to-SQL chat agent; LLM-generated SQL wrapped in markdown fences or prose that cleanSql does not fully strip; CTE (WITH ...) queries that the isSelectSql heuristic does not treat as selects.","solutions":["Print/inspect the cleaned SQL (log at debug shows 待解析sql) to see what actually failed the isSelectSql check","Confirm the statement is a SELECT (or WITH/CTE select) and the dialect parameter matches the SQL flavor","Strip markdown fences, trailing semicolons, and non-SQL text before calling parseSelectSql","If CTE/EXPLAIN statements must be supported, extend the pre-check instead of relying on isSelectSql"],"exampleFix":"// before\nparseSelectSql(userSql, dialect); // throws 请检查sql是否正确\n// after\nString cleaned = userSql.replaceAll(\"```(sql)?|;\\\\s*$\", \"\").trim();\nif (!cleaned.toUpperCase().startsWith(\"SELECT\") && !cleaned.toUpperCase().startsWith(\"WITH\")) {\n    throw new IllegalArgumentException(\"Only SELECT queries are supported\");\n}\nparseSelectSql(cleaned, dialect);","handlingStrategy":"validation","validationCode":"String cleaned = sql == null ? \"\" : sql.trim().replaceFirst(\"^```(sql)?\", \"\").replaceFirst(\"```$\", \"\").trim();\nif (cleaned.isEmpty() || !(cleaned.toUpperCase().startsWith(\"SELECT\") || cleaned.toUpperCase().startsWith(\"WITH\"))) {\n    throw new IllegalArgumentException(\"Only SELECT statements are supported, got: \" + cleaned);\n}","typeGuard":"boolean isReadOnlySelect(String sql) {\n    if (sql == null) return false;\n    String s = sql.trim().toUpperCase();\n    return s.startsWith(\"SELECT\") || s.startsWith(\"WITH\");\n}","tryCatchPattern":"try {\n    SqlModel model = SqlParserUtils.parseSelectSql(sql, dialect);\n} catch (RuntimeException e) {\n    if (\"请检查sql是否正确\".equals(e.getMessage())) {\n        // surface a user-facing 'please check your SQL' error\n    }\n}","preventionTips":["Strip markdown fences and trailing semicolons from LLM-generated SQL before parsing","Validate the statement starts with SELECT/WITH before calling parseSelectSql","Pass the dialect matching the actual SQL flavor","Reject non-SELECT statements at the API boundary"],"tags":["sql","validation","input-validation"],"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"}