diff --git a/core/api/src/main/java/com/blazebit/persistence/FromBaseBuilder.java b/core/api/src/main/java/com/blazebit/persistence/FromBaseBuilder.java new file mode 100644 index 0000000000..ee1f480713 --- /dev/null +++ b/core/api/src/main/java/com/blazebit/persistence/FromBaseBuilder.java @@ -0,0 +1,194 @@ +/* + * Copyright 2014 - 2018 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; + +import javax.persistence.metamodel.EntityType; +import java.util.Collection; + +/** + * An interface for builders that support just the from clause. + * + * @param The concrete builder type + * @author Christian Beikov + * @since 1.3.0 + */ +public interface FromBaseBuilder> { + + /** + * Like {@link FromBaseBuilder#from(Class, String)} with the + * alias equivalent to the camel cased result of what {@link Class#getSimpleName()} of the entity class returns. + * + * @param entityClass The entity class which should be queried + * @return The query builder for chaining calls + */ + public X from(Class entityClass); + + /** + * Sets the entity class on which the query should be based on with the given alias. + * + * @param entityClass The entity class which should be queried + * @param alias The alias for the entity + * @return The query builder for chaining calls + */ + public X from(Class entityClass, String alias); + + /** + * Like {@link FromBaseBuilder#from(EntityType, String)} with the + * alias equivalent to the camel cased result of what {@link EntityType#getName()} of the entity class returns. + * + * @param entityType The entity type which should be queried + * @return The query builder for chaining calls + * @since 1.3.0 + */ + public X from(EntityType entityType); + + /** + * Sets the entity class on which the query should be based on with the given alias. + * + * @param entityType The entity type which should be queried + * @param alias The alias for the entity + * @return The query builder for chaining calls + * @since 1.3.0 + */ + public X from(EntityType entityType, String alias); + + /** + * Like {@link FromBaseBuilder#from(Class)} but explicitly queries the data before any side effects happen because of CTEs. + * + * @param entityClass The entity class which should be queried + * @return The query builder for chaining calls + * @since 1.1.0 + */ + public X fromOld(Class entityClass); + + /** + * Like {@link FromBaseBuilder#from(Class, String)} but explicitly queries the data before any side effects happen because of CTEs. + * + * @param entityClass The entity class which should be queried + * @param alias The alias for the entity + * @return The query builder for chaining calls + * @since 1.1.0 + */ + public X fromOld(Class entityClass, String alias); + + /** + * Like {@link FromBaseBuilder#from(Class)} but explicitly queries the data after any side effects happen because of CTEs. + * + * @param entityClass The entity class which should be queried + * @return The query builder for chaining calls + * @since 1.1.0 + */ + public X fromNew(Class entityClass); + + /** + * Like {@link FromBaseBuilder#from(Class, String)} but explicitly queries the data after any side effects happen because of CTEs. + * + * @param entityClass The entity class which should be queried + * @param alias The alias for the entity + * @return The query builder for chaining calls + * @since 1.1.0 + */ + public X fromNew(Class entityClass, String alias); + + /** + * Add a VALUES clause for values of the given value class to the from clause. + * This introduces a parameter named like the given alias. + * + * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} + * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. + * + * @param valueClass The class of the basic or managed type for which to create a VALUES clause + * @param alias The alias for the entity + * @param valueCount The number of values to use for the values clause + * @return The query builder for chaining calls + * @since 1.2.0 + */ + public X fromValues(Class valueClass, String alias, int valueCount); + + /** + * Add a VALUES clause for values of the type as determined by the given entity attribute to the from clause. + * This introduces a parameter named like the given alias. + * + * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} + * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. + * + * @param entityBaseClass The entity class on which the attribute is located + * @param attributeName The attribute name within the entity class which to use for determining the values type + * @param alias The alias for the entity + * @param valueCount The number of values to use for the values clause + * @return The query builder for chaining calls + * @since 1.3.0 + */ + public X fromValues(Class entityBaseClass, String attributeName, String alias, int valueCount); + + /** + * Add a VALUES clause for values of the given value class to the from clause. + * This introduces a parameter named like the given alias. + * + * In contrast to {@link FromBaseBuilder#fromValues(Class, String, int)} this will only bind the id attribute. + * + * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} + * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. + * + * @param valueClass The class of the identifiable type for which to create a VALUES clause + * @param alias The alias for the entity + * @param valueCount The number of values to use for the values clause + * @return The query builder for chaining calls + * @since 1.2.0 + */ + public X fromIdentifiableValues(Class valueClass, String alias, int valueCount); + + /** + * Like {@link FromBaseBuilder#fromValues(Class, String, int)} but passes the collection size + * as valueCount and directly binds the collection as parameter via {@link CommonQueryBuilder#setParameter(String, Object)}. + * + * @param valueClass The class of the basic or managed type for which to create a VALUES clause + * @param alias The alias for the entity + * @param values The values to use for the values clause + * @param The type of the values + * @return The query builder for chaining calls + * @since 1.2.0 + */ + public X fromValues(Class valueClass, String alias, Collection values); + + /** + * Like {@link FromBaseBuilder#fromValues(Class, String, String, int)} but passes the collection size + * as valueCount and directly binds the collection as parameter via {@link CommonQueryBuilder#setParameter(String, Object)}. + * + * @param entityBaseClass The entity class on which the attribute is located + * @param attributeName The attribute name within the entity class which to use for determining the values type + * @param alias The alias for the entity + * @param values The values to use for the values clause + * @return The query builder for chaining calls + * @since 1.3.0 + */ + public X fromValues(Class entityBaseClass, String attributeName, String alias, Collection values); + + /** + * Like {@link FromBaseBuilder#fromIdentifiableValues(Class, String, int)} but passes the collection size + * as valueCount and directly binds the collection as parameter via {@link CommonQueryBuilder#setParameter(String, Object)}. + * + * @param valueClass The class of the identifiable type for which to create a VALUES clause + * @param alias The alias for the entity + * @param values The values to use for the values clause + * @param The type of the values + * @return The query builder for chaining calls + * @since 1.2.0 + */ + public X fromIdentifiableValues(Class valueClass, String alias, Collection values); + +} diff --git a/core/api/src/main/java/com/blazebit/persistence/FromBuilder.java b/core/api/src/main/java/com/blazebit/persistence/FromBuilder.java index bc013b92c9..61cdb2a009 100644 --- a/core/api/src/main/java/com/blazebit/persistence/FromBuilder.java +++ b/core/api/src/main/java/com/blazebit/persistence/FromBuilder.java @@ -17,7 +17,6 @@ package com.blazebit.persistence; import javax.persistence.metamodel.EntityType; -import java.util.Collection; import java.util.Set; /** @@ -27,7 +26,7 @@ * @author Christian Beikov * @since 1.1.0 */ -public interface FromBuilder> { +public interface FromBuilder> extends FromBaseBuilder, FromProvider { /** * Returns the query roots. @@ -64,169 +63,6 @@ public interface FromBuilder> { */ public Path getPath(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. - * - * @param entityClass The entity class which should be queried - * @return The query builder for chaining calls - */ - public X from(Class entityClass); - - /** - * Sets the entity class on which the query should be based on with the given alias. - * - * @param entityClass The entity class which should be queried - * @param alias The alias for the entity - * @return The query builder for chaining calls - */ - public X from(Class entityClass, String alias); - - /** - * Like {@link FromBuilder#from(EntityType, String)} with the - * alias equivalent to the camel cased result of what {@link EntityType#getName()} of the entity class returns. - * - * @param entityType The entity type which should be queried - * @return The query builder for chaining calls - * @since 1.3.0 - */ - public X from(EntityType entityType); - - /** - * Sets the entity class on which the query should be based on with the given alias. - * - * @param entityType The entity type which should be queried - * @param alias The alias for the entity - * @return The query builder for chaining calls - * @since 1.3.0 - */ - public X from(EntityType entityType, String alias); - - /** - * Like {@link FromBuilder#from(Class)} but explicitly queries the data before any side effects happen because of CTEs. - * - * @param entityClass The entity class which should be queried - * @return The query builder for chaining calls - * @since 1.1.0 - */ - public X fromOld(Class entityClass); - - /** - * Like {@link FromBuilder#from(Class, String)} but explicitly queries the data before any side effects happen because of CTEs. - * - * @param entityClass The entity class which should be queried - * @param alias The alias for the entity - * @return The query builder for chaining calls - * @since 1.1.0 - */ - public X fromOld(Class entityClass, String alias); - - /** - * Like {@link FromBuilder#from(Class)} but explicitly queries the data after any side effects happen because of CTEs. - * - * @param entityClass The entity class which should be queried - * @return The query builder for chaining calls - * @since 1.1.0 - */ - public X fromNew(Class entityClass); - - /** - * Like {@link FromBuilder#from(Class, String)} but explicitly queries the data after any side effects happen because of CTEs. - * - * @param entityClass The entity class which should be queried - * @param alias The alias for the entity - * @return The query builder for chaining calls - * @since 1.1.0 - */ - public X fromNew(Class entityClass, String alias); - - /** - * Add a VALUES clause for values of the given value class to the from clause. - * This introduces a parameter named like the given alias. - * - * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} - * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. - * - * @param valueClass The class of the basic or managed type for which to create a VALUES clause - * @param alias The alias for the entity - * @param valueCount The number of values to use for the values clause - * @return The query builder for chaining calls - * @since 1.2.0 - */ - public X fromValues(Class valueClass, String alias, int valueCount); - - /** - * Add a VALUES clause for values of the type as determined by the given entity attribute to the from clause. - * This introduces a parameter named like the given alias. - * - * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} - * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. - * - * @param entityBaseClass The entity class on which the attribute is located - * @param attributeName The attribute name within the entity class which to use for determining the values type - * @param alias The alias for the entity - * @param valueCount The number of values to use for the values clause - * @return The query builder for chaining calls - * @since 1.3.0 - */ - public X fromValues(Class entityBaseClass, String attributeName, String alias, int valueCount); - - /** - * Add a VALUES clause for values of the given value class to the from clause. - * This introduces a parameter named like the given alias. - * - * In contrast to {@link FromBuilder#fromValues(Class, String, int)} this will only bind the id attribute. - * - * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} - * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. - * - * @param valueClass The class of the identifiable type for which to create a VALUES clause - * @param alias The alias for the entity - * @param valueCount The number of values to use for the values clause - * @return The query builder for chaining calls - * @since 1.2.0 - */ - public X fromIdentifiableValues(Class valueClass, String alias, int valueCount); - - /** - * Like {@link FromBuilder#fromValues(Class, String, int)} but passes the collection size - * as valueCount and directly binds the collection as parameter via {@link CommonQueryBuilder#setParameter(String, Object)}. - * - * @param valueClass The class of the basic or managed type for which to create a VALUES clause - * @param alias The alias for the entity - * @param values The values to use for the values clause - * @param The type of the values - * @return The query builder for chaining calls - * @since 1.2.0 - */ - public X fromValues(Class valueClass, String alias, Collection values); - - /** - * Like {@link FromBuilder#fromValues(Class, String, String, int)} but passes the collection size - * as valueCount and directly binds the collection as parameter via {@link CommonQueryBuilder#setParameter(String, Object)}. - * - * @param entityBaseClass The entity class on which the attribute is located - * @param attributeName The attribute name within the entity class which to use for determining the values type - * @param alias The alias for the entity - * @param values The values to use for the values clause - * @return The query builder for chaining calls - * @since 1.3.0 - */ - public X fromValues(Class entityBaseClass, String attributeName, String alias, Collection values); - - /** - * Like {@link FromBuilder#fromIdentifiableValues(Class, String, int)} but passes the collection size - * as valueCount and directly binds the collection as parameter via {@link CommonQueryBuilder#setParameter(String, Object)}. - * - * @param valueClass The class of the identifiable type for which to create a VALUES clause - * @param alias The alias for the entity - * @param values The values to use for the values clause - * @param The type of the values - * @return The query builder for chaining calls - * @since 1.2.0 - */ - public X fromIdentifiableValues(Class valueClass, String alias, Collection values); - /* * Join methods */ diff --git a/core/api/src/main/java/com/blazebit/persistence/FromProvider.java b/core/api/src/main/java/com/blazebit/persistence/FromProvider.java new file mode 100644 index 0000000000..54b760982c --- /dev/null +++ b/core/api/src/main/java/com/blazebit/persistence/FromProvider.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 - 2018 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; + +import java.util.Set; + +/** + * An interface for builders that support access to the from elements. + * + * @author Christian Beikov + * @since 1.3.0 + */ +public interface FromProvider { + + /** + * Returns the query roots. + * + * @return The roots of this query + * @since 1.2.0 + */ + public Set getRoots(); + + /** + * Returns the from element for the given alias or null. + * + * @param alias The alias of the from element + * @return The from element of this query or null if not found + * @since 1.2.0 + */ + 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); + + /** + * Returns the path object for the given path string, creating it if necessary. + * + * @param path The path string + * @return The path object for this query + * @since 1.2.1 + */ + public Path getPath(String path); + +} diff --git a/core/api/src/main/java/com/blazebit/persistence/MultipleSubqueryInitiator.java b/core/api/src/main/java/com/blazebit/persistence/MultipleSubqueryInitiator.java index 2b7297215d..3f537990c4 100644 --- a/core/api/src/main/java/com/blazebit/persistence/MultipleSubqueryInitiator.java +++ b/core/api/src/main/java/com/blazebit/persistence/MultipleSubqueryInitiator.java @@ -25,6 +25,14 @@ */ public interface MultipleSubqueryInitiator { + /** + * Returns the parent query builder. + * + * @return The parent query builder + * @since 1.3.0 + */ + public CommonQueryBuilder getParentQueryBuilder(); + /** * Starts a {@link SubqueryInitiator} for the given subquery alias. * diff --git a/core/api/src/main/java/com/blazebit/persistence/SubqueryInitiator.java b/core/api/src/main/java/com/blazebit/persistence/SubqueryInitiator.java index 34d109e26e..3b9c255cdf 100644 --- a/core/api/src/main/java/com/blazebit/persistence/SubqueryInitiator.java +++ b/core/api/src/main/java/com/blazebit/persistence/SubqueryInitiator.java @@ -16,6 +16,7 @@ package com.blazebit.persistence; +import javax.persistence.metamodel.EntityType; import java.util.Collection; /** @@ -25,25 +26,15 @@ * @author Christian Beikov * @since 1.0.0 */ -public interface SubqueryInitiator { +public interface SubqueryInitiator extends FromBaseBuilder> { /** - * Like {@link SubqueryInitiator#from(java.lang.Class, java.lang.String)} with the - * alias equivalent to the camel cased result of what {@link Class#getSimpleName()} of the entity class returns. + * Returns the parent query builder. * - * @param entityClass The entity class which should be the entity - * @return A new subquery builder + * @return The parent query builder + * @since 1.3.0 */ - public SubqueryBuilder from(Class entityClass); - - /** - * Creates a new subquery builder with the given entity class as entity in the FROM clause with the given alias. - * - * @param entityClass The entity class which should be the entity - * @param alias The alias for the entity - * @return A new subquery builder - */ - public SubqueryBuilder from(Class entityClass, String alias); + public CommonQueryBuilder getParentQueryBuilder(); /** * Like {@link SubqueryInitiator#from(String, String)} with the @@ -74,13 +65,58 @@ public interface SubqueryInitiator { */ public StartOngoingSetOperationSubqueryBuilder> startSet(); + /* Overrides for proper JavaDoc */ + + /** + * Like {@link SubqueryInitiator#from(java.lang.Class, java.lang.String)} with the + * alias equivalent to the camel cased result of what {@link Class#getSimpleName()} of the entity class returns. + * + * @param entityClass The entity class which should be the entity + * @return A new subquery builder + */ + @Override + public SubqueryBuilder from(Class entityClass); + + /** + * Creates a new subquery builder with the given entity class as entity in the FROM clause with the given alias. + * + * @param entityClass The entity class which should be the entity + * @param alias The alias for the entity + * @return A new subquery builder + */ + @Override + public SubqueryBuilder from(Class entityClass, String alias); + + /** + * Like {@link SubqueryInitiator#from(EntityType, String)} with the + * alias equivalent to the camel cased result of what {@link EntityType#getName()} of the entity class returns. + * + * @param entityType The entity type which should be queried + * @return A new subquery builder + * @since 1.3.0 + */ + @Override + SubqueryBuilder from(EntityType entityType); + + /** + * Creates a new subquery builder with the entity class on which the query should be based on with the given alias. + * + * @param entityType The entity type which should be queried + * @param alias The alias for the entity + * @return A new subquery builder + * @since 1.3.0 + */ + @Override + SubqueryBuilder from(EntityType entityType, String alias); + /** * Like {@link SubqueryInitiator#from(Class)} but explicitly queries the data before any side effects happen because of CTEs. * * @param entityClass The entity class which should be queried - * @return The query builder for chaining calls + * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromOld(Class entityClass); /** @@ -88,18 +124,20 @@ public interface SubqueryInitiator { * * @param entityClass The entity class which should be queried * @param alias The alias for the entity - * @return The query builder for chaining calls + * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromOld(Class entityClass, String alias); /** * Like {@link SubqueryInitiator#from(Class)} but explicitly queries the data after any side effects happen because of CTEs. * * @param entityClass The entity class which should be queried - * @return The query builder for chaining calls + * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromNew(Class entityClass); /** @@ -107,9 +145,10 @@ public interface SubqueryInitiator { * * @param entityClass The entity class which should be queried * @param alias The alias for the entity - * @return The query builder for chaining calls + * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromNew(Class entityClass, String alias); /** @@ -125,8 +164,26 @@ public interface SubqueryInitiator { * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromValues(Class valueClass, String alias, int valueCount); + /** + * Creates a new subquery builder with a VALUES clause for values of the type as determined by the given entity attribute to the from clause. + * This introduces a parameter named like the given alias. + * + * To set the values invoke {@link CommonQueryBuilder#setParameter(String, Object)} + * or {@link javax.persistence.Query#setParameter(String, Object)} with the alias and a collection. + * + * @param entityBaseClass The entity class on which the attribute is located + * @param attributeName The attribute name within the entity class which to use for determining the values type + * @param alias The alias for the entity + * @param valueCount The number of values to use for the values clause + * @return The new subquery builder + * @since 1.3.0 + */ + @Override + SubqueryBuilder fromValues(Class entityBaseClass, String attributeName, String alias, int valueCount); + /** * Creates a new subquery builder with a VALUES clause for values of the given value class in the from clause. * This introduces a parameter named like the given alias. @@ -142,6 +199,7 @@ public interface SubqueryInitiator { * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromIdentifiableValues(Class valueClass, String alias, int valueCount); /** @@ -155,8 +213,23 @@ public interface SubqueryInitiator { * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromValues(Class valueClass, String alias, Collection values); + /** + * Like {@link SubqueryInitiator#fromValues(Class, String, String, int)} but passes the collection size + * as valueCount and directly binds the collection as parameter via {@link SubqueryBuilder#setParameter(String, Object)}. + * + * @param entityBaseClass The entity class on which the attribute is located + * @param attributeName The attribute name within the entity class which to use for determining the values type + * @param alias The alias for the entity + * @param values The values to use for the values clause + * @return The new subquery builder + * @since 1.3.0 + */ + @Override + SubqueryBuilder fromValues(Class entityBaseClass, String attributeName, String alias, Collection values); + /** * Like {@link SubqueryInitiator#fromIdentifiableValues(Class, String, int)} but passes the collection size * as valueCount and directly binds the collection as parameter via {@link SubqueryBuilder#setParameter(String, Object)}. @@ -168,5 +241,7 @@ public interface SubqueryInitiator { * @return A new subquery builder * @since 1.2.0 */ + @Override public SubqueryBuilder fromIdentifiableValues(Class valueClass, String alias, Collection values); + } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/MultipleSubqueryInitiatorImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/MultipleSubqueryInitiatorImpl.java index 08ad48a09b..be59f4ec3b 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/MultipleSubqueryInitiatorImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/MultipleSubqueryInitiatorImpl.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.impl; +import com.blazebit.persistence.CommonQueryBuilder; import com.blazebit.persistence.FullQueryBuilder; import com.blazebit.persistence.MultipleSubqueryInitiator; import com.blazebit.persistence.SubqueryBuilder; @@ -47,6 +48,11 @@ public MultipleSubqueryInitiatorImpl(T result, Expression expression, Expression this.clauseType = clauseType; } + @Override + public CommonQueryBuilder getParentQueryBuilder() { + return (CommonQueryBuilder) subqueryInitFactory.getQueryBuilder(); + } + @Override @SuppressWarnings("unchecked") public SubqueryInitiator> with(String subqueryAlias) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/SubqueryInitiatorImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/SubqueryInitiatorImpl.java index 0843d06ff2..184a58d6e3 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/SubqueryInitiatorImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/SubqueryInitiatorImpl.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.impl; +import com.blazebit.persistence.CommonQueryBuilder; import com.blazebit.persistence.LeafOngoingFinalSetOperationSubqueryBuilder; import com.blazebit.persistence.StartOngoingSetOperationSubqueryBuilder; import com.blazebit.persistence.SubqueryBuilder; @@ -23,6 +24,7 @@ import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.parser.expression.ExpressionFactory; +import javax.persistence.metamodel.EntityType; import java.util.Arrays; import java.util.Collection; @@ -55,6 +57,11 @@ public SubqueryInitiatorImpl(MainQuery mainQuery, QueryContext queryContext, Ali this.queryContext = queryContext; } + @Override + public CommonQueryBuilder getParentQueryBuilder() { + return (CommonQueryBuilder) queryContext.getParent(); + } + @Override public SubqueryBuilder from(Class clazz) { return from(clazz, null); @@ -71,6 +78,22 @@ public SubqueryBuilder from(Class clazz, String alias) { return subqueryBuilder; } + @Override + public SubqueryBuilder from(EntityType entityType) { + return from(entityType, null); + } + + @Override + public SubqueryBuilder from(EntityType entityType, String alias) { + SubqueryBuilderImpl subqueryBuilder = new SubqueryBuilderImpl(mainQuery, queryContext, aliasManager, parentJoinManager, mainQuery.subqueryExpressionFactory, result, listener); + if (inExists) { + subqueryBuilder.selectManager.setDefaultSelect(null, Arrays.asList(new SelectInfo(expressionFactory.createArithmeticExpression("1")))); + } + subqueryBuilder.from(entityType, alias); + listener.onBuilderStarted(subqueryBuilder); + return subqueryBuilder; + } + @Override public SubqueryBuilder from(String correlationPath) { return from(correlationPath, null); @@ -159,6 +182,17 @@ public SubqueryBuilder fromValues(Class valueClass, String alias, int valu return subqueryBuilder; } + @Override + public SubqueryBuilder fromValues(Class entityBaseClass, String attributeName, String alias, int valueCount) { + SubqueryBuilderImpl subqueryBuilder = new SubqueryBuilderImpl(mainQuery, queryContext, aliasManager, parentJoinManager, mainQuery.subqueryExpressionFactory, result, listener); + if (inExists) { + subqueryBuilder.selectManager.setDefaultSelect(null, Arrays.asList(new SelectInfo(expressionFactory.createArithmeticExpression("1")))); + } + subqueryBuilder.fromValues(entityBaseClass, attributeName, alias, valueCount); + listener.onBuilderStarted(subqueryBuilder); + return subqueryBuilder; + } + @Override public SubqueryBuilder fromIdentifiableValues(Class valueClass, String alias, int valueCount) { SubqueryBuilderImpl subqueryBuilder = new SubqueryBuilderImpl(mainQuery, queryContext, aliasManager, parentJoinManager, mainQuery.subqueryExpressionFactory, result, listener); @@ -177,6 +211,13 @@ public SubqueryBuilder fromValues(Class valueClass, String alias, Coll return builder; } + @Override + public SubqueryBuilder fromValues(Class entityBaseClass, String attributeName, String alias, Collection values) { + SubqueryBuilder builder = fromValues(entityBaseClass, attributeName, alias, values.size()); + builder.setParameter(alias, values); + return builder; + } + @Override public SubqueryBuilder fromIdentifiableValues(Class valueClass, String alias, Collection values) { SubqueryBuilder builder = fromIdentifiableValues(valueClass, alias, values.size()); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/builder/predicate/AbstractQuantifiablePredicateBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/builder/predicate/AbstractQuantifiablePredicateBuilder.java index 381b1c7760..aa9fd22632 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/builder/predicate/AbstractQuantifiablePredicateBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/builder/predicate/AbstractQuantifiablePredicateBuilder.java @@ -20,6 +20,7 @@ import com.blazebit.persistence.CaseWhenBuilder; import com.blazebit.persistence.CaseWhenOrThenBuilder; import com.blazebit.persistence.CaseWhenThenBuilder; +import com.blazebit.persistence.CommonQueryBuilder; import com.blazebit.persistence.FullQueryBuilder; import com.blazebit.persistence.LeafOngoingFinalSetOperationSubqueryBuilder; import com.blazebit.persistence.MultipleSubqueryInitiator; @@ -48,6 +49,7 @@ import com.blazebit.persistence.parser.predicate.PredicateQuantifier; import com.blazebit.persistence.parser.predicate.QuantifiableBinaryExpressionPredicate; +import javax.persistence.metamodel.EntityType; import java.util.Collection; /** @@ -101,6 +103,11 @@ protected void chainSubbuilder(Predicate predicate) { this.predicate = predicate; } + @Override + public CommonQueryBuilder getParentQueryBuilder() { + return (CommonQueryBuilder) subqueryInitFactory.getQueryBuilder(); + } + @Override public T value(Object value) { return chain(createPredicate(leftExpression, parameterManager.addParameterExpression(value, clauseType, subqueryInitFactory.getQueryBuilder()), PredicateQuantifier.ONE)); @@ -244,6 +251,18 @@ public SubqueryBuilder from(Class clazz, String alias) { return getSubqueryInitiator().from(clazz, alias); } + @Override + public SubqueryBuilder from(EntityType entityType) { + chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); + return getSubqueryInitiator().from(entityType); + } + + @Override + public SubqueryBuilder from(EntityType entityType, String alias) { + chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); + return getSubqueryInitiator().from(entityType, alias); + } + @Override public SubqueryBuilder from(String correlationPath) { chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); @@ -291,6 +310,12 @@ public SubqueryBuilder fromValues(Class valueClass, String alias, int valu return getSubqueryInitiator().fromValues(valueClass, alias, valueCount); } + @Override + public SubqueryBuilder fromValues(Class entityBaseClass, String attributeName, String alias, int valueCount) { + chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); + return getSubqueryInitiator().fromValues(entityBaseClass, attributeName, alias, valueCount); + } + @Override public SubqueryBuilder fromIdentifiableValues(Class valueClass, String alias, int valueCount) { chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); @@ -303,6 +328,12 @@ public SubqueryBuilder fromValues(Class valueClass, String alias, Coll return getSubqueryInitiator().fromValues(valueClass, alias, values); } + @Override + public SubqueryBuilder fromValues(Class entityBaseClass, String attributeName, String alias, Collection values) { + chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); + return getSubqueryInitiator().fromValues(entityBaseClass, attributeName, alias, values); + } + @Override public SubqueryBuilder fromIdentifiableValues(Class valueClass, String alias, Collection values) { chainSubbuilder(createPredicate(leftExpression, null, PredicateQuantifier.ONE)); diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java b/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java index 09899bfbb3..876391aa05 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java @@ -120,7 +120,7 @@ public Type getRealCurrentType() { } public Class getRealCurrentClass() { - return currentClass.getJavaType(); + return currentClass == null ? null : currentClass.getJavaType(); } public Type getCurrentType() { @@ -135,7 +135,7 @@ public Type getCurrentType() { } public Class getCurrentClass() { - return getCurrentType().getJavaType(); + return getCurrentType() == null ? null : getCurrentType().getJavaType(); } public Class getKeyCurrentClass() { diff --git a/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc b/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc index d792cf829a..5e1709533c 100644 --- a/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc +++ b/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc @@ -475,11 +475,10 @@ The resulting logical JPQL doesn't include individual parameters, but specifies [source,sql] ---- -SELECT TREAT_STRING(myValue.value) +SELECT myValue FROM String(2 VALUES) myValue ---- -The use of `TREAT_STRING` as the name suggests, has the effect that the expression is treated like a string expression. Behind the scenes, a type called `ValuesEntity` is used to be able to implement the VALUES clause. For further information on `TREAT` functions, take a look at the <> chapter. @@ -501,7 +500,7 @@ The logical JPQL encodes this as [source,sql] ---- -SELECT TREAT_STRING(myValue.value) +SELECT myValue FROM String(2 VALUES LIKE Cat.name) myValue ---- diff --git a/documentation/src/main/asciidoc/entity-view/manual/en_US/05_fetch_strategies.adoc b/documentation/src/main/asciidoc/entity-view/manual/en_US/05_fetch_strategies.adoc index a2466710d8..d8c4054d5e 100644 --- a/documentation/src/main/asciidoc/entity-view/manual/en_US/05_fetch_strategies.adoc +++ b/documentation/src/main/asciidoc/entity-view/manual/en_US/05_fetch_strategies.adoc @@ -74,17 +74,16 @@ The main query will fetch the `correlationBasis` and all the other attributes. [source,sql] ---- SELECT - TREAT_LONG(correlationParams.value), + correlationParams, correlated_SameAgedPersons FROM Person correlated_SameAgedPersons, - VALUES ((?), (?), ...) correlationParams # <1> -WHERE correlated_SameAgedPersons.age = TREAT_LONG(correlationParams.value) + Long(20 VALUES) correlationParams +WHERE correlated_SameAgedPersons.age = correlationParams ---- -<1> Actually there will be 20 question marks here because of the defined batch size of 20 The correlation query on the other hand will select the correlation value and the `Person` instances with an age matching any of the `correlationParams` values. What you see here is the use of the link:{core_doc}#anchor-values-clause[`VALUES` clause] for making multiple values available like a table for querying which is required when wanting to select the correlation value. -Selecting the actual correlation value via `TREAT_LONG(correlationParams.value)` along with the `Person` is necessary to be able to correlate the instances to the instance of the main query. +Selecting the actual correlation value via `correlationParams` along with the `Person` is necessary to be able to correlate the instances to the instance of the main query. Depending on how many different values for `age` there are(cardinality), the correlation query might get executed multiple times. In general, the runtime will collect up to _batch size_ different values and then execute the correlation query for these values. diff --git a/documentation/src/main/asciidoc/entity-view/manual/en_US/08_updatable_entity_views.adoc b/documentation/src/main/asciidoc/entity-view/manual/en_US/08_updatable_entity_views.adoc index 793c6bdfe8..45ecfdb652 100644 --- a/documentation/src/main/asciidoc/entity-view/manual/en_US/08_updatable_entity_views.adoc +++ b/documentation/src/main/asciidoc/entity-view/manual/en_US/08_updatable_entity_views.adoc @@ -874,7 +874,7 @@ An attribute does *persist cascading* if a new object reached through that attri The following tables should help illustrate the defaults and are also a good reference. -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== |*Basic simple type* |Relationship updatable |Update cascaded |Persist cascaded @@ -944,7 +944,7 @@ interface View { Using a JPA embeddable type `Embeddable` -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *Basic JPA embeddable type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1014,7 +1014,7 @@ interface View { Using a JPA entity type `Entity2` -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *Basic JPA entity type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1097,7 +1097,7 @@ interface View2 { results in the following default behavior -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *View type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1181,7 +1181,7 @@ interface View2 { } ---- -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *View type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1266,7 +1266,7 @@ Collections are different, as they are mutable by default. Since it is rarely ne collections aren't updatable by default just because they are mutable by design. In order for a collection relationship to be considered updatable, it must have a setter, be annotated with `@UpdatableMapping(updatable = true)` or have an element type that is `@CreatableEntityView`. -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *Basic simple type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1336,7 +1336,7 @@ interface View { Using a JPA embeddable type `Embeddable` -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *Basic JPA embeddable type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1406,7 +1406,7 @@ interface View { Using a JPA entity type `Entity2` -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *Basic JPA entity type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1487,7 +1487,7 @@ interface View2 { } ---- -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *View type* | Relationship updatable | Update cascaded | Persist cascaded @@ -1571,7 +1571,7 @@ interface View2 { } ---- -[width="100%",options="header,footer",cols="3a,1d,1d,1d"] +[width="100%",options="header,footer",cols="3a,1d,1a,1a"] |==================== | *View type* | Relationship updatable | Update cascaded | Persist cascaded diff --git a/entity-view/api/src/main/java/com/blazebit/persistence/view/CorrelationBuilder.java b/entity-view/api/src/main/java/com/blazebit/persistence/view/CorrelationBuilder.java index be4811e272..6d1abfed72 100644 --- a/entity-view/api/src/main/java/com/blazebit/persistence/view/CorrelationBuilder.java +++ b/entity-view/api/src/main/java/com/blazebit/persistence/view/CorrelationBuilder.java @@ -17,8 +17,11 @@ package com.blazebit.persistence.view; import com.blazebit.persistence.CorrelationQueryBuilder; +import com.blazebit.persistence.FromProvider; import com.blazebit.persistence.JoinOnBuilder; +import javax.persistence.metamodel.EntityType; + /** * A builder for correlating a basis with an entity class. * @@ -36,6 +39,14 @@ public interface CorrelationBuilder { */ public T getService(Class serviceClass); + /** + * Returns the correlation from provider. + * + * @return The correlation from provider + * @since 1.3.0 + */ + public FromProvider getCorrelationFromProvider(); + /** * Generates a meaningful alias that can be used for the correlation. * @@ -50,4 +61,13 @@ public interface CorrelationBuilder { * @return The restriction builder for the correlation predicate */ public JoinOnBuilder correlate(Class entityClass); + + /** + * Correlates a basis with the given entity type. + * + * @param entityType The entity type which should be correlated + * @return The restriction builder for the correlation predicate + * @since 1.3.0 + */ + public JoinOnBuilder correlate(EntityType entityType); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java index d1cefe2c94..6c700b4c29 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/ScalarTargetResolvingExpressionVisitor.java @@ -175,12 +175,12 @@ public List getPossibleTargetTypes() { List positions = pathPositions; int size = positions.size(); - if (size == 1 && positions.get(0).getAttribute() == null && managedType.getJavaType().equals(positions.get(0).getRealCurrentClass())) { + if (managedType != null && size == 1 && positions.get(0).getAttribute() == null && managedType.getJavaType().equals(positions.get(0).getRealCurrentClass())) { // When we didn't resolve any property, the expression is probably static and we can't give types in that case return Collections.emptyList(); } - List possibleTargets = new ArrayList(size); + List possibleTargets = new ArrayList<>(size); for (int i = 0; i < size; i++) { PathPosition position = positions.get(i); possibleTargets.add(new TargetTypeImpl(position.hasCollectionJoin(), position.getAttribute(), position.getRealCurrentClass(), position.getKeyCurrentClass(), position.getCurrentClass())); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java index cdbf267fdd..d618d06f35 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java @@ -48,5 +48,5 @@ public interface CollectionAction> { public CollectionAction replaceObjects(Map objectMapping); - public void addAction(List> actions, Collection addedElements, Collection removedElements); + public void addAction(RecordingCollection recordingCollection, List> actions); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java index 74a18720a6..07d5bf05dd 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java @@ -158,10 +158,10 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { CollectionOperations op = new CollectionOperations(actions); - if (op.addElements(addedElements)) { + if (op.addElements(recordingCollection, (Collection) elements)) { actions.add(this); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java index 50f4c1824d..1cd7a20e48 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java @@ -84,7 +84,7 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { actions.clear(); actions.add(this); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionOperations.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionOperations.java index befff36f1d..e5c2524aff 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionOperations.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionOperations.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.impl.collection; import java.util.Collection; +import java.util.Iterator; import java.util.List; /** @@ -60,27 +61,36 @@ public CollectionOperations(List addedElements) { + public boolean addElements(RecordingCollection recordingCollection, Collection addedElements) { if (!addedElements.isEmpty()) { Collection objectsToAdd = addedElements; // Elide removed elements for newly added elements if (removeAction != null) { objectsToAdd = removeAction.onAddObjects(objectsToAdd); } - if (!objectsToAdd.isEmpty()) { - // Merge newly added elements into existing add action - if (addAction != null) { - addAction.onAddObjects(objectsToAdd); - } else { - return true; + // Merge newly added elements into existing add action + if (!objectsToAdd.isEmpty() && addAction != null) { + addAction.onAddObjects(objectsToAdd); + return false; + } else { + if (addedElements != objectsToAdd) { + Iterator iterator = addedElements.iterator(); + while (iterator.hasNext()) { + Object o = iterator.next(); + if (!objectsToAdd.contains(o)) { + iterator.remove(); + recordingCollection.addAddedElement(o); + } + } } + return !objectsToAdd.isEmpty(); } } return false; } - public int removeElements(Collection removedElements) { + public int removeElements(RecordingCollection recordingCollection, Collection removedElements) { if (!removedElements.isEmpty()) { Collection objectsToRemove = removedElements; // Elide added elements for newly removed elements @@ -92,6 +102,16 @@ public int removeElements(Collection removedElements) { if (removeAction != null) { removeAction.onRemoveObjects(objectsToRemove); } else { + if (removedElements != objectsToRemove) { + Iterator iterator = removedElements.iterator(); + while (iterator.hasNext()) { + Object o = iterator.next(); + if (!objectsToRemove.contains(o)) { + iterator.remove(); + recordingCollection.addRemovedElement(o); + } + } + } return removeIndex; } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java index 56f9369b9c..3d783061c5 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java @@ -174,10 +174,10 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { CollectionOperations op = new CollectionOperations(actions); - int removeIndex = op.removeElements(removedElements); + int removeIndex = op.removeElements(recordingCollection, elements); if (removeIndex != -1) { actions.add(removeIndex, this); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java index f47e72bc18..a6917ef226 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java @@ -137,7 +137,7 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { actions.add(this); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java index 8a8e5f855f..89c20db7a8 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java @@ -167,7 +167,7 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { actions.add(this); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java index 712b97baac..cbde5d19dd 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java @@ -138,7 +138,7 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { actions.add(this); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java index f31f3f327a..91fad2f2ff 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java @@ -169,7 +169,7 @@ public CollectionAction replaceObjects(Map objectMapping) { } @Override - public void addAction(List> actions, Collection addedElements, Collection removedElements) { + public void addAction(RecordingCollection recordingCollection, List> actions) { CollectionAction lastAction; // Multiple set operations are coalesced into a single one if (!actions.isEmpty() && (lastAction = actions.get(actions.size() - 1)) instanceof ListSetAction) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java index 354467e2a4..3b77005931 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java @@ -229,6 +229,24 @@ public Set getRemovedElements() { return removedElements.keySet(); } + void addAddedElement(Object o) { + if (removedElements.remove(o) == null) { + addedElements.put((E) o, (E) o); + } + if (parent != null && o instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) o).$$_setParent(this, 1); + } + } + + void addRemovedElement(Object o) { + if (addedElements.remove(o) == null) { + removedElements.put((E) o, (E) o); + } + if (o instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) o).$$_unsetParent(); + } + } + public void setActions(RecordingCollection recordingCollection, Map objectMapping) { if (recordingCollection.actions == null) { this.actions = null; @@ -360,13 +378,19 @@ public void initiateActionsAgainstState(List> actions, C ini for (CollectionAction action : actions) { for (Object o : action.getAddedObjects(initialState)) { - addedElements.put((E) o, (E) o); - removedElements.remove(o); - // We don't set the parent here because that will happen during the setParent call for this collection + if (removedElements.remove(o) == null) { + addedElements.put((E) o, (E) o); + // We don't set the parent here because that will happen during the setParent call for this collection + } else { + if (o instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) o).$$_unsetParent(); + } + } } for (Object o : action.getRemovedObjects(initialState)) { - removedElements.put((E) o, (E) o); - addedElements.remove(o); + if (addedElements.remove(o) == null) { + removedElements.put((E) o, (E) o); + } if (o instanceof BasicDirtyTracker) { ((BasicDirtyTracker) o).$$_unsetParent(); } @@ -397,7 +421,7 @@ protected final void addAction(CollectionAction action) { // addAction optimizes actions by figuring converting to physical changes if (optimize) { - action.addAction(actions, addedElements, removedElements); + action.addAction(this, actions); } else { actions.add(action); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java index f934656c6b..e3f5954f41 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java @@ -73,7 +73,7 @@ public boolean removeAll(Collection c) { public void clear() { int size = delegate.size(); // Always remove the last element to benefit from tail removals - for (int i = size - 1; i > 0; i--) { + for (int i = size - 1; i >= 0; i--) { addRemoveAction(i); } delegate.clear(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java index e5e14ce4e5..c62ad1564f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java @@ -424,25 +424,37 @@ public void initiateActionsAgainstState(List> actions, C initialSta for (MapAction action : actions) { for (Object o : action.getAddedKeys(initialState)) { - addedKeys.put((K) o, (K) o); - removedKeys.remove(o); - // We don't set the parent here because that will happen during the setParent call for this collection + if (removedKeys.remove(o) == null) { + addedKeys.put((K) o, (K) o); + // We don't set the parent here because that will happen during the setParent call for this collection + } else { + if (o instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) o).$$_unsetParent(); + } + } } for (Object o : action.getRemovedKeys(initialState)) { - removedKeys.put((K) o, (K) o); - addedKeys.remove(o); + if (addedKeys.remove(o) == null) { + removedKeys.put((K) o, (K) o); + } if (o instanceof BasicDirtyTracker) { ((BasicDirtyTracker) o).$$_unsetParent(); } } for (Object o : action.getAddedElements(initialState)) { - addedElements.put((V) o, (V) o); - removedElements.remove(o); - // We don't set the parent here because that will happen during the setParent call for this collection + if (removedElements.remove(o) == null) { + addedElements.put((V) o, (V) o); + // We don't set the parent here because that will happen during the setParent call for this collection + } else { + if (o instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) o).$$_unsetParent(); + } + } } for (Object o : action.getRemovedElements(initialState)) { - removedElements.put((V) o, (V) o); - addedElements.remove(o); + if (addedElements.remove(o) == null) { + removedElements.put((V) o, (V) o); + } if (o instanceof BasicDirtyTracker) { ((BasicDirtyTracker) o).$$_unsetParent(); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java index 7259b18298..6b8cb7d7a7 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java @@ -589,7 +589,7 @@ private static void validateTypesCompatible(ManagedType managedType, String e ScalarTargetResolvingExpressionVisitor visitor = new ScalarTargetResolvingExpressionVisitor(managedType, context.getEntityMetamodel(), context.getJpqlFunctions()); try { - context.getExpressionFactory().createSimpleExpression(expression, false).accept(visitor); + context.getTypeValidationExpressionFactory().createSimpleExpression(expression, false).accept(visitor); } catch (SyntaxErrorException ex) { context.addError("Syntax error in " + expressionLocation + " '" + expression + "' of the " + location + ": " + ex.getMessage()); } catch (IllegalArgumentException ex) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java index 98398fe1c4..a7b82a0c14 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MetamodelBuildingContextImpl.java @@ -16,12 +16,14 @@ package com.blazebit.persistence.view.impl.metamodel; +import com.blazebit.persistence.parser.AliasReplacementVisitor; import com.blazebit.persistence.parser.EntityMetamodel; import com.blazebit.persistence.parser.expression.AbstractCachingExpressionFactory; import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.parser.expression.ExpressionFactory; import com.blazebit.persistence.parser.expression.MacroConfiguration; import com.blazebit.persistence.parser.expression.MacroFunction; +import com.blazebit.persistence.parser.expression.NullExpression; import com.blazebit.persistence.spi.JpaProvider; import com.blazebit.persistence.spi.JpqlFunction; import com.blazebit.persistence.view.FlushMode; @@ -30,6 +32,7 @@ import com.blazebit.persistence.view.Mapping; import com.blazebit.persistence.view.MappingCorrelated; import com.blazebit.persistence.view.MappingCorrelatedSimple; +import com.blazebit.persistence.view.MappingSubquery; import com.blazebit.persistence.view.impl.ConfigurationProperties; import com.blazebit.persistence.view.impl.JpqlMacroAdapter; import com.blazebit.persistence.view.impl.MacroConfigurationExpressionFactory; @@ -214,6 +217,18 @@ public List getPossibleTarget } else if (mapping instanceof MappingCorrelated) { // We can't determine the managed type return Collections.emptyList(); + } else if (mapping instanceof MappingSubquery) { + MappingSubquery mappingSubquery = (MappingSubquery) mapping; + if (!mappingSubquery.expression().isEmpty()) { + Expression simpleExpression = typeValidationExpressionFactory.createSimpleExpression(((MappingSubquery)mapping).expression(), true); + AliasReplacementVisitor aliasReplacementVisitor = new AliasReplacementVisitor(new NullExpression(), mappingSubquery.subqueryAlias()); + simpleExpression.accept(aliasReplacementVisitor); + ScalarTargetResolvingExpressionVisitor visitor = new ScalarTargetResolvingExpressionVisitor((ManagedType) null, entityMetamodel, jpqlFunctions); + simpleExpression.accept(visitor); + return visitor.getPossibleTargetTypes(); + } + // We can't determine the managed type + return Collections.emptyList(); } else { // There is no entity model type for other mappings return Collections.emptyList(); @@ -259,7 +274,7 @@ public Type getBasicType(ViewMapping viewMapping, java.lang.reflect.Type Map, Type> convertedTypeMap = null; for (Class entityModelType : possibleTypes) { - if (!typeConverterMap.isEmpty()) { + if (entityModelType != null && !typeConverterMap.isEmpty()) { convertedTypeMap = convertedTypeRegistry.get(key); if (convertedTypeMap != null) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java index 712d773787..0921256bab 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java @@ -477,7 +477,8 @@ private boolean validateCascadeSubtypeMappings(MetamodelBuildingContext context, while (iterator.hasNext()) { Map.Entry entry = iterator.next(); ViewMapping mapping = entry.getKey(); - if (mapping.validateDependencies(context, dependencies, this, null, reportError)) { + // Only report errors if this is an explicit mapping i.e. entry.getValue() == Boolean.TRUE + if (mapping.validateDependencies(context, dependencies, this, null, entry.getValue() == Boolean.TRUE)) { iterator.remove(); // This is only an error if a mapping was explicit if (entry.getValue() == Boolean.TRUE) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/mapper/AbstractCorrelationJoinTupleElementMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/mapper/AbstractCorrelationJoinTupleElementMapper.java index 36499a6d19..b212eb86e9 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/mapper/AbstractCorrelationJoinTupleElementMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/mapper/AbstractCorrelationJoinTupleElementMapper.java @@ -21,6 +21,7 @@ import com.blazebit.persistence.parser.expression.ExpressionFactory; import com.blazebit.persistence.view.impl.CorrelationProviderHelper; import com.blazebit.persistence.view.impl.PrefixingQueryGenerator; +import com.blazebit.persistence.view.impl.macro.EmbeddingViewJpqlMacro; import java.util.Collections; @@ -50,8 +51,12 @@ public AbstractCorrelationJoinTupleElementMapper(ExpressionFactory ef, String jo this.correlationResult = correlationAlias; } else { StringBuilder sb = new StringBuilder(correlationAlias.length() + correlationResult.length() + 1); + EmbeddingViewJpqlMacro embeddingViewJpqlMacro = (EmbeddingViewJpqlMacro) ef.getDefaultMacroConfiguration().get("EMBEDDING_VIEW").getState()[0]; + String oldEmbeddingViewPath = embeddingViewJpqlMacro.getEmbeddingViewPath(); + embeddingViewJpqlMacro.setEmbeddingViewPath(embeddingViewPath); Expression expr = ef.createSimpleExpression(correlationResult, false); - SimpleQueryGenerator generator = new PrefixingQueryGenerator(Collections.singletonList(correlationAlias)); + embeddingViewJpqlMacro.setEmbeddingViewPath(oldEmbeddingViewPath); + SimpleQueryGenerator generator = new PrefixingQueryGenerator(Collections.singletonList(correlationAlias), joinBase, null, null); generator.setQueryBuffer(sb); expr.accept(generator); this.correlationResult = sb.toString().intern(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedBatchTupleListTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedBatchTupleListTransformer.java index b28430637a..8138228296 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedBatchTupleListTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedBatchTupleListTransformer.java @@ -44,13 +44,13 @@ public abstract class AbstractCorrelatedBatchTupleListTransformer extends AbstractCorrelatedTupleListTransformer { public static final String CORRELATION_KEY_ALIAS = "correlationKey"; - protected static final int VALUE_INDEX = 0; - protected static final int KEY_INDEX = 1; private static final String CORRELATION_PARAM_PREFIX = "correlationParam_"; protected final int batchSize; protected final boolean correlatesThis; protected final BatchCorrelationMode expectBatchCorrelationMode; + protected final int valueIndex; + protected final int keyIndex; protected String correlationParamName; protected String correlationSelectExpression; @@ -65,6 +65,8 @@ public AbstractCorrelatedBatchTupleListTransformer(ExpressionFactory ef, Correla this.batchSize = entityViewConfiguration.getBatchSize(attributePath, defaultBatchSize); this.correlatesThis = correlatesThis; this.expectBatchCorrelationMode = entityViewConfiguration.getExpectBatchCorrelationValues(attributePath); + this.valueIndex = correlator.getElementOffset(); + this.keyIndex = valueIndex + 1; } private String generateCorrelationParamName() { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedSubselectTupleListTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedSubselectTupleListTransformer.java index b1671d8211..0448e103f9 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedSubselectTupleListTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/AbstractCorrelatedSubselectTupleListTransformer.java @@ -45,9 +45,6 @@ */ public abstract class AbstractCorrelatedSubselectTupleListTransformer extends AbstractCorrelatedTupleListTransformer { - protected static final int VALUE_INDEX = 0; - protected static final int VIEW_INDEX = 1; - protected final EntityViewManagerImpl evm; protected final String viewRootAlias; protected final String viewRootIdExpression; @@ -58,6 +55,8 @@ public abstract class AbstractCorrelatedSubselectTupleListTransformer extends Ab protected final int maximumViewMapperCount; protected final String correlationBasisExpression; protected final String correlationKeyExpression; + protected final int valueIndex; + protected final int viewIndex; protected int keyIndex; protected FullQueryBuilder criteriaBuilder; @@ -77,6 +76,8 @@ public AbstractCorrelatedSubselectTupleListTransformer(ExpressionFactory ef, Cor this.maximumViewMapperCount = Math.max(1, Math.max(viewRootIdMapperCount, embeddingViewIdMapperCount)); this.correlationBasisExpression = correlationBasisExpression; this.correlationKeyExpression = correlationKeyExpression; + this.valueIndex = correlator.getElementOffset(); + this.viewIndex = valueIndex + 1; } private static int viewIdMapperCount(ManagedViewType viewRootType) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/BasicCorrelator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/BasicCorrelator.java index b1df21a6da..ae4e60d6db 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/BasicCorrelator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/BasicCorrelator.java @@ -28,6 +28,11 @@ */ public final class BasicCorrelator implements Correlator { + @Override + public int getElementOffset() { + return 0; + } + @Override public ObjectBuilder finish(FullQueryBuilder criteriaBuilder, EntityViewConfiguration entityViewConfiguration, int tupleSuffix, String correlationRoot, EmbeddingViewJpqlMacro embeddingViewJpqlMacro) { criteriaBuilder.select(correlationRoot); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionBatchTupleListTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionBatchTupleListTransformer.java index 0c9165e046..aded95e169 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionBatchTupleListTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionBatchTupleListTransformer.java @@ -57,14 +57,14 @@ protected void populateResult(Map correlationValues, Objec Map> collections = new HashMap>(list.size()); for (int i = 0; i < list.size(); i++) { Object[] element = (Object[]) list.get(i); - Collection result = collections.get(element[KEY_INDEX]); + Collection result = collections.get(element[keyIndex]); if (result == null) { result = (Collection) createDefaultResult(); - collections.put(element[KEY_INDEX], result); + collections.put(element[keyIndex], result); } - if (element[VALUE_INDEX] != null) { - add(result, element[VALUE_INDEX]); + if (element[valueIndex] != null) { + add(result, element[valueIndex]); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionSubselectTupleListTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionSubselectTupleListTransformer.java index 3753d113b0..73f047ee6c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionSubselectTupleListTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedCollectionSubselectTupleListTransformer.java @@ -55,10 +55,10 @@ protected void populateResult(Map> correlation collections = new HashMap<>(list.size()); for (int i = 0; i < list.size(); i++) { Object[] element = list.get(i); - Map> viewRootResult = collections.get(element[VIEW_INDEX]); + Map> viewRootResult = collections.get(element[viewIndex]); if (viewRootResult == null) { viewRootResult = new HashMap<>(); - collections.put(element[VIEW_INDEX], viewRootResult); + collections.put(element[viewIndex], viewRootResult); } Collection result = viewRootResult.get(element[keyIndex]); if (result == null) { @@ -66,8 +66,8 @@ protected void populateResult(Map> correlation viewRootResult.put(element[keyIndex], result); } - if (element[VALUE_INDEX] != null) { - add(result, element[VALUE_INDEX]); + if (element[valueIndex] != null) { + add(result, element[valueIndex]); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularBatchTupleListTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularBatchTupleListTransformer.java index 42ce6ed1e1..6b023ed42b 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularBatchTupleListTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularBatchTupleListTransformer.java @@ -52,7 +52,7 @@ protected void populateResult(Map correlationValues, Objec } } else { for (Object[] element : (List) (List) list) { - correlationValues.get(element[KEY_INDEX]).onResult(element[VALUE_INDEX], this); + correlationValues.get(element[keyIndex]).onResult(element[valueIndex], this); } } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularSubselectTupleListTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularSubselectTupleListTransformer.java index e65c29149e..95bca07d8a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularSubselectTupleListTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/CorrelatedSingularSubselectTupleListTransformer.java @@ -40,7 +40,7 @@ public CorrelatedSingularSubselectTupleListTransformer(ExpressionFactory ef, Cor @Override protected void populateResult(Map> correlationValues, List list) { for (Object[] element : (List) (List) list) { - correlationValues.get(element[VIEW_INDEX]).get(element[keyIndex]).onResult(element[VALUE_INDEX], this); + correlationValues.get(element[viewIndex]).get(element[keyIndex]).onResult(element[valueIndex], this); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/Correlator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/Correlator.java index b32b42c31a..03c97206db 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/Correlator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/Correlator.java @@ -28,6 +28,8 @@ */ public interface Correlator { + public int getElementOffset(); + public ObjectBuilder finish(FullQueryBuilder criteriaBuilder, EntityViewConfiguration entityViewConfiguration, int tupleSuffix, String correlationRoot, EmbeddingViewJpqlMacro embeddingViewJpqlMacro); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/JoinCorrelationBuilder.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/JoinCorrelationBuilder.java index 424180aba8..511278038e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/JoinCorrelationBuilder.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/JoinCorrelationBuilder.java @@ -17,10 +17,12 @@ package com.blazebit.persistence.view.impl.objectbuilder.transformer.correlation; import com.blazebit.persistence.CorrelationQueryBuilder; +import com.blazebit.persistence.FromProvider; import com.blazebit.persistence.FullQueryBuilder; import com.blazebit.persistence.JoinOnBuilder; import com.blazebit.persistence.view.CorrelationBuilder; +import javax.persistence.metamodel.EntityType; import java.util.Map; /** @@ -52,6 +54,11 @@ public T getService(Class serviceClass) { return criteriaBuilder.getService(serviceClass); } + @Override + public FromProvider getCorrelationFromProvider() { + return criteriaBuilder; + } + @Override public String getCorrelationAlias() { return correlationAlias; @@ -72,4 +79,18 @@ public JoinOnBuilder correlate(Class entityClass) { return (JoinOnBuilder) (JoinOnBuilder) criteriaBuilder.leftJoinOn(joinBase, entityClass, correlationAlias); } + @Override + public JoinOnBuilder correlate(EntityType entityType) { + if (correlated) { + throw new IllegalArgumentException("Can not correlate with multiple entity classes!"); + } + + // Basic element has an alias, subviews don't + if (selectAlias != null) { + criteriaBuilder.select(correlationResult, selectAlias); + } + + correlated = true; + return (JoinOnBuilder) (JoinOnBuilder) criteriaBuilder.leftJoinOn(joinBase, entityType, correlationAlias); + } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubqueryCorrelationBuilder.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubqueryCorrelationBuilder.java index a163bcc60f..c11b17c42c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubqueryCorrelationBuilder.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubqueryCorrelationBuilder.java @@ -17,10 +17,13 @@ package com.blazebit.persistence.view.impl.objectbuilder.transformer.correlation; import com.blazebit.persistence.CorrelationQueryBuilder; +import com.blazebit.persistence.FromProvider; import com.blazebit.persistence.FullQueryBuilder; import com.blazebit.persistence.JoinOnBuilder; import com.blazebit.persistence.view.CorrelationBuilder; +import javax.persistence.metamodel.EntityType; + /** * * @author Christian Beikov @@ -54,6 +57,11 @@ public T getService(Class serviceClass) { return criteriaBuilder.getService(serviceClass); } + @Override + public FromProvider getCorrelationFromProvider() { + return criteriaBuilder; + } + @Override public String getCorrelationAlias() { return correlationAlias; @@ -91,4 +99,31 @@ public JoinOnBuilder correlate(Class entityClass) { return correlationBuilder; } + @Override + public JoinOnBuilder correlate(EntityType entityType) { + if (correlationRoot != null) { + throw new IllegalArgumentException("Can not correlate with multiple entity classes!"); + } + + JoinOnBuilder correlationBuilder; + if (batchSize > 1) { + if (correlationBasisEntity != null) { + criteriaBuilder.fromIdentifiableValues(correlationBasisEntity, correlationKeyAlias, batchSize); + } else { + criteriaBuilder.fromValues(correlationBasisType, correlationKeyAlias, batchSize); + } + + correlationBuilder = (JoinOnBuilder) (JoinOnBuilder) criteriaBuilder.innerJoinOn(entityType, correlationAlias); + } else { + if (innerJoin) { + correlationBuilder = (JoinOnBuilder) (JoinOnBuilder) criteriaBuilder.innerJoinOn(entityType, correlationAlias); + } else { + criteriaBuilder.from(entityType, correlationAlias); + correlationBuilder = criteriaBuilder.getService(JoinOnBuilder.class); + } + } + + this.correlationRoot = correlationResult; + return correlationBuilder; + } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubviewCorrelator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubviewCorrelator.java index 4d24398241..ff6e84dae7 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubviewCorrelator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/correlation/SubviewCorrelator.java @@ -45,6 +45,11 @@ public SubviewCorrelator(ManagedViewTypeImplementor managedViewType, MappingC this.attributePath = attributePath; } + @Override + public int getElementOffset() { + return managedViewType.getInheritanceSubtypeConfiguration(null).hasSubtypes() ? 1 : 0; + } + @Override @SuppressWarnings({"rawtypes", "unchecked"}) public ObjectBuilder finish(FullQueryBuilder criteriaBuilder, EntityViewConfiguration entityViewConfiguration, int tupleSuffix, String correlationRoot, EmbeddingViewJpqlMacro embeddingViewJpqlMacro) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java index 64484f4289..9fa6ea189d 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java @@ -281,9 +281,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } else { if (entityAttributeMapper == null) { // We have a correlation mapping here - if (recordingCollection.hasActions()) { - recordingCollection.resetActions(context); - } + recordingCollection.resetActions(context); } List embeddables = null; @@ -300,9 +298,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer if (initial instanceof RecordingCollection) { initial = (V) ((RecordingCollection) initial).getInitialVersion(); } - if (recordingCollection.hasActions()) { - recordingCollection.resetActions(context); - } + recordingCollection.resetActions(context); // If the initial object was null like it happens during full flushing, we can only replace the collection if (initial == null) { replaceCollection(context, ownerView, view, null, current, FlushStrategy.QUERY); @@ -385,6 +381,9 @@ protected boolean deleteElements(UpdateContext context, Object ownerView, Object if (entityIdAttributeName != null) { removedObjects = appendRemoveSpecific(context, deleteCb, fusedCollectionActions); removedAll = false; + if (removedObjects.isEmpty()) { + deleteCb = null; + } } } } else { @@ -541,7 +540,9 @@ protected void flushCollectionOperations(UpdateContext context, Object ownerView @SuppressWarnings("unchecked") public boolean flushEntity(UpdateContext context, E entity, Object ownerView, Object view, V value, Runnable postReplaceListener) { if (flushOperation != null) { - value = replaceWithRecordingCollection(context, view, value, collectionActions); + if (!(value instanceof RecordingCollection)) { + value = replaceWithRecordingCollection(context, view, value, collectionActions); + } invokeFlushOperation(context, ownerView, view, entity, value); return true; } @@ -606,7 +607,7 @@ public boolean flushEntity(UpdateContext context, E entity, Object ownerView, Ob if (!replace) { if (entityAttributeMapper == null) { // When having a correlated attribute, we consider is being dirty when it changed - wasDirty |= recordingCollection.hasActions(); + wasDirty |= !recordingCollection.resetActions(context).isEmpty(); } else { Collection collection = (Collection) entityAttributeMapper.getValue(entity); if (collection == null) { @@ -693,6 +694,9 @@ public boolean flushEntity(UpdateContext context, E entity, Object ownerView, Ob } if (replace) { + if (isRecording) { + ((RecordingCollection, ?>) value).resetActions(context); + } replaceCollection(context, ownerView, view, entity, value, FlushStrategy.ENTITY); return true; } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java index 2276394b4d..5ede62caee 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java @@ -56,9 +56,17 @@ public FusedCollectionIndexActions(List> collectionActio List> trimmedObjectMap = collectionAction.getTrimmedObjectEntries(); for (Map.Entry entry : removedObjectMap) { - int index = applyIndexTranslations(translateOperations, -entry.getValue()); - RemoveOperation removeOperation = new RemoveOperation(index, entry.getKey()); - addTranslateOperation(translateOperations, index, Integer.MAX_VALUE, -1, removeOperation, null); + if (appendIndex <= entry.getValue()) { + int indexToRemove = entry.getValue() - appendIndex; + appendedObjects.remove(indexToRemove); + if (appendedObjects.isEmpty()) { + appendIndex = Integer.MAX_VALUE; + } + } else { + int index = applyIndexTranslations(translateOperations, -entry.getValue()); + RemoveOperation removeOperation = new RemoveOperation(index, entry.getKey()); + addTranslateOperation(translateOperations, index, Integer.MAX_VALUE, -1, removeOperation, null); + } } for (Map.Entry entry : trimmedObjectMap) { int index = applyIndexTranslations(translateOperations, -entry.getValue()); @@ -72,8 +80,35 @@ public FusedCollectionIndexActions(List> collectionActio replaceOperations.add(replaceOperation); addTranslateOperation(translateOperations, index, Integer.MAX_VALUE, 1, null, replaceOperation); } - for (Map.Entry entry : appendedObjectMap) { - appendIndex = Math.min(entry.getValue(), appendIndex); + OUTER: for (Map.Entry entry : appendedObjectMap) { + int index = entry.getValue(); + for (int i = 0; i < translateOperations.size(); i++) { + IndexTranslateOperation translateOperation = translateOperations.get(i); + // We look at translate operations that removes the index range within which the append is + if (translateOperation.offset == -1 && translateOperation.startIndex <= index && index <= translateOperation.endIndex) { + Iterator iterator = translateOperation.removeOperations.iterator(); + while (iterator.hasNext()) { + RemoveOperation removeOperation = iterator.next(); + // Find the remove operation for the current append index + if (index == removeOperation.index) { + // If we readd an object we removed before + if (removeOperation.removedObject == entry.getKey()) { + // Drop the remove operation + iterator.remove(); + // Remove the translate if possible + if (translateOperation.removeOperations.isEmpty()) { + translateOperations.remove(i); + } + // Also skip adding the append + continue OUTER; + } + break; + } + } + break; + } + } + appendIndex = Math.min(index, appendIndex); appendedObjects.add(entry.getKey()); } } @@ -141,7 +176,7 @@ public FusedCollectionIndexActions(List> collectionActio private static void addTranslateOperation(List translateOperations, int startIndex, int endIndex, int offset, RemoveOperation removeOperation, ReplaceOperation replaceOperation) { if (translateOperations.isEmpty()) { - translateOperations.add(new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? Collections.emptyList() : Collections.singletonList(removeOperation))); + translateOperations.add(new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? new ArrayList() : new ArrayList<>(Collections.singletonList(removeOperation)))); } else { for (int i = 0; i < translateOperations.size(); i++) { IndexTranslateOperation indexTranslateOperation = translateOperations.get(i); @@ -167,13 +202,13 @@ private static void addTranslateOperation(List translat return; } else { translateOperations.set(i, new IndexTranslateOperation(indexTranslateOperation.startIndex, startIndex, indexTranslateOperation.offset, indexTranslateOperation.removeOperations)); - translateOperations.add(i + 1, new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? Collections.emptyList() : Collections.singletonList(removeOperation))); + translateOperations.add(i + 1, new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? new ArrayList() : new ArrayList<>(Collections.singletonList(removeOperation)))); return; } } } - translateOperations.add(new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? Collections.emptyList() : Collections.singletonList(removeOperation))); + translateOperations.add(new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? new ArrayList() : new ArrayList<>(Collections.singletonList(removeOperation)))); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedMapActions.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedMapActions.java index 158da12590..5f82dbc1e0 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedMapActions.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedMapActions.java @@ -74,6 +74,7 @@ public FusedMapActions(ViewToEntityMapper keyViewToEntityMapper, List addedElements, MutableStateTrackable element) { + for (Object addedElement : addedElements) { + if (addedElement == element) { + return true; + } + } + + return false; + } + @Override protected void invokeCollectionAction(UpdateContext context, Object ownerView, Object view, V targetCollection, Object value, List> collectionActions) { if (mapping == null) { @@ -291,18 +302,16 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } else { boolean isRecording = current instanceof RecordingMap; if (isRecording) { - RecordingMap, ?, ?> recordingCollection = (RecordingMap, ?, ?>) current; + RecordingMap, ?, ?> recordingMap = (RecordingMap, ?, ?>) current; if (entityAttributeMapper == null) { // We have a correlation mapping here - if (recordingCollection.hasActions()) { - recordingCollection.resetActions(context); - } + recordingMap.resetActions(context); } Map embeddables = null; if (elementDescriptor.shouldFlushMutations()) { if (elementDescriptor.shouldJpaPersistOrMerge()) { - mergeAndRequeue(context, recordingCollection, (Map) recordingCollection.getDelegate()); + mergeAndRequeue(context, recordingMap, (Map) recordingMap.getDelegate()); } else if (elementDescriptor.isSubview() && (elementDescriptor.isIdentifiable() || isIndexed())) { embeddables = flushCollectionViewElements(context, current); } @@ -313,9 +322,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer if (initial instanceof RecordingMap) { initial = (V) ((RecordingMap) initial).getInitialVersion(); } - if (recordingCollection.hasActions()) { - recordingCollection.resetActions(context); - } + recordingMap.resetActions(context); // If the initial object was null like it happens during full flushing, we can only replace the collection if (initial == null) { replaceCollection(context, ownerView, view, null, current, FlushStrategy.QUERY); @@ -384,6 +391,9 @@ protected boolean deleteElements(UpdateContext context, Object ownerView, Object if (removeSpecific) { removedObjects = appendRemoveSpecific(context, deleteCb, fusedCollectionActions); removedAll = false; + if (removedObjects.isEmpty()) { + deleteCb = null; + } } } else { removedAll = false; @@ -628,7 +638,9 @@ private Map flushCollectionViewElements(UpdateContext context, V @SuppressWarnings("unchecked") public boolean flushEntity(UpdateContext context, E entity, Object ownerView, Object view, V value, Runnable postReplaceListener) { if (flushOperation != null) { - value = replaceWithRecordingCollection(context, view, value, collectionActions); + if (!(value instanceof RecordingMap)) { + value = replaceWithRecordingCollection(context, view, value, collectionActions); + } invokeFlushOperation(context, ownerView, view, entity, value); return true; } @@ -689,7 +701,7 @@ public boolean flushEntity(UpdateContext context, E entity, Object ownerView, Ob if (!replace) { if (entityAttributeMapper == null) { // When having a correlated attribute, we consider is being dirty when it changed - wasDirty |= recordingMap.hasActions(); + wasDirty |= !recordingMap.resetActions(context).isEmpty(); } else { Map map = (Map) entityAttributeMapper.getValue(entity); if (map == null) { @@ -777,6 +789,9 @@ public boolean flushEntity(UpdateContext context, E entity, Object ownerView, Ob } if (replace) { + if (isRecording) { + ((RecordingMap, ?, ?>) value).resetActions(context); + } replaceCollection(context, ownerView, view, entity, value, FlushStrategy.ENTITY); return true; } diff --git a/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java b/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java index 97f7c69d0d..aab78d6462 100644 --- a/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java +++ b/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java @@ -18,10 +18,12 @@ import com.blazebit.persistence.view.impl.collection.ListAction; import com.blazebit.persistence.view.impl.collection.ListAddAction; +import com.blazebit.persistence.view.impl.collection.ListAddAllAction; import com.blazebit.persistence.view.impl.collection.ListRemoveAction; import org.junit.Assert; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -95,4 +97,34 @@ public void testMultipleRemoveRanges() { Assert.assertEquals(0, indexActions.getAddCount()); Assert.assertEquals(2, indexActions.getUpdateCount()); } + + @Test + public void testAddClearAndReadd() { + List objects = new ArrayList<>(Arrays.asList("o1", "o2")); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListAddAction<>(1, true, objects.get(1)), + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(0, false, objects), + new ListAddAllAction<>(0, true, objects) + )); + Assert.assertEquals(0, indexActions.getRemoveCount()); + Assert.assertEquals(1, indexActions.getAddCount()); + Assert.assertEquals(0, indexActions.getUpdateCount()); + } + + @Test + public void testAddClearAndReadd2() { + List objects = new ArrayList<>(Arrays.asList("o1", "o2", "o3", "o4")); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListAddAction<>(3, true, objects.get(3)), + new ListRemoveAction<>(3, false, objects), + new ListRemoveAction<>(2, false, objects), + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(0, false, objects), + new ListAddAllAction<>(0, true, objects) + )); + Assert.assertEquals(0, indexActions.getRemoveCount()); + Assert.assertEquals(1, indexActions.getAddCount()); + Assert.assertEquals(0, indexActions.getUpdateCount()); + } } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/TypeConverterTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/TypeConverterTest.java index b6f449a1ad..20e613e577 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/TypeConverterTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/TypeConverterTest.java @@ -26,6 +26,7 @@ import com.blazebit.persistence.view.spi.EntityViewConfiguration; import com.blazebit.persistence.view.spi.type.TypeConverter; import com.blazebit.persistence.view.testsuite.AbstractEntityViewTest; +import com.blazebit.persistence.view.testsuite.convert.type.model.DocumentSubqueryTypeConverterView; import com.blazebit.persistence.view.testsuite.convert.type.model.DocumentTypeConverterView; import org.junit.Assert; import org.junit.Before; @@ -132,4 +133,32 @@ public Long convertToUnderlyingType(String object) { assertEquals("1", documentView.getAge()); } + + @Test + public void testTypeConverterSubquery() { + EntityViewConfiguration cfg = EntityViews.createDefaultConfiguration(); + cfg.addEntityView(DocumentSubqueryTypeConverterView.class); + cfg.registerTypeConverter(Long.class, String.class, new TypeConverter() { + @Override + public Class getUnderlyingType(Class owningClass, Type declaredType) { + return String.class; + } + + @Override + public String convertToViewType(Long object) { + return Long.toString(object); + } + + @Override + public Long convertToUnderlyingType(String object) { + return Long.valueOf(object); + } + }); + evm = cfg.createEntityViewManager(cbf); + CriteriaBuilder criteria = cbf.create(em, Document.class); + DocumentSubqueryTypeConverterView documentView = evm.applySetting(EntityViewSetting.create(DocumentSubqueryTypeConverterView.class), criteria) + .getSingleResult(); + + assertEquals("1", documentView.getAge()); + } } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/model/DocumentSubqueryTypeConverterView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/model/DocumentSubqueryTypeConverterView.java new file mode 100644 index 0000000000..6fc12693dd --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/type/model/DocumentSubqueryTypeConverterView.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 - 2018 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.view.testsuite.convert.type.model; + +import com.blazebit.persistence.SubqueryInitiator; +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.IdMapping; +import com.blazebit.persistence.view.MappingSubquery; +import com.blazebit.persistence.view.SubqueryProvider; + +import java.io.Serializable; + +/** + * + * @author Christian Beikov + * @since 1.2.0 + */ +@EntityView(Document.class) +public interface DocumentSubqueryTypeConverterView extends Serializable { + + @IdMapping + public Long getId(); + + @MappingSubquery(value = SubqueryCorrelator.class, subqueryAlias = "q", expression = "COALESCE(q, 0L)") + public String getAge(); + + public static class SubqueryCorrelator implements SubqueryProvider { + @Override + public T createSubquery(SubqueryInitiator subqueryInitiator) { + return subqueryInitiator.from(Document.class) + .select("age") + .where("EMBEDDING_VIEW(id)").eqExpression("id") + .end(); + } + } + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java index 135b057097..eb9a623780 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java @@ -126,7 +126,7 @@ public void testUpdateAddToCollection() { builder.validate(); - assertNoUpdateAndReload(docView, true); + assertNoUpdateAndReload(docView); assertEquals(doc1.getPartners().size(), docView.getPartners().size() - 1); docView.getPartners().remove(newPerson); assertSubviewEquals(doc1.getPartners(), docView.getPartners()); @@ -196,7 +196,7 @@ public void testUpdateAddToCollectionAndModifySubview() { } builder.validate(); - assertNoUpdateAndReload(docView, true); + assertNoUpdateAndReload(docView); assertEquals(doc1.getPartners().size(), docView.getPartners().size() - 1); docView.getPartners().remove(newPerson); assertEquals(doc1.getPartners().size(), docView.getPartners().size()); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java index 04d1ae45ed..89d96e0616 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.List; import static org.junit.Assert.*; @@ -217,6 +218,26 @@ public void testUpdateAddToCollectionAndModify() { // When newPerson.setName("newPerson"); docView.getPeople().add(newPerson); + verifyUpdateAddToCollectionAndModify(docView); + } + + @Test + public void testClearUpdateAddToCollectionAndModify() { + // Given + final UpdatableDocumentWithCollectionsView docView = getDoc1View(); + UpdatablePersonView newPerson = getP2View(UpdatablePersonView.class); + clearQueries(); + + // When + newPerson.setName("newPerson"); + docView.getPeople().add(newPerson); + List copy = new ArrayList<>(docView.getPeople()); + docView.getPeople().clear(); + docView.getPeople().addAll(copy); + verifyUpdateAddToCollectionAndModify(docView); + } + + public void verifyUpdateAddToCollectionAndModify(UpdatableDocumentWithCollectionsView docView) { update(docView); // Then diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java index 38bac05bac..df2dc0be89 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java @@ -195,6 +195,26 @@ public void testUpdateAddToCollectionAndModify() { // When newPerson.setName("newPerson"); docView.getContacts().put(2, newPerson); + verifyUpdateAddToCollectionAndModify(docView); + } + + @Test + public void testClearUpdateAddToCollectionAndModify() { + // Given + final UpdatableDocumentWithMapsView docView = getDoc1View(); + UpdatablePersonView newPerson = getP2View(UpdatablePersonView.class); + clearQueries(); + + // When + newPerson.setName("newPerson"); + docView.getContacts().put(2, newPerson); + HashMap copy = new HashMap<>(docView.getContacts()); + docView.getContacts().clear(); + docView.getContacts().putAll(copy); + verifyUpdateAddToCollectionAndModify(docView); + } + + private void verifyUpdateAddToCollectionAndModify(UpdatableDocumentWithMapsView docView) { update(docView); // Then diff --git a/parent/pom.xml b/parent/pom.xml index 593934deb1..355fc0bb64 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -57,7 +57,7 @@ 5.2.9.Final 5.3.1.Final - 5.4.0-SNAPSHOT + 5.4.0.CR1 5.3.1.Final