Support @NativeQuery for syntax highlighting and validation
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user