HQL and JPQL syntax validation reconciling
This commit is contained in:
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.app;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.HqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.QueryJdtAstReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JpqlSupportState;
|
||||
@@ -114,5 +115,9 @@ public class JdtConfig {
|
||||
@Bean JdtDataQuerySemanticTokensProvider jpqlJdtSemanticTokensProvider(JpqlSemanticTokens jpqlProvider, HqlSemanticTokens hqlProvider, JpqlSupportState supportState) {
|
||||
return new JdtDataQuerySemanticTokensProvider(jpqlProvider, hqlProvider, supportState);
|
||||
}
|
||||
|
||||
@Bean QueryJdtAstReconciler dataQueryReconciler() {
|
||||
return new QueryJdtAstReconciler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,5 +37,8 @@ public class SpringProblemCategories {
|
||||
|
||||
public static final ProblemCategory VERSION_VALIDATION = new ProblemCategory("version-validation", "Versions and Support Ranges",
|
||||
new Toggle("Enablement", EnumSet.of(OFF, ON), ON, "boot-java.validation.java.version-validation"));
|
||||
|
||||
public static final ProblemCategory JPQL = new ProblemCategory("jpql-validation", "JPQL",
|
||||
new Toggle("Enablement", EnumSet.of(OFF, ON), ON, "boot-java.validation.jpql"));;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public class Annotations {
|
||||
public static final String JPA_JAVAX_EMBEDDED_ID = "javax.persistence.EmbeddedId";
|
||||
public static final String JPA_JAKARTA_ID_CLASS = "jakarta.persistence.IdClass";
|
||||
public static final String JPA_JAVAX_ID_CLASS = "javax.persistence.IdClass";
|
||||
public static final String DATA_QUERY = "org.springframework.data.jpa.repository.Query";
|
||||
|
||||
public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
|
||||
public static final String INJECT = "javax.inject.Inject";
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.antlr.v4.runtime.ANTLRErrorListener;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ConsoleErrorListener;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.atn.ATNConfigSet;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
import org.springframework.ide.vscode.parser.hql.HqlLexer;
|
||||
import org.springframework.ide.vscode.parser.hql.HqlParser;
|
||||
|
||||
public class HqlReconciler implements Reconciler {
|
||||
|
||||
@Override
|
||||
public void reconcile(String text, int startPosition, IProblemCollector problemCollector) {
|
||||
HqlLexer lexer = new HqlLexer(CharStreams.fromString(text));
|
||||
CommonTokenStream antlrTokens = new CommonTokenStream(lexer);
|
||||
HqlParser parser = new HqlParser(antlrTokens);
|
||||
|
||||
parser.removeErrorListener(ConsoleErrorListener.INSTANCE);
|
||||
|
||||
parser.addErrorListener(new ANTLRErrorListener() {
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
|
||||
String msg, RecognitionException e) {
|
||||
Token token = (Token) offendingSymbol;
|
||||
int offset = token.getStartIndex();
|
||||
int length = token.getStopIndex() - token.getStartIndex() + 1;
|
||||
if (token.getStartIndex() >= token.getStopIndex()) {
|
||||
offset = token.getStartIndex() - token.getCharPositionInLine();
|
||||
length = token.getCharPositionInLine() + 1;
|
||||
}
|
||||
problemCollector.accept(new ReconcileProblemImpl(QueryProblemType.EXPRESSION_SYNTAX, msg, startPosition + offset, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction,
|
||||
ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex,
|
||||
BitSet conflictingAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact,
|
||||
BitSet ambigAlts, ATNConfigSet configs) {
|
||||
}
|
||||
});
|
||||
|
||||
parser.ql_statement();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ConsoleErrorListener;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ErrorNode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
@@ -49,6 +50,8 @@ public class HqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
|
||||
Map<Token, String> semantics = new HashMap<>();
|
||||
|
||||
parser.removeErrorListener(ConsoleErrorListener.INSTANCE);
|
||||
|
||||
parser.addParseListener(new HqlBaseListener() {
|
||||
|
||||
private void processTerminalNode(TerminalNode node) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -19,14 +20,13 @@ import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.TextBlock;
|
||||
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
@@ -69,44 +69,61 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation a) {
|
||||
if (isQueryAnnotation(a)) {
|
||||
Expression queryValueNode = ((List<?>) a.values()).stream()
|
||||
.filter(MemberValuePair.class::isInstance)
|
||||
.map(MemberValuePair.class::cast)
|
||||
.filter(p -> "value".equals(p.getName().getIdentifier()))
|
||||
.findFirst().map(p -> p.getValue())
|
||||
.get();
|
||||
if (queryValueNode instanceof StringLiteral) {
|
||||
IAnnotationBinding annotationBinding = a.resolveAnnotationBinding();
|
||||
String query = getJpaQuery(annotationBinding);
|
||||
if (query != null && !query.isBlank()) {
|
||||
int valueOffset = queryValueNode.getStartPosition() + 1;
|
||||
tokensData.addAll(provider.computeTokens(query, valueOffset));
|
||||
List<?> values = a.values();
|
||||
|
||||
Expression queryExpression = null;
|
||||
boolean isNative = false;
|
||||
for (Object value : values) {
|
||||
if (value instanceof MemberValuePair) {
|
||||
MemberValuePair pair = (MemberValuePair) value;
|
||||
String name = pair.getName().getFullyQualifiedName();
|
||||
if (name != null) {
|
||||
switch (name) {
|
||||
case "value":
|
||||
queryExpression = pair.getValue();
|
||||
break;
|
||||
case "nativeQuery":
|
||||
Expression expression = pair.getValue();
|
||||
if (expression != null) {
|
||||
Object o = expression.resolveConstantExpressionValue();
|
||||
if (o instanceof Boolean b) {
|
||||
isNative = b.booleanValue();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (queryExpression != null) {
|
||||
if (isNative) {
|
||||
//TODO: SQL semantic tokens
|
||||
} else {
|
||||
tokensData.addAll(computeTokensForQueryExpression(provider, queryExpression));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation a) {
|
||||
if (isQueryAnnotation(a) && a.getValue() instanceof StringLiteral) {
|
||||
IAnnotationBinding annotationBinding = a.resolveAnnotationBinding();
|
||||
String query = getJpaQuery(annotationBinding);
|
||||
if (query != null && !query.isBlank()) {
|
||||
int valueOffset = a.getValue().getStartPosition() + 1;
|
||||
tokensData.addAll(provider.computeTokens(query, valueOffset));
|
||||
}
|
||||
if (isQueryAnnotation(a)) {
|
||||
tokensData.addAll(computeTokensForQueryExpression(provider, a.getValue()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if ("createQuery".equals(node.getName().getIdentifier()) && node.arguments().size() <= 2 && node.arguments().get(0) instanceof StringLiteral queryExpr) {
|
||||
if ("createQuery".equals(node.getName().getIdentifier()) && node.arguments().size() <= 2 && node.arguments().get(0) instanceof Expression queryExpr) {
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
if ("jakarta.persistence.EntityManager".equals(methodBinding.getDeclaringClass().getQualifiedName())) {
|
||||
if (methodBinding.getParameterTypes().length <= 2 && "java.lang.String".equals(methodBinding.getParameterTypes()[0].getQualifiedName())) {
|
||||
tokensData.addAll(provider.computeTokens(queryExpr.getLiteralValue(), queryExpr.getStartPosition() + 1));
|
||||
tokensData.addAll(computeTokensForQueryExpression(provider, queryExpr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,33 +135,31 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
|
||||
return tokensData;
|
||||
}
|
||||
|
||||
private static List<SemanticTokenData> computeTokensForQueryExpression(SemanticTokensDataProvider provider, Expression valueExp) {
|
||||
String query = null;
|
||||
int offset = 0;
|
||||
if (valueExp instanceof StringLiteral sl) {
|
||||
query = sl.getEscapedValue();
|
||||
query = query.substring(1, query.length() - 1);
|
||||
offset = sl.getStartPosition() + 1; // +1 to skip over opening "
|
||||
} else if (valueExp instanceof TextBlock tb) {
|
||||
query = tb.getEscapedValue();
|
||||
query = query.substring(3, query.length() - 3);
|
||||
offset = tb.getStartPosition() + 3; // +3 to skip over opening """
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
return provider.computeTokens(query, offset);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
private static boolean isQueryAnnotation(Annotation a) {
|
||||
return FQN_QUERY.equals(a.getTypeName().getFullyQualifiedName())
|
||||
|| QUERY.equals(a.getTypeName().getFullyQualifiedName());
|
||||
}
|
||||
|
||||
private static String getJpaQuery(IAnnotationBinding annotationBinding) {
|
||||
if (annotationBinding != null && annotationBinding.getAnnotationType() != null) {
|
||||
if (FQN_QUERY.equals(annotationBinding.getAnnotationType().getQualifiedName())) {
|
||||
String query = null;
|
||||
boolean isNative = false;
|
||||
for (IMemberValuePairBinding pair : annotationBinding.getAllMemberValuePairs()) {
|
||||
switch (pair.getName()) {
|
||||
case "value":
|
||||
query = (String) pair.getValue();
|
||||
break;
|
||||
case "nativeQuery":
|
||||
isNative = (Boolean) pair.getValue();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return isNative ? null : query;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return supportState.isEnabled() && SpringProjectUtil.hasDependencyStartingWith(project, "spring-data-jpa", null);
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
@@ -22,10 +23,12 @@ import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
public class JpaQueryPropertiesLanguageServerComponents implements LanguageServerComponents {
|
||||
|
||||
private final QueryPropertiesSemanticTokensHandler semanticTokensHandler;
|
||||
private final NamedQueryPropertiesReconcileEngine reconcileEngine;
|
||||
|
||||
public JpaQueryPropertiesLanguageServerComponents(SimpleTextDocumentService documents, JavaProjectFinder projectsFinder,
|
||||
JpqlSemanticTokens jpqlSemanticTokensProvider, HqlSemanticTokens hqlSematicTokensProvider, JpqlSupportState supportState) {
|
||||
this.semanticTokensHandler = new QueryPropertiesSemanticTokensHandler(documents, projectsFinder, jpqlSemanticTokensProvider, hqlSematicTokensProvider, supportState);
|
||||
this.reconcileEngine = new NamedQueryPropertiesReconcileEngine(projectsFinder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,4 +41,9 @@ public class JpaQueryPropertiesLanguageServerComponents implements LanguageServe
|
||||
return Optional.of(semanticTokensHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<IReconcileEngine> getReconcileEngine() {
|
||||
return Optional.of(reconcileEngine);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.antlr.v4.runtime.ANTLRErrorListener;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ConsoleErrorListener;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.atn.ATNConfigSet;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
import org.springframework.ide.vscode.parser.jpql.JpqlLexer;
|
||||
import org.springframework.ide.vscode.parser.jpql.JpqlParser;
|
||||
|
||||
public class JpqlReconciler implements Reconciler {
|
||||
|
||||
@Override
|
||||
public void reconcile(String text, int startPosition, IProblemCollector problemCollector) {
|
||||
JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(text));
|
||||
CommonTokenStream antlrTokens = new CommonTokenStream(lexer);
|
||||
JpqlParser parser = new JpqlParser(antlrTokens);
|
||||
|
||||
parser.removeErrorListener(ConsoleErrorListener.INSTANCE);
|
||||
|
||||
parser.addErrorListener(new ANTLRErrorListener() {
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
|
||||
String msg, RecognitionException e) {
|
||||
Token token = (Token) offendingSymbol;
|
||||
int offset = token.getStartIndex();
|
||||
int length = token.getStopIndex() - token.getStartIndex() + 1;
|
||||
if (token.getStartIndex() >= token.getStopIndex()) {
|
||||
offset = token.getStartIndex() - token.getCharPositionInLine();
|
||||
length = token.getCharPositionInLine() + 1;
|
||||
}
|
||||
problemCollector.accept(new ReconcileProblemImpl(QueryProblemType.EXPRESSION_SYNTAX, msg, startPosition + offset, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction,
|
||||
ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex,
|
||||
BitSet conflictingAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact,
|
||||
BitSet ambigAlts, ATNConfigSet configs) {
|
||||
}
|
||||
});
|
||||
|
||||
parser.ql_statement();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.ConsoleErrorListener;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ErrorNode;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
@@ -51,6 +52,8 @@ public class JpqlSemanticTokens implements SemanticTokensDataProvider {
|
||||
JpqlParser parser = new JpqlParser(antlrTokens);
|
||||
Map<Token, String> semantics = new HashMap<>();
|
||||
|
||||
parser.removeErrorListener(ConsoleErrorListener.INSTANCE);
|
||||
|
||||
parser.addParseListener(new JpqlBaseListener() {
|
||||
|
||||
private void processTerminalNode(TerminalNode node) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.java.properties.antlr.parser.AntlrParser;
|
||||
import org.springframework.ide.vscode.java.properties.parser.ParseResults;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.KeyValuePair;
|
||||
import org.springframework.ide.vscode.java.properties.parser.PropertiesAst.Value;
|
||||
|
||||
public class NamedQueryPropertiesReconcileEngine implements IReconcileEngine {
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
|
||||
public NamedQueryPropertiesReconcileEngine(JavaProjectFinder projectFinder) {
|
||||
this.projectFinder = projectFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(IDocument doc, IProblemCollector problemCollector) {
|
||||
try {
|
||||
Reconciler reconciler = projectFinder.find(new TextDocumentIdentifier(doc.getUri()))
|
||||
.map(p -> SpringProjectUtil.hasDependencyStartingWith(p, "spring-data-jpa", null) ? new HqlReconciler() : new JpqlReconciler())
|
||||
.orElse(new JpqlReconciler());
|
||||
|
||||
AntlrParser parser = new AntlrParser();
|
||||
ParseResults parseResults = parser.parse(doc.get());
|
||||
for (KeyValuePair pair : parseResults.ast.getNodes(KeyValuePair.class)) {
|
||||
Value value = pair.getValue();
|
||||
reconciler.reconcile(value.decode(), value.getOffset(), problemCollector);
|
||||
}
|
||||
} finally {
|
||||
problemCollector.endCollecting();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.TextBlock;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
|
||||
public class QueryJdtAstReconciler implements JdtAstReconciler {
|
||||
|
||||
@Override
|
||||
public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) throws RequiredCompleteAstException {
|
||||
return new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
|
||||
Set<String> allAnnotations = AnnotationHierarchies.getTransitiveSuperAnnotations(node.resolveTypeBinding());
|
||||
if (!allAnnotations.contains(Annotations.DATA_QUERY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<?> values = node.values();
|
||||
|
||||
Expression queryExpression = null;
|
||||
boolean isNative = false;
|
||||
for (Object value : values) {
|
||||
if (value instanceof MemberValuePair) {
|
||||
MemberValuePair pair = (MemberValuePair) value;
|
||||
String name = pair.getName().getFullyQualifiedName();
|
||||
if (name != null) {
|
||||
switch (name) {
|
||||
case "value":
|
||||
queryExpression = pair.getValue();
|
||||
break;
|
||||
case "nativeQuery":
|
||||
Expression expression = pair.getValue();
|
||||
if (expression != null) {
|
||||
Object o = expression.resolveConstantExpressionValue();
|
||||
if (o instanceof Boolean b) {
|
||||
isNative = b.booleanValue();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (queryExpression != null) {
|
||||
if (isNative) {
|
||||
//TODO: SQL syntax validation
|
||||
} else {
|
||||
reconcileExpression(getQueryReconciler(project), queryExpression, problemCollector);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
Set<String> allAnnotations = AnnotationHierarchies.getTransitiveSuperAnnotations(node.resolveTypeBinding());
|
||||
if (!allAnnotations.contains(Annotations.DATA_QUERY)) {
|
||||
return false;
|
||||
}
|
||||
reconcileExpression(getQueryReconciler(project), node.getValue(), problemCollector);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if ("createQuery".equals(node.getName().getIdentifier()) && node.arguments().size() <= 2 && node.arguments().get(0) instanceof Expression queryExpr) {
|
||||
IMethodBinding methodBinding = node.resolveMethodBinding();
|
||||
if ("jakarta.persistence.EntityManager".equals(methodBinding.getDeclaringClass().getQualifiedName())) {
|
||||
if (methodBinding.getParameterTypes().length <= 2 && "java.lang.String".equals(methodBinding.getParameterTypes()[0].getQualifiedName())) {
|
||||
reconcileExpression(getQueryReconciler(project), queryExpr, problemCollector);
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets either HQL or JPQL reconciler
|
||||
*/
|
||||
private static Reconciler getQueryReconciler(IJavaProject project) {
|
||||
return SpringProjectUtil.hasDependencyStartingWith(project, "hibernate-core", null) ? new HqlReconciler() : new JpqlReconciler();
|
||||
}
|
||||
|
||||
private void reconcileExpression(Reconciler reconciler, Expression valueExp, IProblemCollector problemCollector) {
|
||||
String query = null;
|
||||
int offset = 0;
|
||||
if (valueExp instanceof StringLiteral sl) {
|
||||
query = sl.getEscapedValue();
|
||||
query = query.substring(1, query.length() - 1);
|
||||
offset = sl.getStartPosition() + 1; // +1 to skip over opening "
|
||||
} else if (valueExp instanceof TextBlock tb) {
|
||||
query = tb.getEscapedValue();
|
||||
query = query.substring(3, query.length() - 3);
|
||||
offset = tb.getStartPosition() + 3; // +3 to skip over opening """
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
reconciler.reconcile(query, offset, problemCollector);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return SpringProjectUtil.hasDependencyStartingWith(project, "spring-data-jpa", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemType getProblemType() {
|
||||
return QueryProblemType.EXPRESSION_SYNTAX;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.ERROR;
|
||||
|
||||
import org.springframework.ide.vscode.boot.common.SpringProblemCategories;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemCategory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
|
||||
public enum QueryProblemType implements ProblemType {
|
||||
|
||||
EXPRESSION_SYNTAX(ERROR, "Syntax", "Query Expression Syntax");
|
||||
|
||||
private final ProblemSeverity defaultSeverity;
|
||||
private String description;
|
||||
private String label;
|
||||
|
||||
private QueryProblemType(ProblemSeverity defaultSeverity, String description) {
|
||||
this(defaultSeverity, description, null);
|
||||
}
|
||||
|
||||
private QueryProblemType(ProblemSeverity defaultSeverity, String description, String label) {
|
||||
this.description = description;
|
||||
this.defaultSeverity = defaultSeverity;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemSeverity getDefaultSeverity() {
|
||||
return defaultSeverity;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
if (label==null) {
|
||||
label = createDefaultLabel();
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
private String createDefaultLabel() {
|
||||
String label = this.toString().substring(5).toLowerCase().replace('_', ' ');
|
||||
return Character.toUpperCase(label.charAt(0)) + label.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemCategory getCategory() {
|
||||
return SpringProblemCategories.JPQL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class AnnotationNodeReconciler implements JdtAstReconciler {
|
||||
new AnnotationParamReconciler(SPRING_POST_FILTER, "value", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler)
|
||||
|
||||
};
|
||||
config.addListener(evt -> this.spelExpressionReconciler.setEnabled(config.isSpelExpressionValidationEnabled()));
|
||||
|
||||
@@ -91,6 +91,54 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleMemberAnnotationWithTextBlock() throws Exception {
|
||||
String source = """
|
||||
package my.package
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface OwnerRepository {
|
||||
|
||||
@Query(\"""
|
||||
SELECT DISTINCT owner FROM Owner owner
|
||||
\""")
|
||||
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 = provider.computeTokens(jp, cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(125, 131, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("SELECT");
|
||||
|
||||
token = tokens.get(1);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(132, 140, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("DISTINCT");
|
||||
|
||||
token = tokens.get(2);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(141, 146, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
|
||||
token = tokens.get(3);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(147, 151, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("FROM");
|
||||
|
||||
token = tokens.get(4);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(152, 157, "class", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("Owner");
|
||||
|
||||
token = tokens.get(5);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(158, 163, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalAnnotation() throws Exception {
|
||||
String source = """
|
||||
@@ -184,6 +232,55 @@ public class JdtDataQuerySemanticTokensProviderTest {
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createQueryMethodWithTextBlock() throws Exception {
|
||||
String source = """
|
||||
package my.package
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
public interface OwnerRepository {
|
||||
|
||||
default void findByLastName(EntityManager manager) {
|
||||
manager.createQuery(\"""
|
||||
SELECT DISTINCT owner FROM Owner owner
|
||||
\""")
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
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 = provider.computeTokens(jp, cu);
|
||||
|
||||
SemanticTokenData token = tokens.get(0);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(181, 187, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("SELECT");
|
||||
|
||||
token = tokens.get(1);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(188, 196, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("DISTINCT");
|
||||
|
||||
token = tokens.get(2);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(197, 202, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
|
||||
token = tokens.get(3);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(203, 207, "keyword", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("FROM");
|
||||
|
||||
token = tokens.get(4);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(208, 213, "class", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("Owner");
|
||||
|
||||
token = tokens.get(5);
|
||||
assertThat(token).isEqualTo(new SemanticTokenData(214, 219, "variable", new String[0]));
|
||||
assertThat(source.substring(token.start(), token.end())).isEqualTo("owner");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeQuery() throws Exception {
|
||||
String source = """
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.jpa.queries;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class QueryReconcilerTest {
|
||||
|
||||
@Autowired
|
||||
private BootLanguageServerHarness harness;
|
||||
@Autowired
|
||||
private JavaProjectFinder projectFinder;
|
||||
|
||||
private File directory;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/spring-modulith-example-full/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void noErrorsInPropsFile() throws Exception {
|
||||
String source = """
|
||||
query1=SELECT ptype FROM PetType ptype ORDER BY ptype.name
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/resources/jpa-named-queries.properties").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JPA_QUERY_PROPERTIES, source, docUri);
|
||||
editor.assertProblems();
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorsInPropsFile() throws Exception {
|
||||
String source = """
|
||||
query1=SELECTX ptype FROM PetType ptype ORDER BY ptype.name
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/resources/jpa-named-queries.properties").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JPA_QUERY_PROPERTIES, source, docUri);
|
||||
editor.assertProblems("SELECTX|mismatched input 'SELECTX'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noErrorsForHqlInPropsFile() throws Exception {
|
||||
String source = """
|
||||
query1=SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/resources/jpa-named-queries.properties").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JPA_QUERY_PROPERTIES, source, docUri);
|
||||
editor.assertProblems();
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorsForJpqlInPropsFile() throws Exception {
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/super-property-nav-sample/").toURI());
|
||||
String projectDir = directory.toURI().toString();
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
|
||||
String source = """
|
||||
query1=SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName%
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/resources/jpa-named-queries.properties").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JPA_QUERY_PROPERTIES, source, docUri);
|
||||
editor.assertProblems("WHERE|no viable alternative");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noErrors() throws Exception {
|
||||
String source = """
|
||||
package example.demo;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface OwnerRepository extends Repository<Object, Integer> {
|
||||
|
||||
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
|
||||
List<Object> findPetTypes();
|
||||
|
||||
}
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/java/example/demo/OwnerRepository.java").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
|
||||
editor.assertProblems();
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorReported() throws Exception {
|
||||
String source = """
|
||||
package example.demo;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface OwnerRepository extends Repository<Object, Integer> {
|
||||
|
||||
@Query("SELECTX ptype FROM PetType ptype ORDER BY ptype.name")
|
||||
List<Object> findPetTypes();
|
||||
|
||||
}
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/java/example/demo/OwnerRepository.java").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
|
||||
editor.assertProblems("SELECTX|mismatched input 'SELECTX'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void textBlock() throws Exception {
|
||||
String source = """
|
||||
package example.demo;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface OwnerRepository extends Repository<Object, Integer> {
|
||||
|
||||
@Query(\"""
|
||||
SELECTX ptype FROM PetType ptype ORDER BY ptype.name
|
||||
\""")
|
||||
List<Object> findPetTypes();
|
||||
|
||||
}
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/java/example/demo/OwnerRepository.java").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
|
||||
editor.assertProblems("SELECTX|mismatched input 'SELECTX'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalAnnotation() throws Exception {
|
||||
String source = """
|
||||
package example.demo;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface OwnerRepository extends Repository<Object, Integer> {
|
||||
|
||||
@Query(value = "SELECTX ptype FROM PetType ptype ORDER BY ptype.name", nativeQuery = false)
|
||||
List<Object> findPetTypes();
|
||||
|
||||
}
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/java/example/demo/OwnerRepository.java").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
|
||||
editor.assertProblems("SELECTX|mismatched input 'SELECTX'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeSqlAnnotation() throws Exception {
|
||||
String source = """
|
||||
package example.demo;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface OwnerRepository extends Repository<Object, Integer> {
|
||||
|
||||
@Query(value = "SELECTX ptype FROM PetType ptype ORDER BY ptype.name", nativeQuery = true)
|
||||
List<Object> findPetTypes();
|
||||
|
||||
}
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/java/example/demo/OwnerRepository.java").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
|
||||
editor.assertProblems();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noErrorForHql() throws Exception {
|
||||
String source = """
|
||||
package example.demo;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface OwnerRepository extends Repository<Object, Integer> {
|
||||
|
||||
@Query("SELECT DISTINCT owner FROM Owner owner left join owner.pets WHERE owner.lastName LIKE :lastName% ")
|
||||
Page<Object> findByLastName(@Param("lastName") String lastName, Pageable pageable);
|
||||
|
||||
}
|
||||
""";
|
||||
String docUri = directory.toPath().resolve("src/main/java/example/demo/OwnerRepository.java").toUri()
|
||||
.toString();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, source, docUri);
|
||||
editor.assertProblems();
|
||||
}
|
||||
}
|
||||
@@ -1188,4 +1188,4 @@
|
||||
"extensionDependencies": [
|
||||
"redhat.java"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user