Skip to content

Commit 55fafc9

Browse files
committed
Fix parameter parsing inside SQL/JPQL comments
Prevent ParameterBindingParser from treating ? and : as bind markers when they appear inside SQL or JPQL comments. The query preprocessor now uses a state-aware parser to track block comments, line comments, and string literals, ensuring parameter markers are ignored only in valid comment regions while preserving optimizer hints and query structure. Fixes #4090 Signed-off-by: Nabil Fawwaz Elqayyim <master@nabilfawwaz.com>
1 parent d128d20 commit 55fafc9

2 files changed

Lines changed: 104 additions & 1 deletion

File tree

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PreprocessedQuery.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
*
5959
* @author Christoph Strobl
6060
* @author Mark Paluch
61+
* @author Nabil Fawwaz Elqayyim
6162
* @since 4.0
6263
*/
6364
public final class PreprocessedQuery implements DeclaredQuery {
@@ -247,6 +248,7 @@ PreprocessedQuery parse(String query, Function<String, DeclaredQuery> declaredQu
247248

248249
String resultingQuery = parsedQuery.getQueryString();
249250
Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(resultingQuery);
251+
java.util.BitSet commentPositions = findCommentPositions(resultingQuery);
250252

251253
ParameterBindings parameterBindings = new ParameterBindings(bindings, it -> checkAndRegister(it, bindings));
252254
int currentIndex = 0;
@@ -255,7 +257,7 @@ PreprocessedQuery parse(String query, Function<String, DeclaredQuery> declaredQu
255257

256258
while (matcher.find()) {
257259

258-
if (parsedQuery.isQuoted(matcher.start())) {
260+
if (parsedQuery.isQuoted(matcher.start()) || commentPositions.get(matcher.start())) {
259261
continue;
260262
}
261263

@@ -625,4 +627,68 @@ public boolean hasLabels() {
625627
}
626628
}
627629

630+
private static java.util.BitSet findCommentPositions(String query) {
631+
int queryLen = query.length();
632+
java.util.BitSet isComment = new java.util.BitSet(queryLen);
633+
boolean inBlockComment = false;
634+
boolean inLineComment = false;
635+
boolean inString = false;
636+
637+
for (int i = 0; i < queryLen; i++) {
638+
char c = query.charAt(i);
639+
char next = (i + 1 < queryLen) ? query.charAt(i + 1) : '\0';
640+
641+
// String Literal State: Comment markers are treated as normal characters
642+
if (inString) {
643+
if (c == '\'') {
644+
// Handle SQL-style escaped quotes: ''
645+
if (next == '\'') {
646+
i++;
647+
} else {
648+
inString = false;
649+
}
650+
}
651+
continue;
652+
}
653+
654+
// Block Comment State: Identify positions within /* ... */
655+
if (inBlockComment) {
656+
isComment.set(i);
657+
if (c == '*' && next == '/') {
658+
isComment.set(i + 1);
659+
inBlockComment = false;
660+
i++;
661+
}
662+
continue;
663+
}
664+
665+
// Line Comment State: Identify positions within -- or //
666+
if (inLineComment) {
667+
isComment.set(i);
668+
// End line comment state on newline or carriage return
669+
if (c == '\n' || c == '\r') {
670+
inLineComment = false;
671+
}
672+
continue;
673+
}
674+
675+
// Entering States: Check for quotes or comment markers
676+
if (c == '\'') {
677+
inString = true;
678+
} else if (c == '/' && next == '*') {
679+
// Start of block comment
680+
inBlockComment = true;
681+
isComment.set(i);
682+
isComment.set(i + 1);
683+
i++;
684+
} else if ((c == '-' && next == '-') || (c == '/' && next == '/')) {
685+
// Start of line comment (both SQL-style '--' and Java-style '//')
686+
inLineComment = true;
687+
isComment.set(i);
688+
isComment.set(i + 1);
689+
i++;
690+
}
691+
}
692+
return isComment;
693+
}
628694
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PreprocessedQueryUnitTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* @author Jens Schauder
3030
* @author Diego Krupitza
3131
* @author Mark Paluch
32+
* @author Nabil Fawwaz Elqayyim
3233
*/
3334
class PreprocessedQueryUnitTests {
3435

@@ -52,4 +53,40 @@ static Stream<Arguments.ArgumentSet> parameters() {
5253
Arguments.argumentSet("no parameter", "select u from User u where u.email = u.email", false));
5354
}
5455

56+
@ParameterizedTest // GH-4090
57+
@MethodSource("commentParameters")
58+
void shouldIgnoreMarkersInComments(String queryText, int expectedBindingCount, String description) {
59+
60+
// Use the official static factory method from the DeclaredQuery interface
61+
DeclaredQuery declaredQuery = DeclaredQuery.jpqlQuery(queryText);
62+
PreprocessedQuery preprocessed = PreprocessedQuery.parse(declaredQuery);
63+
64+
assertThat(preprocessed.getBindings())
65+
.as(description)
66+
.hasSize(expectedBindingCount);
67+
}
68+
69+
static Stream<Arguments> commentParameters() {
70+
return Stream.of(
71+
// 1. Block Comment Scenarios
72+
Arguments.of("SELECT e FROM Entity e /* block ? */ WHERE e.id = :id", 1, "Standard block comment"),
73+
Arguments.of("SELECT e FROM Entity e /* asterisk * inside */ WHERE e.id = :id", 1, "Asterisk inside block comment"),
74+
75+
// 2. SQL-style Line Comment Scenarios
76+
Arguments.of("SELECT e FROM Entity e -- line comment \n WHERE e.id = :id", 1, "SQL-style line comment with newline"),
77+
78+
// 3. Java-style Line Comment Scenarios
79+
// This specific case turns the remaining yellow branches green.
80+
Arguments.of("SELECT e FROM Entity e // java comment \r WHERE e.id = :id", 1, "Java-style line comment with carriage return"),
81+
82+
// 4. Mathematical Operators
83+
// Ensures '/' and '-' are not always treated as comment starts.
84+
Arguments.of("SELECT e FROM Entity e WHERE e.id / 2 = 1 AND e.id * 2 = :id", 1, "Slash and asterisk as operators"),
85+
Arguments.of("SELECT e FROM Entity e WHERE e.id - 1 = :id", 1, "Hyphen as subtraction operator"),
86+
87+
// 5. String Literal and Escaping Scenarios
88+
// Confirms that markers inside quotes are ignored and escaped quotes are handled.
89+
Arguments.of("SELECT e FROM Entity e WHERE e.name = 'It''s a -- marker' AND e.id = :id", 1, "Markers inside string literals")
90+
);
91+
}
5592
}

0 commit comments

Comments
 (0)