{"record":{"id":"93e7f5c0e6e64f46","repo":"Tencent/APIJSON","slug":"abstractsqlexecutor-executeappjoin-stringutil-ise","errorCode":null,"errorMessage":"AbstractSQLExecutor.executeAppJoin  StringUtil.isEmpty(sql, true) >> return null;","messagePattern":"AbstractSQLExecutor\\.executeAppJoin  StringUtil\\.isEmpty\\(sql, true\\) >> return null;","errorType":"exception","errorClass":"NullPointerException","httpStatus":500,"severity":"critical","filePath":"APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java","lineNumber":836,"sourceCode":"\t\t\t\t//          else {\r\n\t\t\t\t//            column.add(\"row_number()OVER(PARTITION BY \" + q + key + q + \" ORDER BY \" + q + key + q + \" ASC):_row_num_\");\r\n\t\t\t\t//          }\r\n\t\t\t\t//          jc.setColumn(column);\r\n\t\t\t\t//        }\r\n\r\n\t\t\t\tint childCount = cc.getCount();\r\n\t\t\t\tint allChildCount = childCount*config.getCount();  // 所有分组子项数量总和\r\n\t\t\t\tboolean isOne2Many = childCount != 1 || join.isOne2Many();\r\n\t\t\t\t// 一对多会漏副表数据  TODO 似乎一对一走以下优化 row_number() <= childCount 逻辑也没问题\r\n\t\t\t\t//        if (isOne2Many == false && allChildCount > 0 && jc.getCount() < allChildCount) {\r\n\t\t\t\t//          jc.setCount(allChildCount);\r\n\t\t\t\t//        }\r\n\r\n\t\t\t\tboolean prepared = jc.isPrepared();\r\n\t\t\t\tString sql = jc.gainSQL(false);\r\n\r\n\t\t\t\tif (StringUtil.isEmpty(sql, true)) {\r\n\t\t\t\t\tthrow new NullPointerException(TAG + \".executeAppJoin  StringUtil.isEmpty(sql, true) >> return null;\");\r\n\t\t\t\t}\r\n\r\n\t\t\t\tString sql2 = null;\r\n\t\t\t\tif (childCount > 0 && isOne2Many && (jc.isMySQL() == false || jc.gainDBVersionNums()[0] >= 8)) {\r\n\t\t\t\t\t//          加 row_number 字段并不会导致 count 等聚合函数统计出错，结果偏大，SQL JOIN 才会，之前没发现是因为缓存失效 bug\r\n\t\t\t\t\t//          boolean noAggrFun = true;\r\n\t\t\t\t\t//          List<String> column = jc.getColumn();\r\n\t\t\t\t\t//          if (column != null) {\r\n\t\t\t\t\t//            for (String c : column) {\r\n\t\t\t\t\t//              int start = c == null ? -1 : c.indexOf(\"(\");\r\n\t\t\t\t\t//              int end = start <= 0 ? -1 : c.lastIndexOf(\")\");\r\n\t\t\t\t\t//              if (start > 0 && end > start) {\r\n\t\t\t\t\t//                String fun = c.substring(0, start);\r\n\t\t\t\t\t//                if (AbstractSQLConfig.SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) {\r\n\t\t\t\t\t//                  noAggrFun = false;\r\n\t\t\t\t\t//                  break;\r\n\t\t\t\t\t//                }\r\n\t\t\t\t\t//              }\r","sourceCodeStart":818,"sourceCodeEnd":854,"githubUrl":"https://github.com/Tencent/APIJSON/blob/5284052872898eddc449a58f629e5c8d588b8e22/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java#L818-L854","documentation":"Internal NullPointerException from executeAppJoin: jc.gainSQL(false) returned an empty/blank string. The APP JOIN executor is about to run the vice-table query; an empty SQL means the join config failed to generate a statement (e.g. no table or no resolvable columns), which is a server-side invariant violation, not normal input validation.","triggerScenarios":"An APP JOIN whose join config ended up with no table name or an SQL generator path returning empty — typically a misconfigured join alias referencing a table that was removed from the request, or internal state corruption after refactoring.","commonSituations":"Request-graph manipulation (conditionally removing tables while leaving the join), custom SQLConfig subclasses overriding getSQL/atti incorrectly, version mismatches.","solutions":["Ensure the joined table object stays in the request with at least the referenced alias/columns.","If extending SQLConfig, verify getSQL(false) never returns empty for valid configs.","Log the failing join config (table, alias, column) to identify which entry produces empty SQL; fix or remove it.","Align APIJSON versions and check upstream issues if the request is fully standard."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (StringUtil.isEmpty(jc.gainSQL(false), true)) { throw new IllegalStateException('APP JOIN config produced empty SQL for table ' + jc.getTable()); }","typeGuard":null,"tryCatchPattern":"try { executor.execute(config); } catch (NullPointerException npe) { if (String(npe.getMessage()).contains('executeAppJoin')) { log.error('empty SQL for join config {}', config.getTable()); return serverError(); } throw npe; }","preventionTips":["Keep the joined table object (with table name and alias) present in every request that declares the join.","If overriding SQLConfig.getSQL, guarantee non-empty output for valid configs.","Log join configs before execution to catch degenerate configurations early."],"tags":["apijson","app-join","internal-error","sql-generation"],"backgroundTag":null,"analyzedSha":"5284052872898eddc449a58f629e5c8d588b8e22","analyzedAt":"2026-08-14T15:15:29.577Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}