Skip to content

Commit

Permalink
[#421] Add test and fix for use of OUTER and macros in correlation pa…
Browse files Browse the repository at this point in the history
…th expression. Fixes #421
  • Loading branch information
beikov committed Jul 20, 2018
1 parent f10c566 commit 376b967
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Not yet released
* Introduced `EMBEDDING_VIEW` function as proper replacement for many `OUTER` function uses in entity views
* Smoothen support for embeddables in updatable entity views
* Improve performance by omitting null precedence emulation on MySQL for the default null precedence
* Allow use of `OUTER` and other macros in subquery correlation join path

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import com.blazebit.persistence.parser.predicate.Predicate;
import com.blazebit.persistence.parser.predicate.PredicateBuilder;
import com.blazebit.persistence.impl.transform.ExpressionModifierVisitor;
import com.blazebit.persistence.parser.util.ExpressionUtils;
import com.blazebit.persistence.parser.util.JpaMetamodelUtils;
import com.blazebit.persistence.impl.util.SqlUtils;
import com.blazebit.persistence.spi.DbmsDialect;
Expand Down Expand Up @@ -626,6 +627,13 @@ String addRoot(String correlationPath, String rootAlias) {
} else {
throw new IllegalArgumentException("Unexpected expression type[" + expression.getClass().getSimpleName() + "] in treat expression: " + treatExpression);
}
} else if (expr instanceof FunctionExpression && ExpressionUtils.isOuterFunction((FunctionExpression) expr)) {
FunctionExpression outerFunctionExpr = (FunctionExpression) expr;
PathExpression pathExpression = (PathExpression) outerFunctionExpr.getExpressions().get(0);
List<PathElementExpression> pathElements = pathExpression.getExpressions();
elementExpr = pathElements.get(pathElements.size() - 1);
result = implicitJoin(parent.getRootNodeOrFail("Can't use OUTER when parent query has multiple roots!"), pathExpression, null, null, 0, pathElements.size() - 1, true);
correlationParent = result.baseNode;
} else {
throw new IllegalArgumentException("Correlation join path [" + correlationPath + "] is not a valid join path");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ parsePath : state_field_path_expression EOF
| single_element_path_expression EOF
;

parseJoinPath : join_association_path_expression EOF
parseJoinPath : join_association_path_expression EOF #NormalJoinPathExpression
| Outer_function '(' join_association_path_expression ')' EOF #OuterJoinPathExpression
| macroName=Identifier '(' (join_association_path_expression (',' simple_expression)*)? ')' EOF #MacroJoinPathExpression
;

join_association_path_expression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ public Expression visitOuter_expression(JPQLSelectExpressionParser.Outer_express

@Override
public Expression visitMacro_expression(Macro_expressionContext ctx) {
final String macroName = ctx.macroName.getText().toUpperCase();
return visitMacroExpression(macroName, ctx);
}

public Expression visitMacroExpression(String macroName, ParserRuleContext ctx) {
List<Expression> funcArgs = new ArrayList<Expression>(ctx.getChildCount());
// Special handling of empty invocation, the position 2 contains an empty child node
if (ctx.getChildCount() != 4 || !ctx.getChild(2).getText().isEmpty()) {
Expand All @@ -131,7 +136,6 @@ public Expression visitMacro_expression(Macro_expressionContext ctx) {
}
}

final String macroName = ctx.macroName.getText().toUpperCase();
MacroFunction macro = macros.get(macroName);
if (macro == null) {
throw new SyntaxErrorException("The macro '" + macroName + "' could not be found in the macro map!");
Expand Down Expand Up @@ -407,6 +411,17 @@ public Expression visitTreated_key_value_expression(Treated_key_value_expression
return new TreatExpression(ctx.key_value_expression().accept(this), ctx.subtype().getText());
}

@Override
public Expression visitOuterJoinPathExpression(JPQLSelectExpressionParser.OuterJoinPathExpressionContext ctx) {
return handleFunction(ctx.getStart().getText(), ctx);
}

@Override
public Expression visitMacroJoinPathExpression(JPQLSelectExpressionParser.MacroJoinPathExpressionContext ctx) {
final String macroName = ctx.macroName.getText().toUpperCase();
return visitMacroExpression(macroName, ctx);
}

@Override
public Expression visitSimpleJoinPathExpression(SimpleJoinPathExpressionContext ctx) {
PathExpression path = (PathExpression) ctx.simple_subpath().accept(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import javax.persistence.EntityManager;
import javax.persistence.Tuple;

import com.blazebit.persistence.spi.FunctionRenderContext;
import com.blazebit.persistence.spi.JpqlMacro;
import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus;
import com.blazebit.persistence.testsuite.tx.TxVoidWork;
import org.junit.Test;
Expand Down Expand Up @@ -250,6 +252,39 @@ public void testSubqueryCorrelatesSimple() {
crit.getResultList();
}

@Test
// Test for #421
public void testSubqueryCorrelatesOuter() {
CriteriaBuilder<Document> crit = cbf.create(em, Document.class, "d")
.where("owner").in()
.from("OUTER(people)")
.where("partnerDocument").eqExpression("d")
.end();
String expectedQuery = "SELECT d FROM Document d WHERE d.owner IN (SELECT person FROM d.people person WHERE person.partnerDocument = d)";
assertEquals(expectedQuery, crit.getQueryString());
crit.getResultList();
}

@Test
// Test for #421
public void testSubqueryCorrelatesMacro() {
CriteriaBuilder<Document> crit = cbf.create(em, Document.class, "d");
crit.registerMacro("test", new JpqlMacro() {
@Override
public void render(FunctionRenderContext context) {
context.addChunk("d.");
context.addArgument(0);
}
});
crit.where("owner").in()
.from("TEST(people)")
.where("partnerDocument").eqExpression("d")
.end();
String expectedQuery = "SELECT d FROM Document d WHERE d.owner IN (SELECT person FROM d.people person WHERE person.partnerDocument = d)";
assertEquals(expectedQuery, crit.getQueryString());
crit.getResultList();
}

@Test
// TODO: Report datanucleus issue
@Category({ NoDatanucleus.class })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate42;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate43;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate50;
import com.blazebit.persistence.testsuite.base.jpa.category.NoHibernate51;
import com.blazebit.persistence.testsuite.base.jpa.category.NoOpenJPA;
import com.blazebit.persistence.testsuite.entity.IntIdEntity;
import com.blazebit.persistence.testsuite.tx.TxVoidWork;
Expand Down Expand Up @@ -63,6 +64,9 @@
* @author Christian Beikov
* @since 1.3.0
*/
// NOTE: EclipseLink doesn't support many to one relations in embedded ids
// NOTE: Element collection fetching of non-roots only got fixed in Hibernate 5.2.3: https://hibernate.atlassian.net/browse/HHH-11140
@Category({ NoHibernate42.class, NoHibernate43.class, NoHibernate50.class, NoHibernate51.class, NoEclipselink.class })
public class EmbeddedFetchTest extends AbstractEntityViewTest {

protected EmbeddableTestEntity doc1;
Expand Down

0 comments on commit 376b967

Please sign in to comment.