Skip to content

Commit

Permalink
#366 - Implemented simple correlations, reworked metamodel building, …
Browse files Browse the repository at this point in the history
…updated how general correlations work
  • Loading branch information
beikov committed Mar 16, 2017
1 parent 3e12fa1 commit e625ec7
Show file tree
Hide file tree
Showing 139 changed files with 1,981 additions and 753 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Not yet released
* Entity View Spring integration now allows the use of `includeFilters` and `excludeFilters` on `@EnableEntityViews`
* Extended `SubqueryInitiator` by most of the `from()` variants
* Support enum and entity type literal like the JPA spec says
* Introduction of `@MappingCorrelatedSimple` for simple correlations
* Allow empty correlation result with `JOIN` fetch strategy

### Bug fixes

Expand Down Expand Up @@ -46,6 +48,8 @@ Not yet released
* Renamed showcase project artifacts to be consistent
* Removed special qualified literals for enums and entity types
* Removed the `QueryTransformer` SPI as it is not required anymore
* Changed the default correlation fetch strategy from `JOIN` to `SELECT`
* Changed `CorrelationProvider` and `CorrelationBuilder` to disallow specifying a custom alias

## 1.2.0-Alpha2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ public interface FromBuilder<X extends FromBuilder<X>> {
*/
public From getFrom(String alias);

/**
* Returns the from element for the given path, creating it if necessary.
*
* @param path The path to the from element
* @return The from element of this query
* @since 1.2.0
*/
public From getFromByPath(String path);

/**
* Like {@link FromBuilder#from(Class, String)} with the
* alias equivalent to the camel cased result of what {@link Class#getSimpleName()} of the entity class returns.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.blazebit.persistence.WhereOrBuilder;
import com.blazebit.persistence.impl.expression.Expression;
import com.blazebit.persistence.impl.expression.ExpressionFactory;
import com.blazebit.persistence.impl.expression.PathExpression;
import com.blazebit.persistence.impl.expression.SubqueryExpressionFactory;
import com.blazebit.persistence.impl.expression.VisitorAdapter;
import com.blazebit.persistence.impl.function.entity.ValuesEntity;
Expand Down Expand Up @@ -657,11 +658,11 @@ public Set<From> getRoots() {
return new LinkedHashSet<From>(joinManager.getRoots());
}

public From getRoot() {
public JoinNode getRoot() {
return joinManager.getRootNodeOrFail("This should never happen. Please report this error!");
}

public From getFrom(String alias) {
public JoinNode getFrom(String alias) {
AliasInfo info = aliasManager.getAliasInfo(alias);
if (info == null || !(info instanceof JoinAliasInfo)) {
return null;
Expand All @@ -670,6 +671,12 @@ public From getFrom(String alias) {
return ((JoinAliasInfo) info).getJoinNode();
}

public JoinNode getFromByPath(String path) {
PathExpression pathExpression = expressionFactory.createPathExpression(path);
joinManager.implicitJoin(pathExpression, true, null, null, false, false, true);
return (JoinNode) pathExpression.getBaseNode();
}

public boolean isEmpty() {
return joinManager.getRoots().isEmpty()
|| (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import com.blazebit.persistence.impl.expression.TemporalLiteral;
import com.blazebit.persistence.impl.expression.WhenClauseExpression;
import com.blazebit.persistence.impl.predicate.BooleanLiteral;
import com.blazebit.persistence.impl.transform.AliasReplacementVisitor;

import javax.persistence.Basic;
import javax.persistence.ElementCollection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.blazebit.persistence.impl.transform;
package com.blazebit.persistence.impl;

import com.blazebit.persistence.impl.expression.Expression;
import com.blazebit.persistence.impl.expression.InplaceModificationResultVisitorAdapter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2014 - 2017 Blazebit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.blazebit.persistence.impl;

import com.blazebit.persistence.impl.expression.PathElementExpression;
import com.blazebit.persistence.impl.expression.PathExpression;
import com.blazebit.persistence.impl.expression.PropertyExpression;

import java.util.List;

/**
*
* @author Christian Beikov
* @since 1.2.0
*/
public class PrefixingAndAliasReplacementQueryGenerator extends SimpleQueryGenerator {

private final String prefix;
private final String substitute;
private final String alias;
private final String aliasToSkip;

public PrefixingAndAliasReplacementQueryGenerator(String prefix, String substitute, String alias) {
this(prefix, substitute, alias, null);
}

public PrefixingAndAliasReplacementQueryGenerator(String prefix, String substitute, String alias, String aliasToSkip) {
this.prefix = prefix;
this.substitute = substitute;
this.alias = alias;
this.aliasToSkip = aliasToSkip;
}

@Override
public void visit(PathExpression expression) {
List<PathElementExpression> expressions = expression.getExpressions();
int size = expressions.size();

if (size == 1) {
PathElementExpression elementExpression = expressions.get(0);
if (elementExpression instanceof PropertyExpression) {
if (alias.equals(((PropertyExpression) elementExpression).getProperty())) {
sb.append(substitute);
return;
}
}
} else if (aliasToSkip != null) {
PathElementExpression elementExpression = expressions.get(0);
if (elementExpression instanceof PropertyExpression) {
if (aliasToSkip.equals(((PropertyExpression) elementExpression).getProperty())) {
super.visit(expression);
return;
}
}
}
sb.append(prefix);
super.visit(expression);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ public static MacroConfiguration of(Map<String, MacroFunction> macros) {
return new MacroConfiguration(map);
}

public MacroFunction get(String name) {
return macros.get(name);
}

public MacroConfiguration with(Map<String, MacroFunction> newMacros) {
NavigableMap<String, MacroFunction> map = new TreeMap<String, MacroFunction>(this.macros);
for (Map.Entry<String, MacroFunction> entry : newMacros.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@
*/
public class GroupByTest extends AbstractCoreTest {

// NOTE: SQL Server complains that the properties of "owner_1" are not in the group by but datanucleus puts them there
// Remove when the issue is fixed: https://github.com/datanucleus/datanucleus-rdbms/issues/155
// Datanucleus does not support grouping by a byte[] as it seems
@Test
@Category({ NoDatanucleus.class })
public void testGroupByEntitySelect() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@
import javax.persistence.EntityManager;
import javax.persistence.Tuple;

import com.blazebit.persistence.testsuite.base.category.NoDatanucleus;
import com.blazebit.persistence.testsuite.tx.TxVoidWork;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import com.blazebit.persistence.CriteriaBuilder;
import com.blazebit.persistence.JoinType;
Expand Down Expand Up @@ -516,8 +514,6 @@ public void testSelectSuperExpressionSubquery(){
}

@Test
// NOTE: Datanucleus JPQL parser has a bug: https://github.com/datanucleus/datanucleus-core/issues/175
@Category({ NoDatanucleus.class })
public void testSelectMinimalTrimFunction(){
CriteriaBuilder<String> cb = cbf.create(em, String.class)
.from(Document.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,16 +744,17 @@ Apart from unmapped relations, there is sometimes the need to _correlate_ entiti
For these cases {projectname} entity views introduces the concept of _correlated mappings_. These mappings can be used to connect entities through a custom criteria instead of through mapped entity relations.
Correlated mappings can be used for any attribute type(basic, entity, subview, collection) although singular basic attributes can also be implemented as <<Subquery mappings,normal subqueries>>.

A correlation mapping is declared by annotating the desired attribute with `@MappingCorrelated`.
// and also with @MappingCorrelatedSimple
// ==== General correlated mappings
A correlation mapping is declared by annotating the desired attribute with `@MappingCorrelated` or `@MappingCorrelatedSimple`.

==== General correlated mappings

In order to map the correlation you need to specify some values

* `correlationBasis` - An expression that maps to the so called _correlation key_
* `correlator` - The `CorrelationProvider` to use for the correlation that introduces a so called _correlated entity_
* `correlationResult` - An expression that refers to the alias of the _correlated entity_ or an attribute of that entity

There is also the possibility to specify a <<anchor-fetch-strategies,_fetch strategy_>> that should be used for the correlation. By default, the `JOIN` strategy is used.
By default, the correlated entity type is _projected into_ the view. To map a specific property of the entity type, use the `correlationResult` attribute.
There is also the possibility to specify a <<anchor-fetch-strategies,_fetch strategy_>> that should be used for the correlation. By default, the `SELECT` strategy is used.

[source,java]
----
Expand All @@ -766,24 +767,25 @@ public interface CatView {
@MappingCorrelated(
correlationBasis = "age",
correlator = PersonAgeCorrelationProvider.class,
correlationResult = "pers"
fetch = FetchStrategy.JOIN
)
Set<Person> getSameAgedPersons();
static class PersonAgeCorrelationProvider implements CorrelationProvider {
@Override
public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) {
builder.correlate(Person.class, "pers") # <1>
.on("pers.age").inExpressions(correlationExpression) # <2>
final String alias = builder.getCorrelationAlias(); # <1>
builder.correlate(Person.class)
.on(alias + ".age").inExpressions(correlationExpression) # <2>
.end();
}
}
}
----
<1> Currently the alias _leaks_ into the main query builder when using the `JOIN` strategy so try to use a unique name
<2> Represents the `correlationBasis`. We generally recommend to use the `IN` predicate through `inExpressions()` to be able to easily switch the fetch strategy
<1> `getCorrelationAlias()` defines the alias for the correlated entity
<2> `correlationExpression` represents the `correlationBasis`. We generally recommend to use the `IN` predicate through `inExpressions()` to be able to easily switch the fetch strategy

Depending on the fetch strategy multiple other queries might be executed. Check out the different <<anchor-fetch-strategies,fetch strategies>> for further information.
In this case, the `JOIN` strategy was used, so the following query is generated.
Expand All @@ -792,23 +794,61 @@ In this case, the `JOIN` strategy was used, so the following query is generated.
----
SELECT cat.id, pers
FROM Cat cat
LEFT JOIN Person pers # <1>
ON cat.age = pers.age # <2>
LEFT JOIN Person correlated_SameAgedPersons # <1>
ON cat.age = correlated_SameAgedPersons.age # <2>
----
<1> This is makes use of the so called link:{core_doc}#anchor-entity-joins[`entity join` feature] which is only available in newer JPA provider versions
<1> This makes use of the so called link:{core_doc}#anchor-entity-joins[`entity join` feature] which is only available in newer JPA provider versions
<2> Note that the `IN` predicate which was used in the correlation provider was rewritten to a equality predicate

Since entity joins are required for using the `JOIN` fetch strategy with correlation mappings you have to make sure your JPA provider supports them.
If your JPA provider does not support entity joins, you have to use a different fetch strategy instead.

NOTE: Entity joins are only supported in newer versions of JPA providers(Hibernate 5.1+, EclipseLink 2.4+, DataNucleus 5+)

WARNING: Until https://github.com/Blazebit/blaze-persistence/issues/341[#341] is fixed, you should make sure that if you use relations as correlation keys, if it wouldn't be better to use the relations id instead or vice versa.
For some providers it might even be necessary to use the plain relation `relation` instead of it's id `relation.id` or the other way round because of possible cyclic join dependencies.
WARNING: Until https://github.com/Blazebit/blaze-persistence/issues/341[#341] is fixed, you should make sure that when using relations as correlation keys, if it wouldn't be better to use the relations id instead or vice versa.
For some providers it might even be *necessary* to use the plain relation `relation` instead of it's id `relation.id` or the other way round because of possible cyclic join dependencies.
The possible cyclic joins might happen for a JPA provider because it might generate a join for an expression like `relation.id` whereas other JPA providers might not.

// ==== Simple correlated mappings
// TODO: mention this can be done simple with `@MappingCorrelatedSimple`
==== Simple correlated mappings

Since correlation providers are mostly static, {projectname} also offers a way to define simple correlations in a declarative manner.
The `@MappingCorrelatedSimple` annotation only requires a few values

* `correlationBasis` - An expression that maps to the so called _correlation key_
* `correlated` - The _correlated entity_ type
* `correlationExpression` - The expression to use for correlating the _correlated entity_ type to the view

[source,java]
----
@EntityView(Person.class)
public interface PersonView {
@IdMapping("id")
Long getId();
String getName();
}
@EntityView(Cat.class)
public interface CatView {
@IdMapping("id")
Long getId();
@MappingCorrelated(
correlationBasis = "age",
correlated = Person.class,
correlationExpression = "age IN correlationKey" # <1>
fetch = FetchStrategy.JOIN
)
Set<PersonView> getSameAgedPersons(); # <2>
}
----
<1> The expression uses the default name for the correlation key but could use a different name by specifying the attribute `correlationKeyAlias`
<2> As you see here, it is obviously also possible to map subviews for correlated entity types

Just like the general correlation, by default, the correlated entity type is _projected into_ the view. To map a specific property of the entity type, use the `correlationResult` attribute.
There is also the possibility to specify a <<anchor-fetch-strategies,_fetch strategy_>> that should be used for the correlation. By default, the `SELECT` strategy is used.

=== Mapping expression extensions

Expand Down Expand Up @@ -914,18 +954,18 @@ public interface CatView {
@MappingCorrelated(
correlationBasis = "age",
correlator = CatAgeCorrelationProvider.class,
correlationResult = "correlatedCat"
correlator = CatAgeCorrelationProvider.class
)
Set<Cat> getSameAgedCats();
static class CatAgeCorrelationProvider implements CorrelationProvider {
@Override
public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) {
builder.correlate(Cat.class, "correlatedCat")
.on("correlatedCat.age").inExpressions(correlationExpression)
.on("correlatedCat.id").notInExpressions("VIEW_ROOT(id)") # <1>
final String correlatedCat = builder.getCorrelationAlias();
builder.correlate(Cat.class)
.on(correlatedCat + ".age").inExpressions(correlationExpression)
.on(correlatedCat + ".id").notInExpressions("VIEW_ROOT(id)") # <1>
.end();
}
Expand Down
Loading

0 comments on commit e625ec7

Please sign in to comment.