Skip to content

[#10173] Guard the empty list case in sql generation for PostgreSQL#10261

Open
pithecuse527 wants to merge 1 commit intoapache:mainfrom
pithecuse527:fix/invalid-postgresql-with-empty-roleids
Open

[#10173] Guard the empty list case in sql generation for PostgreSQL#10261
pithecuse527 wants to merge 1 commit intoapache:mainfrom
pithecuse527:fix/invalid-postgresql-with-empty-roleids

Conversation

@pithecuse527
Copy link

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,

  1. If you propose a new API, clarify the use case for a new API.
  2. If you fix a bug, describe the bug.)

Fix: #10173

Does this PR introduce any user-facing change?

No

How was this patch tested?

UTs

Copilot AI review requested due to automatic review settings March 5, 2026 16:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@pithecuse527 pithecuse527 requested a review from Copilot March 5, 2026 17:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@pithecuse527 pithecuse527 requested a review from Copilot March 5, 2026 19:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

+ ") "
+ "</if>"
+ "<if test='roleIds == null or roleIds.size() == 0'>"
+ "AND 1= 0 "
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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=`).

Suggested change
+ "AND 1= 0 "
+ "AND 1 = 0 "

Copilot uses AI. Check for mistakes.

Assertions.assertFalse(
normalizedSql.matches(".*\\bIN\\s*\\(\\s*\\).*"),
"Empty roleIds should not generate invalid SQL IN (...) with no values");
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"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)");

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +49
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");
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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);

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No real need to do this, unless you want to, WET or DAMP tests are usually fine.

@yuqi1129 yuqi1129 requested a review from Copilot March 6, 2026 03:53
@yuqi1129
Copy link
Contributor

yuqi1129 commented Mar 6, 2026

@pithecuse527
Could you take the time resolve the comments?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment on lines +91 to +100
+ "<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>"
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@jerryshao jerryshao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Good fix — MyBatis does not support IN () with an empty collection, so guarding it is necessary.

Minor issues

  1. Typo / inconsistent spacing: In UserRoleRelPostgreSQLProvider.softDeleteUserRoleRelByUserAndRoles, the empty-list guard reads "AND 1= 0 " (missing space before 0). All other providers use "AND 1 = 0 ". Please fix for consistency.

  2. Consider early return at service layer: Using AND 1 = 0 is a valid SQL trick, but it is a hidden no-op that may surprise future maintainers reading the mapper. If the caller already knows roleIds is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement] Empty roleIds producing invalid PostgreSQL IN () SQL

5 participants