Support @NativeQuery for syntax highlighting and validation

This commit is contained in:
aboyko
2025-03-28 18:08:22 -04:00
parent d89fd1454e
commit 20b3bfa68c
5 changed files with 171 additions and 6 deletions

View File

@@ -48,6 +48,7 @@ public class Annotations {
public static final String DATA_QUERY_META_ANNOTATION = "org.springframework.data.annotation.QueryAnnotation";
public static final String DATA_JPA_QUERY = "org.springframework.data.jpa.repository.Query";
public static final String DATA_JPA_NATIVE_QUERY = "org.springframework.data.jpa.repository.NativeQuery";
public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
public static final String QUALIFIER = "org.springframework.beans.factory.annotation.Qualifier";

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2024 Broadcom, Inc.
* Copyright (c) 2024, 2025 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -27,11 +27,15 @@ public class JdtQueryVisitorUtils {
private static final String QUERY = "Query";
private static final String NAMED_QUERY = "NamedQuery";
private static final String NATIVE_QUERY = "NativeQuery";
public record EmbeddedQueryExpression(EmbeddedLanguageSnippet query, boolean isNative) {};
public static EmbeddedQueryExpression extractQueryExpression(AnnotationHierarchies annotationHierarchies, SingleMemberAnnotation a) {
if (isQueryAnnotation(annotationHierarchies, a)) {
if (isNativeQueryAnnotation(annotationHierarchies, a)) {
EmbeddedLanguageSnippet expression = EmbeddedLangAstUtils.extractEmbeddedExpression(a.getValue());
return expression == null ? null : new EmbeddedQueryExpression(expression, true);
} else if (isQueryAnnotation(annotationHierarchies, a)) {
EmbeddedLanguageSnippet expression = EmbeddedLangAstUtils.extractEmbeddedExpression(a.getValue());
return expression == null ? null : new EmbeddedQueryExpression(expression, false);
}
@@ -41,7 +45,22 @@ public class JdtQueryVisitorUtils {
public static EmbeddedQueryExpression extractQueryExpression(AnnotationHierarchies annotationHierarchies, NormalAnnotation a) {
Expression queryExpression = null;
boolean isNative = false;
if (isQueryAnnotation(annotationHierarchies, a)) {
if (isNativeQueryAnnotation(annotationHierarchies, a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
String name = pair.getName().getFullyQualifiedName();
if (name != null) {
switch (name) {
case "value":
queryExpression = pair.getValue();
isNative = true;
break;
}
}
}
}
} else if (isQueryAnnotation(annotationHierarchies, a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
@@ -119,5 +138,15 @@ public class JdtQueryVisitorUtils {
}
return false;
}
static boolean isNativeQueryAnnotation(AnnotationHierarchies annotationHierarchies, Annotation a) {
if (NATIVE_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || Annotations.DATA_JPA_NATIVE_QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
IAnnotationBinding type = a.resolveAnnotationBinding();
if (type != null) {
return annotationHierarchies.isAnnotatedWith(type, Annotations.DATA_JPA_NATIVE_QUERY);
}
}
return false;
}
}

View File

@@ -72,7 +72,8 @@ public class QueryJdtAstReconciler implements JdtAstReconciler {
public boolean visit(SingleMemberAnnotation node) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node);
if (q != null) {
getQueryReconciler(project).reconcile(q.query().getText(), q.query()::toSingleJavaRange, context.getProblemCollector());
Optional<Reconciler> reconcilerOpt = q.isNative() ? getSqlReconciler(project) : Optional.of(getQueryReconciler(project));
reconcilerOpt.ifPresent(r -> r.reconcile(q.query().getText(), q.query()::toSingleJavaRange, context.getProblemCollector()));
}
return super.visit(node);
}

View File

@@ -292,7 +292,7 @@ public class JdtDataQuerySemanticTokensProviderTest {
}
@Test
void nativeQuery() throws Exception {
void nativeQueryAttribute() throws Exception {
String source = """
package my.package
@@ -357,7 +357,141 @@ public class JdtDataQuerySemanticTokensProviderTest {
assertThat(token).isEqualTo(new SemanticTokenData(167, 168, "number", new String[0]));
}
@Test
void nativeQuery_1() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.NativeQuery;
public interface OwnerRepository {
@NativeQuery(value = "SELECT * FROM USERS u WHERE u.status = 1")
void findByLastName();
}
""";
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString();
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp);
assertThat(cu).isNotNull();
List<SemanticTokenData> tokens = computeTokens(cu);
SemanticTokenData token = tokens.get(0);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("SELECT");
assertThat(token).isEqualTo(new SemanticTokenData(140, 146, "keyword", new String[0]));
token = tokens.get(1);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("*");
assertThat(token).isEqualTo(new SemanticTokenData(147, 148, "operator", new String[0]));
token = tokens.get(2);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("FROM");
assertThat(token).isEqualTo(new SemanticTokenData(149, 153, "keyword", new String[0]));
token = tokens.get(3);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("USERS");
assertThat(token).isEqualTo(new SemanticTokenData(154, 159, "variable", new String[0]));
token = tokens.get(4);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("u");
assertThat(token).isEqualTo(new SemanticTokenData(160, 161, "variable", new String[0]));
token = tokens.get(5);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("WHERE");
assertThat(token).isEqualTo(new SemanticTokenData(162, 167, "keyword", new String[0]));
token = tokens.get(6);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("u");
assertThat(token).isEqualTo(new SemanticTokenData(168, 169, "variable", new String[0]));
token = tokens.get(7);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo(".");
assertThat(token).isEqualTo(new SemanticTokenData(169, 170, "operator", new String[0]));
token = tokens.get(8);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("status");
assertThat(token).isEqualTo(new SemanticTokenData(170, 176, "property", new String[0]));
token = tokens.get(9);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("=");
assertThat(token).isEqualTo(new SemanticTokenData(177, 178, "operator", new String[0]));
token = tokens.get(10);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("1");
assertThat(token).isEqualTo(new SemanticTokenData(179, 180, "number", new String[0]));
}
@Test
void nativeQuery_2() throws Exception {
String source = """
package my.package
import org.springframework.data.jpa.repository.NativeQuery;
public interface OwnerRepository {
@NativeQuery("SELECT * FROM USERS u WHERE u.status = 1")
void findByLastName();
}
""";
String uri = Paths.get(jp.getLocationUri()).resolve("src/main/resource/my/package/OwnerRepository.java").toUri().toASCIIString();
CompilationUnit cu = CompilationUnitCache.parse2(source.toCharArray(), uri, "OwnerRepository.java", jp);
assertThat(cu).isNotNull();
List<SemanticTokenData> tokens = computeTokens(cu);
SemanticTokenData token = tokens.get(0);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("SELECT");
assertThat(token).isEqualTo(new SemanticTokenData(132, 138, "keyword", new String[0]));
token = tokens.get(1);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("*");
assertThat(token).isEqualTo(new SemanticTokenData(140, 141, "operator", new String[0]));
token = tokens.get(2);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("FROM");
assertThat(token).isEqualTo(new SemanticTokenData(142, 146, "keyword", new String[0]));
token = tokens.get(3);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("USERS");
assertThat(token).isEqualTo(new SemanticTokenData(147, 152, "variable", new String[0]));
token = tokens.get(4);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("u");
assertThat(token).isEqualTo(new SemanticTokenData(153, 154, "variable", new String[0]));
token = tokens.get(5);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("WHERE");
assertThat(token).isEqualTo(new SemanticTokenData(155, 160, "keyword", new String[0]));
token = tokens.get(6);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("u");
assertThat(token).isEqualTo(new SemanticTokenData(161, 162, "variable", new String[0]));
token = tokens.get(7);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo(".");
assertThat(token).isEqualTo(new SemanticTokenData(162, 163, "operator", new String[0]));
token = tokens.get(8);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("status");
assertThat(token).isEqualTo(new SemanticTokenData(163, 169, "property", new String[0]));
token = tokens.get(9);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("=");
assertThat(token).isEqualTo(new SemanticTokenData(170, 171, "operator", new String[0]));
token = tokens.get(10);
assertThat(source.substring(token.getStart(), token.getEnd())).isEqualTo("1");
assertThat(token).isEqualTo(new SemanticTokenData(172, 173, "number", new String[0]));
}
@Test
void nativeQueryWithSpel() throws Exception {
String source = """

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<version>3.4.4</version>
<relativePath />
</parent>