[#10173] Guard the empty list case in sql generation for PostgreSQL#10261
[#10173] Guard the empty list case in sql generation for PostgreSQL#10261pithecuse527 wants to merge 1 commit intoapache:mainfrom
Conversation
| + ") " | ||
| + "</if>" | ||
| + "<if test='roleIds == null or roleIds.size() == 0'>" | ||
| + "AND 1= 0 " |
There was a problem hiding this comment.
"AND 1= 0 " has inconsistent spacing compared to the other providers (AND 1 = 0). For readability/consistency (and to avoid style drift in generated SQL), align it to AND 1 = 0(also consider removing the extra space after=`).
| + "AND 1= 0 " | |
| + "AND 1 = 0 " |
|
|
||
| Assertions.assertFalse( | ||
| normalizedSql.matches(".*\\bIN\\s*\\(\\s*\\).*"), | ||
| "Empty roleIds should not generate invalid SQL IN (...) with no values"); |
There was a problem hiding this comment.
This assertion only verifies the absence of IN (), but it would still pass if the SQL accidentally dropped the role filter entirely (potentially updating all roles for the user/group). Consider strengthening the test by also asserting the presence of the intended guard predicate (e.g., AND 1 = 0) or otherwise verifying the WHERE clause becomes unsatisfiable when roleIds is empty. Same pattern applies to the other three new test classes.
| "Empty roleIds should not generate invalid SQL IN (...) with no values"); | |
| "Empty roleIds should not generate invalid SQL IN (...) with no values"); | |
| Assertions.assertTrue( | |
| normalizedSql.matches(".*\\b1\\s*=\\s*0\\b.*"), | |
| "Empty roleIds should result in an unsatisfiable WHERE clause (e.g., AND 1 = 0)"); |
| SqlSource sqlSource = | ||
| new XMLLanguageDriver().createSqlSource(new Configuration(), script, Map.class); | ||
| Map<String, Object> params = new HashMap<>(); | ||
| params.put("groupId", 1L); | ||
| params.put("roleIds", Collections.emptyList()); | ||
|
|
||
| BoundSql boundSql = sqlSource.getBoundSql(params); | ||
| String normalizedSql = boundSql.getSql().replaceAll("\\s+", " ").trim(); | ||
|
|
||
| Assertions.assertFalse( | ||
| normalizedSql.matches(".*\\bIN\\s*\\(\\s*\\).*"), | ||
| "Empty roleIds should not generate invalid SQL IN (...) with no values"); |
There was a problem hiding this comment.
The four new test classes appear to be near-identical aside from provider type and parameter key (userId vs groupId). To reduce duplication and ease future changes (e.g., adjusting normalization/assertions), consider extracting a small shared helper (e.g., assertNoEmptyInClause(script, params)) or converting to a parameterized test with inputs (provider/script supplier + id key).
| SqlSource sqlSource = | |
| new XMLLanguageDriver().createSqlSource(new Configuration(), script, Map.class); | |
| Map<String, Object> params = new HashMap<>(); | |
| params.put("groupId", 1L); | |
| params.put("roleIds", Collections.emptyList()); | |
| BoundSql boundSql = sqlSource.getBoundSql(params); | |
| String normalizedSql = boundSql.getSql().replaceAll("\\s+", " ").trim(); | |
| Assertions.assertFalse( | |
| normalizedSql.matches(".*\\bIN\\s*\\(\\s*\\).*"), | |
| "Empty roleIds should not generate invalid SQL IN (...) with no values"); | |
| Map<String, Object> params = new HashMap<>(); | |
| params.put("groupId", 1L); | |
| params.put("roleIds", Collections.emptyList()); | |
| assertNoEmptyInClause( | |
| script, params, "Empty roleIds should not generate invalid SQL IN (...) with no values"); | |
| } | |
| private void assertNoEmptyInClause( | |
| String script, Map<String, Object> params, String failureMessage) { | |
| SqlSource sqlSource = | |
| new XMLLanguageDriver().createSqlSource(new Configuration(), script, Map.class); | |
| BoundSql boundSql = sqlSource.getBoundSql(params); | |
| String normalizedSql = boundSql.getSql().replaceAll("\\s+", " ").trim(); | |
| Assertions.assertFalse( | |
| normalizedSql.matches(".*\\bIN\\s*\\(\\s*\\).*"), failureMessage); |
There was a problem hiding this comment.
No real need to do this, unless you want to, WET or DAMP tests are usually fine.
|
@pithecuse527 |
| + "<if test='roleIds != null and roleIds.size() > 0'>" | ||
| + "AND role_id IN (" | ||
| + "<foreach collection='roleIds' item='roleId' separator=','>" | ||
| + "#{roleId}" | ||
| + "</foreach>" | ||
| + " )" | ||
| + " AND deleted_at = 0" | ||
| + ") " | ||
| + "</if>" | ||
| + "<if test='roleIds == null or roleIds.size() == 0'>" | ||
| + "AND 1 = 0 " | ||
| + "</if>" |
There was a problem hiding this comment.
These two <if> blocks are mutually exclusive, but the intent is clearer and less repetitive using <choose>/<when>/<otherwise>. Refactoring to <choose> would make the “non-empty list” vs “empty/null list” branching more explicit and reduce the chance of future edits accidentally creating overlapping conditions.
...pache/gravitino/storage/relational/mapper/provider/base/TestGroupRoleRelBaseSQLProvider.java
Show resolved
Hide resolved
jerryshao
left a comment
There was a problem hiding this comment.
Code Review
Good fix — MyBatis does not support IN () with an empty collection, so guarding it is necessary.
Minor issues
-
Typo / inconsistent spacing: In
UserRoleRelPostgreSQLProvider.softDeleteUserRoleRelByUserAndRoles, the empty-list guard reads"AND 1= 0 "(missing space before0). All other providers use"AND 1 = 0 ". Please fix for consistency. -
Consider early return at service layer: Using
AND 1 = 0is a valid SQL trick, but it is a hidden no-op that may surprise future maintainers reading the mapper. If the caller already knowsroleIdsis empty and no rows should be affected, consider returning early before calling the mapper (or asserting non-empty at the service layer). This would make the intent explicit. If you prefer to keep the SQL-level guard (e.g., for atomicity), a comment explaining the intent would help.
Positive
- Four unit tests covering all four providers is thorough.
- The fix is symmetric across base and PostgreSQL providers, which is correct.
Please fix the spacing typo before merging.
What changes were proposed in this pull request?
Guard empty roleIds from generating invalid PostgreSQL IN () SQL in UserRoleRel and GroupRoleRel providers.
Why are the changes needed?
(Please clarify why the changes are needed. For instance,
Fix: #10173
Does this PR introduce any user-facing change?
No
How was this patch tested?
UTs