From 89fa184379cc9da1eea3b9f322e5ed13e6f8fec2 Mon Sep 17 00:00:00 2001 From: TommyLemon Date: Sun, 15 Feb 2026 16:16:19 +0800 Subject: [PATCH] =?UTF-8?q?LEFT/RIGHT=20JOIN=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9C=A8=E5=A4=96=E5=B1=82WHERE/GROUP=20BY/HAVING=20?= =?UTF-8?q?=E5=8A=A0=E6=9D=A1=E4=BB=B6/=E6=8E=92=E5=BA=8F(=E5=BE=85?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BC=95=E5=85=A5=20bug)=20#824?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/apijson/orm/AbstractParser.java | 48 +++++++---- .../java/apijson/orm/AbstractSQLConfig.java | 79 +++++++++++++++---- .../java/apijson/orm/AbstractSQLExecutor.java | 12 +-- .../src/main/java/apijson/orm/SQLConfig.java | 5 +- 4 files changed, 105 insertions(+), 39 deletions(-) diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index cd0b2c7b..77cb745c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1563,20 +1563,34 @@ else if (join != null){ throw new UnsupportedDataTypeException(TAG + ".onJoinParse join 只能是 String 或 Map 类型!"); } - List> slashKeys = new ArrayList<>(); - List> nonSlashKeys = new ArrayList<>(); Set> entries = joinMap == null ? null : joinMap.entrySet(); if (entries == null || entries.isEmpty()) { Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); return null; } - for (Entry e : entries) { + + M mainOuter = JSON.createJSONObject(); + Join mainJoin = new Join<>(); + mainJoin.setRequest(mainOuter); + + List> slashKeys = new ArrayList<>(); + List> nonSlashKeys = new ArrayList<>(); + // https://github.com/Tencent/APIJSON/issues/824#issuecomment-3038316490 + for (Entry e : entries) { // https://github.com/Tencent/APIJSON/pull/829 String path = e.getKey(); - if (path != null && path.indexOf("/") > 0) { - slashKeys.add(e); // 以 / 开头的 key,例如 ) { + if (path != null && path.indexOf("/") > 0) { + slashKeys.add(e); // 以 / 开头的 key,例如 ) { whereJoinMap.put(tableKey, tableObj); } else { - Log.w(TAG, "跳过 join 中 key = " + tableKey + ",因为它不在 request 中"); + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + tableKey + "' 不是 JOIN 副表的名称 !"); } } + List> joinList = new ArrayList<>(); + joinList.add(mainJoin); Set> set = joinMap == null ? null : new LinkedHashSet<>(slashKeys); if (set == null || set.isEmpty()) { Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); - return null; + return joinList; } - List> joinList = new ArrayList<>(); - for (Entry e : set) { // { &/User:{}, == false) { throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0,) outer); + M outerObj = JSON.createJSONObject((Map) outer); j.setOn(outerObj); j.setRequest(requestObj); - if (whereJoinMap.containsKey(table)) { - Object rawOuter = whereJoinMap.get(table); - M outerObj1 = (M) JSON.createJSONObject((Map) rawOuter); + Object rawOuter = whereJoinMap.get(table); + if (rawOuter instanceof Map) { + M outerObj1 = JSON.createJSONObject((Map) rawOuter); j.setOuter(outerObj1); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index d9e9eb01..de4414a3 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -2347,6 +2347,16 @@ public String gainColumnString(boolean inSQLJoin) throws Exception { if (joinList != null) { boolean first = true; for (Join join : joinList) { + if (join == null) { + continue; + } + if (join.getJoinType() == null) { + SQLConfig cfg = join.getJoinConfig(); + List col = cfg == null ? null : cfg.getColumn(); + column = col == null ? column : col; + continue; + } + if (join.isAppJoin()) { continue; } @@ -3315,11 +3325,23 @@ else if (key.equals(userIdInKey)) { */ @Override public String gainWhereString(boolean hasPrefix) throws Exception { - String combineExpr = getCombine(); + Join join = joinList == null || joinList.isEmpty() ? null : joinList.get(0); + SQLConfig cfg = join == null || join.getJoinType() != null ? null : join.getJoinConfig(); + Map joinWhere = cfg == null ? null : cfg.getWhere(); + //String ws = cfg == null ? null : cfg.gainWhereString(hasPrefix); + //if (StringUtil.isNotEmpty(ws, true)) { + // return ws; + //} + + String joinCombine = cfg == null ? null : cfg.getCombine(); + Map where = joinWhere == null ? getWhere() : joinWhere; + String combineExpr = StringUtil.isEmpty(joinCombine, true) ? getCombine() : joinCombine; if (StringUtil.isEmpty(combineExpr, false)) { - return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); + Map> joinCombineMap = cfg == null ? null : cfg.getCombineMap(); + Map> combineMap = joinCombineMap == null ? getCombineMap() : joinCombineMap; + return getWhereString(hasPrefix, getMethod(), where, combineMap, getJoinList(), ! isTest()); } - return getWhereString(hasPrefix, getMethod(), getWhere(), combineExpr, getJoinList(), ! isTest()); + return getWhereString(hasPrefix, getMethod(), where, combineExpr, getJoinList(), ! isTest()); } /**获取WHERE * @param method @@ -3741,7 +3763,10 @@ protected String concatJoinWhereString(String whereString) throws Exception { boolean changed = false; // 各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? for (Join j : joinList) { - String jt = j.getJoinType(); + String jt = j == null ? null : j.getJoinType(); + if (jt == null) { + continue; + } switch (jt) { case "*": // CROSS JOIN @@ -3752,7 +3777,7 @@ protected String concatJoinWhereString(String whereString) throws Exception { if (outerConfig == null){ break; } - boolean isMain1 = outerConfig.isMain(); + outerConfig.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); String outerWhere = outerConfig.gainWhereString(false); @@ -4021,7 +4046,10 @@ else if (isTest()) { return gainSQLValue(key).toString(); } - Map keyMap = getKeyMap(); + Join join = joinList == null || joinList.isEmpty() ? null : joinList.get(0); + SQLConfig cfg = join == null || join.getJoinType() != null ? null : join.getJoinConfig(); + Map joinKeyMap = cfg == null ? null : cfg.getKeyMap(); + Map keyMap = joinKeyMap == null ? getKeyMap() : joinKeyMap; String expression = keyMap == null ? null : keyMap.get(key); if (expression == null) { expression = COLUMN_KEY_MAP == null ? null : COLUMN_KEY_MAP.get(key); @@ -5162,13 +5190,23 @@ public String gainJoinString() throws Exception { List pvl = getPreparedValueList(); // new ArrayList<>(); //boolean changed = false; - // 主表不用别名 String ta; - for (Join j : joinList) { + // 主表不用别名 String ta; + boolean first = true; + for (Join j : joinList) { + if (j == null) { + continue; + } + if (first) { + first = false; + continue; + } + onGainJoinString(j); if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; } + String type = j.getJoinType(); //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 @@ -5490,7 +5528,7 @@ public static , L extends List> SQLConf String catalog = getString(request, KEY_CATALOG); String schema = getString(request, KEY_SCHEMA); - SQLConfig config = (SQLConfig) callback.getSQLConfig(method, database, schema, datasource, table); + SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); config.setDatabase(database); // 不删,后面表对象还要用的,必须放在 parseJoin 前 @@ -6306,6 +6344,11 @@ public static , L extends List> SQLConf for (Join join : joinList) { table = join.getTable(); alias = join.getAlias(); + + if (table == null && join.getJoinType() == null) { + table = config.getTable(); + } + //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 SQLConfig joinConfig = newSQLConfig(method, table, alias, join.getRequest(), null, false, callback); SQLConfig cacheConfig = join.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias @@ -6319,6 +6362,7 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { throw new IllegalArgumentException("主表 " + config.getTable() + " 的 @database:" + config.getDatabase() + " 和它 SQL JOIN 的副表 " + table + " 的 @database:" + joinConfig.getDatabase() + " 不一致!"); } + if (joinConfig.getSchema() == null) { joinConfig.setSchema(config.getSchema()); //主表 JOIN 副表,默认 schema 一致 } @@ -6327,16 +6371,17 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 } - if (isQuery) { - config.setKeyPrefix(true); + if (join.getJoinType() != null) { + if (isQuery) { + config.setKeyPrefix(true); + } + joinConfig.setMain(false).setKeyPrefix(true); } - joinConfig.setMain(false).setKeyPrefix(true); - if (join.getOn() != null) { SQLConfig onConfig = newSQLConfig(method, table, alias, join.getOn(), null, false, callback); - onConfig.setMain(false) - .setKeyPrefix(true) + onConfig.setMain(joinConfig.isMain()) + .setKeyPrefix(joinConfig.isKeyPrefix()) .setDatabase(joinConfig.getDatabase()) .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 @@ -6345,8 +6390,8 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { if (join.getOuter() != null) { SQLConfig outerConfig = newSQLConfig(method, table, alias, join.getOuter(), null, false, callback); - outerConfig.setMain(false) - .setKeyPrefix(true) + outerConfig.setMain(joinConfig.isMain()) + .setKeyPrefix(joinConfig.isKeyPrefix()) .setDatabase(joinConfig.getDatabase()) .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 join.setOuterConfig(outerConfig); diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 797ac3de..e833753e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -426,7 +426,7 @@ public M execute(@NotNull SQLConfig config, boolean unknownType) throws // 为什么 isExplain == false 不用判断?因为所有字段都在一张 Query Plan 表 if (index <= 0 && columnIndexAndJoinMap != null) { // && viceColumnStart > length) { - SQLConfig curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getCacheConfig(); + SQLConfig curConfig = curJoin == null || curJoin.getJoinType() == null || ! curJoin.isSQLJoin() ? null : curJoin.getCacheConfig(); List curColumn = curConfig == null ? null : curConfig.getColumn(); String sqlTable = curConfig == null ? null : curConfig.gainSQLTable(); String sqlAlias = curConfig == null ? null : curConfig.getAlias(); @@ -469,7 +469,7 @@ public M execute(@NotNull SQLConfig config, boolean unknownType) throws else if (isMain) { for (int j = 0; j < joinList.size(); j++) { Join join = joinList.get(j); - SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); + SQLConfig cfg = join == null || join.getJoinType() == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); List c = cfg == null ? null : cfg.getColumn(); if (cfg != null) { @@ -501,7 +501,7 @@ else if (isMain) { int joinCount = joinList.size(); for (int j = lastViceTableStart; j < joinCount; j++) { // 查找副表 @column,定位字段所在表 Join join = joinList.get(j); - SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); + SQLConfig cfg = join == null || join.getJoinType() == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); List c = cfg == null ? null : cfg.getColumn(); nextViceColumnStart += (c != null && ! c.isEmpty() ? @@ -574,7 +574,7 @@ else if (isMain) { } if (curColumn == null) { - curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getJoinConfig(); + curConfig = curJoin == null || curJoin.getJoinType() == null || ! curJoin.isSQLJoin() ? null : curJoin.getJoinConfig(); curColumn = curConfig == null ? null : curConfig.getColumn(); } @@ -590,7 +590,7 @@ else if (isMain) { // 如果是主表则直接用主表对应的 item,否则缓存副表数据到 childMap Join prevJoin = columnIndexAndJoinMap == null || i < 2 ? null : columnIndexAndJoinMap[i - 2]; if (curJoin != prevJoin) { // 前后字段不在同一个表对象,即便后面出现 null,也不该是主表数据,而是逻辑 bug 导致 - SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null; + SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() && curJoin.getJoinType() == null ? curJoin.getCacheConfig() : null; boolean hasPK = false; if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 List onList = curJoin.getOnList(); @@ -991,7 +991,7 @@ protected M onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet r Object value = getValue(config, rs, rsmd, row, table, columnIndex, label, childMap, keyMap); // 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值 - if (value != null || ENABLE_OUTPUT_NULL_COLUMN || (join == null && table.isEmpty())) { + if (value != null || ENABLE_OUTPUT_NULL_COLUMN || ((join == null || join.getJoinType() == null) && table.isEmpty())) { table.put(label, value); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 5386d160..9b65bac0 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -392,7 +392,10 @@ static String gainSQLAlias(@NotNull String table, String alias) { return StringUtil.isEmpty(alias) ? table : table + "__" + alias; // 带上原表名,避免 alias 和其它表名/字段名冲突 } - + default String gainColumnString() throws Exception { + return gainColumnString(false); + } + String gainColumnString(boolean inSQLJoin) throws Exception; String gainWhereString(boolean hasPrefix) throws Exception; String gainRawSQL(String key, Object value) throws Exception;