Skip to content

Commit

Permalink
[master] Jakarta Persistence 3.2 new feature - JPQL functions REPLACE…
Browse files Browse the repository at this point in the history
…() (#1924)

* Jakarta Persistence 3.2 new feature - JPQL functions REPLACE()
  • Loading branch information
rfelcman committed Aug 18, 2023
1 parent 1b96153 commit a45b1ba
Show file tree
Hide file tree
Showing 37 changed files with 984 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
import org.eclipse.persistence.jpa.jpql.parser.ReplaceExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
Expand Down Expand Up @@ -1756,6 +1757,28 @@ public void visit(RegexpExpression expression) {
type[0] = Boolean.class;
}

@Override
public void visit(ReplaceExpression expression) {

// Create the first expression
expression.getFirstExpression().accept(this);
Expression firstExpression = queryExpression;

// Create the second expression
expression.getSecondExpression().accept(this);
Expression secondExpression = queryExpression;

// Create the third expression
expression.getThirdExpression().accept(this);
Expression thirdExpression = queryExpression;

// Now create the REPLACE expression
queryExpression = firstExpression.replace(secondExpression, thirdExpression);

// Set the expression type
type[0] = String.class;
}

@Override
public void visit(ResultVariable expression) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression;
import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral;
import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression;
import org.eclipse.persistence.jpa.jpql.parser.ReplaceExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause;
Expand Down Expand Up @@ -491,6 +492,12 @@ protected void visit(org.eclipse.persistence.jpa.jpql.parser.Expression expressi
addAttribute(ExpressionTools.EMPTY_STRING, queryExpression);
}

@Override
public void visit(ReplaceExpression expression) {
Expression queryExpression = queryContext.buildExpression(expression, type);
addAttribute(ExpressionTools.EMPTY_STRING, queryExpression, type[0]);
}

@Override
public void visit(ResultVariable expression) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
import org.eclipse.persistence.jpa.jpql.parser.ReplaceExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
Expand Down Expand Up @@ -1070,6 +1071,11 @@ public void visit(RegexpExpression expression) {
type = Boolean.class;
}

@Override
public void visit(ReplaceExpression expression) {
type = String.class;
}

@Override
public void visit(ResultVariable expression) {
expression.getSelectExpression().accept(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.TypedQuery;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
import org.eclipse.persistence.jpa.test.query.model.StringEntity;
import org.eclipse.persistence.jpa.test.framework.DDLGen;
import org.eclipse.persistence.jpa.test.framework.Emf;
import org.eclipse.persistence.jpa.test.framework.EmfRunner;
import org.eclipse.persistence.jpa.test.framework.Property;
import org.eclipse.persistence.sessions.Session;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
Expand Down Expand Up @@ -211,4 +213,129 @@ public void testString_concatOperatorWhere02() {
}
}
}

@Test
public void testString_replaceFunctionSelect01() {
if (emf == null)
return;

//REPLACE is not implemented in Apache Derby DB
if (emf.unwrap(Session.class).getPlatform().isDerby()) {
return;
}
EntityManager em = emf.createEntityManager();

try {
TypedQuery<String> query = em.createQuery("SELECT REPLACE('John', 'o', 'a') FROM StringEntity s WHERE s.id = 1", String.class);
String result = query.getSingleResult();
Assert.assertEquals("Jahn", result);
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if(em.isOpen()) {
em.close();
}
}
}

@Test
public void testString_replaceFunctionSelect02() {
if (emf == null)
return;

//REPLACE is not implemented in Apache Derby DB
if (emf.unwrap(Session.class).getPlatform().isDerby()) {
return;
}
EntityManager em = emf.createEntityManager();

try {
TypedQuery<String> query = em.createQuery("SELECT REPLACE(s.firstName, 'o', 'a') FROM StringEntity s WHERE s.id = 1", String.class);
String result = query.getSingleResult();
Assert.assertEquals("Jahn", result);
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if(em.isOpen()) {
em.close();
}
}
}

@Test
public void testString_replaceFunctionSelect03() {
if (emf == null)
return;

//REPLACE is not implemented in Apache Derby DB
if (emf.unwrap(Session.class).getPlatform().isDerby()) {
return;
}
EntityManager em = emf.createEntityManager();

try {
TypedQuery<String> query = em.createQuery("SELECT REPLACE(REPLACE(s.firstName, 'o', 'a'), 'a', 'o') FROM StringEntity s WHERE s.id = 1", String.class);
String result = query.getSingleResult();
Assert.assertEquals("John", result);
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if(em.isOpen()) {
em.close();
}
}
}

@Test
public void testString_replaceFunctionWhere01() {
if (emf == null)
return;

//REPLACE is not implemented in Apache Derby DB
if (emf.unwrap(Session.class).getPlatform().isDerby()) {
return;
}
EntityManager em = emf.createEntityManager();

try {
TypedQuery<String> query = em.createQuery("SELECT s.firstName FROM StringEntity s WHERE REPLACE(s.firstName, 'o', 'a') = 'Jahn'", String.class);
String result = query.getSingleResult();
Assert.assertEquals("John", result);
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if(em.isOpen()) {
em.close();
}
}
}

@Test
public void testString_replaceFunctionWhere02() {
if (emf == null)
return;

//REPLACE is not implemented in Apache Derby DB
if (emf.unwrap(Session.class).getPlatform().isDerby()) {
return;
}
EntityManager em = emf.createEntityManager();

try {
TypedQuery<String> query = em.createQuery("SELECT s.firstName FROM StringEntity s WHERE REPLACE(REPLACE(s.firstName, 'o', 'a'), 'a', 'o') = 'John'", String.class);
String result = query.getSingleResult();
Assert.assertEquals("John", result);
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if(em.isOpen()) {
em.close();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.RangeDeclarationBNF;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.ReplaceExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
Expand Down Expand Up @@ -831,6 +832,55 @@ public String rightParenthesisMissingKey(ObjectExpression expression) {
};
}

protected AbstractTripleEncapsulatedExpressionHelper<ReplaceExpression> buildReplaceExpressionHelper() {
return new AbstractTripleEncapsulatedExpressionHelper<ReplaceExpression>(this) {
@Override
protected String firstCommaMissingKey() {
return ReplaceExpression_MissingFirstComma;
}
@Override
protected String firstExpressionInvalidKey() {
return ReplaceExpression_InvalidFirstExpression;
}
@Override
protected String firstExpressionMissingKey() {
return ReplaceExpression_MissingFirstExpression;
}
@Override
public String identifier(ReplaceExpression expression) {
return REPLACE;
}
@Override
public String leftParenthesisMissingKey(ReplaceExpression expression) {
return ReplaceExpression_MissingLeftParenthesis;
}
@Override
public String rightParenthesisMissingKey(ReplaceExpression expression) {
return ReplaceExpression_MissingRightParenthesis;
}
@Override
protected String secondCommaMissingKey() {
return ReplaceExpression_MissingSecondComma;
}
@Override
protected String secondExpressionInvalidKey() {
return ReplaceExpression_InvalidSecondExpression;
}
@Override
protected String secondExpressionMissingKey() {
return ReplaceExpression_MissingSecondExpression;
}
@Override
protected String thirdExpressionInvalidKey() {
return ReplaceExpression_InvalidThirdExpression;
}
@Override
protected String thirdExpressionMissingKey() {
return ReplaceExpression_MissingThirdExpression;
}
};
}

protected AbstractSingleEncapsulatedExpressionHelper<SizeExpression> buildSizeExpressionHelper() {
return new AbstractSingleEncapsulatedExpressionHelper<SizeExpression>(this) {
@Override
Expand Down Expand Up @@ -1586,6 +1636,15 @@ protected void registerHelper(String id, Object helper) {
helpers.put(id, helper);
}

protected AbstractTripleEncapsulatedExpressionHelper<ReplaceExpression> replaceExpressionHelper() {
AbstractTripleEncapsulatedExpressionHelper<ReplaceExpression> helper = getHelper(REPLACE);
if (helper == null) {
helper = buildReplaceExpressionHelper();
registerHelper(REPLACE, helper);
}
return helper;
}

protected AbstractSingleEncapsulatedExpressionHelper<SizeExpression> sizeExpressionHelper() {
AbstractSingleEncapsulatedExpressionHelper<SizeExpression> helper = getHelper(SIZE);
if (helper == null) {
Expand Down Expand Up @@ -3954,6 +4013,11 @@ public void visit(RangeVariableDeclaration expression) {
}
}

@Override
public void visit(ReplaceExpression expression) {
validateAbstractTripleEncapsulatedExpression(expression, replaceExpressionHelper());
}

@Override
public void visit(ResultVariable expression) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
Expand Down Expand Up @@ -91,6 +91,7 @@
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.ReplaceExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
Expand Down Expand Up @@ -2068,6 +2069,50 @@ protected void validateRangeVariableDeclarationRootObject(RangeVariableDeclarati
expression.getRootObject().accept(this);
}


/**
* Validates the encapsulated expression of the given <code><b>REPLACEMENT</b></code> expression.
* The test to perform is:
* <ul>
* <li>If the encapsulated expression is a path expression, validation makes sure it is a basic
* mapping, an association field is not allowed.</li>
* <li>If the encapsulated expression is not a path expression, validation will be redirected to
* that expression but the returned status will not be changed.</li>
* </ul>
*
* @param expression The {@link ReplaceExpression} to validate by validating its encapsulated expression
* @return A number indicating the validation result. {@link #isValid(int, int)} can be used to
* determine the validation status of an expression based on its position
*/
protected int validateReplaceExpression(ReplaceExpression expression) {

int result = 0;

// Validate the first expression
if (expression.hasFirstExpression()) {
Expression firstExpression = expression.getFirstExpression();

// Special case for state field path expression, association field is not allowed
StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression);

if (pathExpression != null) {
boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY);
updateStatus(result, 0, valid);
}
else {
firstExpression.accept(this);
}
}

// Validate the second expression
expression.getSecondExpression().accept(this);

// Validate the third expression
expression.getThirdExpression().accept(this);

return result;
}

/**
* Validates the given {@link ResultVariable}. The default behavior does not require to
* semantically validate it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@ public interface JPQLQueryProblemMessages {
String RegexpExpression_InvalidStringExpression = "REGEXP_EXPRESSION_INVALID_STRING_EXPRESSION";
String RegexpExpression_MissingPatternValue = "REGEXP_EXPRESSION_MISSING_PATTERN_VALUE";
String RegexpExpression_MissingStringExpression = "REGEXP_EXPRESSION_MISSING_STRING_EXPRESSION";
String ReplaceExpression_FirstExpression_WrongType = "REPLACE_EXPRESSION_FIRST_EXPRESSION_WRONG_TYPE";
String ReplaceExpression_InvalidFirstExpression = "REPLACE_EXPRESSION_INVALID_FIRST_EXPRESSION";
String ReplaceExpression_InvalidSecondExpression = "REPLACE_EXPRESSION_INVALID_SECOND_EXPRESSION";
String ReplaceExpression_InvalidThirdExpression = "REPLACE_EXPRESSION_INVALID_THIRD_EXPRESSION";
String ReplaceExpression_MissingFirstComma = "REPLACE_EXPRESSION_MISSING_FIRST_COMMA";
String ReplaceExpression_MissingFirstExpression = "REPLACE_EXPRESSION_MISSING_FIRST_EXPRESSION";
String ReplaceExpression_MissingLeftParenthesis = "REPLACE_EXPRESSION_MISSING_LEFT_PARENTHESIS";
String ReplaceExpression_MissingRightParenthesis = "REPLACE_EXPRESSION_MISSING_RIGHT_PARENTHESIS";
String ReplaceExpression_MissingSecondComma = "REPLACE_EXPRESSION_MISSING_SECOND_COMMA";
String ReplaceExpression_MissingSecondExpression = "REPLACE_EXPRESSION_MISSING_SECOND_EXPRESSION";
String ReplaceExpression_MissingThirdExpression = "REPLACE_EXPRESSION_MISSING_THIRD_EXPRESSION";
String ReplaceExpression_SecondExpression_WrongType = "REPLACE_EXPRESSION_SECOND_EXPRESSION_WRONG_TYPE";
String ReplaceExpression_ThirdExpression_WrongType = "REPLACE_EXPRESSION_THIRD_EXPRESSION_WRONG_TYPE";
String ResultVariable_InvalidJPAVersion = "RESULT_VARIABLE_INVALID_JPA_VERSION";
String ResultVariable_MissingResultVariable = "RESULT_VARIABLE_MISSING_RESULT_VARIABLE";
String ResultVariable_MissingSelectExpression = "RESULT_VARIABLE_MISSING_SELECT_EXPRESSION";
Expand Down

0 comments on commit a45b1ba

Please sign in to comment.