From 637f19a8f3a8f8aa66e8734af5e0242ec107c6c1 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 1 Nov 2018 11:11:10 +0100 Subject: [PATCH] Implemented plural only embeddable owner VALUES clause support and add VALUES LIKE clause --- .../persistence/spi/JpaMetamodelAccessor.java | 8 + .../impl/AbstractCommonQueryBuilder.java | 294 +++++++++++------- .../impl/EmbeddableSplittingVisitor.java | 6 +- .../persistence/impl/JoinManager.java | 237 ++++++++------ .../blazebit/persistence/impl/JoinNode.java | 86 +++-- .../blazebit/persistence/impl/JpaUtils.java | 23 +- .../persistence/impl/ParameterManager.java | 24 +- .../impl/ValuesParameterBinder.java | 14 +- .../impl/function/entity/EntityFunction.java | 2 +- .../impl/query/CustomQuerySpecification.java | 24 +- .../impl/query/EntityFunctionNode.java | 20 +- .../transform/SizeTransformationVisitor.java | 2 +- .../persistence/impl/util/SqlUtils.java | 73 ++++- .../parser/ListIndexAttribute.java | 6 + .../persistence/parser/MapEntryAttribute.java | 6 + .../persistence/parser/MapKeyAttribute.java | 6 + .../parser/QualifiedAttribute.java | 4 + .../testsuite/entity/Document.java | 8 +- .../entity/NameObjectContainer2.java | 88 ++++++ .../testsuite/CollectionRoleInsertTest.java | 27 +- .../persistence/testsuite/GroupByTest.java | 9 +- .../testsuite/ValuesClauseTest.java | 194 +++++++++++- .../view/impl/metamodel/AttributeMapping.java | 10 + .../metamodel/MethodAttributeMapping.java | 14 +- .../view/impl/metamodel/ViewMappingImpl.java | 2 + .../ViewTypeObjectBuilderTemplate.java | 10 +- .../UpdatableSubviewTupleTransformer.java | 3 + .../AbstractEntityViewUpdateDocumentTest.java | 4 - ...tyViewUpdateNestedMutableFlatViewTest.java | 3 +- .../mutable/model/UpdatableDocumentView.java | 4 +- .../UpdatableNameObjectContainerView2.java | 39 +++ .../AbstractEntityViewRemoveDocumentTest.java | 4 - ...actEntityViewOrphanRemoveDocumentTest.java | 2 - .../UpdatableDocumentForOneToOneView.java | 2 +- .../unmapped/model/UpdatableDocumentView.java | 2 + .../src/test/resources/logging.properties | 1 + .../DataNucleus51JpaMetamodelAccessor.java | 19 ++ .../DataNucleusJpaMetamodelAccessor.java | 19 +- .../hibernate/base/HibernateJpaProvider.java | 83 +++-- .../jpa/JpaMetamodelAccessorImpl.java | 5 + 40 files changed, 1049 insertions(+), 338 deletions(-) create mode 100644 core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/NameObjectContainer2.java create mode 100644 entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableNameObjectContainerView2.java diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/JpaMetamodelAccessor.java b/core/api/src/main/java/com/blazebit/persistence/spi/JpaMetamodelAccessor.java index 476336251c..f84f121b04 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/JpaMetamodelAccessor.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/JpaMetamodelAccessor.java @@ -68,4 +68,12 @@ public interface JpaMetamodelAccessor { * @return Whether the attribute is composite */ boolean isCompositeNode(Attribute attr); + + /** + * Returns whether the given attribute is an element collection. + * + * @param attribute The attribute to check + * @return true if the attribute is an element collection, false otherwise + */ + boolean isElementCollection(Attribute attribute); } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java index 431a3a260e..dc55971c0c 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java @@ -719,15 +719,12 @@ public BuilderType fromIdentifiableValues(Class valueClass, String alias, int } } - Class valuesClazz = valueClass; ManagedType type = mainQuery.metamodel.getManagedType(valueClass); - String treatFunction = null; - String castedParameter = null; - if (!(type instanceof IdentifiableType)) { + if (!JpaMetamodelUtils.isIdentifiable(type)) { throw new IllegalArgumentException("Only identifiable types allowed!"); } - joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, treatFunction, castedParameter, true, null, null); + joinManager.addRootValues(valueClass, valueClass, alias, valueCount, null, null, true, true, null, null, null, null); fromClassExplicitlySet = true; return (BuilderType) this; @@ -735,109 +732,112 @@ public BuilderType fromIdentifiableValues(Class valueClass, String alias, int public BuilderType fromValues(Class valueClass, String alias, int valueCount) { ManagedType type = mainQuery.metamodel.getManagedType(valueClass); - String sqlType = null; if (type == null) { - sqlType = mainQuery.dbmsDialect.getSqlType(valueClass); + String sqlType = mainQuery.dbmsDialect.getSqlType(valueClass); if (sqlType == null) { throw new IllegalArgumentException("The basic type " + valueClass.getSimpleName() + " has no column type registered in the DbmsDialect '" + mainQuery.dbmsDialect.getClass().getName() + "'! Register a column type or consider using the fromValues variant that extracts column types from entity attributes!"); } + String typeName = cbf.getNamedTypes().get(valueClass); + if (typeName == null) { + throw new IllegalArgumentException("Unsupported non-managed type for VALUES clause: " + valueClass.getName() + ". You can register the type via com.blazebit.persistence.spi.CriteriaBuilderConfiguration.registerNamedType. Please report this so that we can add the type definition as well!"); + } + + String castedParameter = mainQuery.dbmsDialect.cast("?", sqlType); + ExtendedAttribute valuesLikeAttribute = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, ValuesEntity.class).getAttribute("value"); + + prepareFromModification(); + joinManager.addRootValues(ValuesEntity.class, valueClass, alias, valueCount, typeName, castedParameter, false, true, "value", valuesLikeAttribute, null, null); + } else if (type instanceof EntityType) { + prepareFromModification(); + joinManager.addRootValues(valueClass, valueClass, alias, valueCount, null, null, false, true, null, null, null, null); + } else { + ExtendedManagedType extendedManagedType = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, valueClass); + Map.Entry, String> entry = extendedManagedType.getEmbeddableSingularOwner(); + boolean singular = true; + if (entry == null) { + singular = false; + entry = extendedManagedType.getEmbeddablePluralOwner(); + } + if (entry == null) { + throw new IllegalArgumentException("Unsupported use of embeddable type [" + valueClass + "] for values clause! Use the entity type and fromIdentifiableValues instead or introduce a CTE entity containing just the embeddable to be able to query it!"); + } + Class valueHolderEntityClass = entry.getKey().getJavaType(); + String valuesLikeAttributeName = entry.getValue(); + ExtendedAttribute valuesLikeAttribute = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, valueHolderEntityClass).getAttribute(valuesLikeAttributeName); + prepareFromModification(); + joinManager.addRootValues(valueHolderEntityClass, valueClass, alias, valueCount, null, null, false, singular, valuesLikeAttributeName, valuesLikeAttribute, null, null); } - return fromValues0(valueClass, sqlType, alias, valueCount, null); + + fromClassExplicitlySet = true; + return (BuilderType) this; } - public BuilderType fromValues(Class entityBaseClass, String attributeName, String alias, int valueCount) { + public BuilderType fromValues(Class entityBaseClass, String originalAttributeName, String alias, int valueCount) { ExtendedManagedType extendedManagedType = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityBaseClass); String keyFunction = "key("; String indexFunction = "index("; - ExtendedAttribute attribute; + String attributeName = originalAttributeName; + ExtendedAttribute valuesLikeAttribute; Class elementClass; boolean index = false; - String valueLikeClause; + boolean singular = false; + String valuesLikeClause; + String qualificationExpression; if (attributeName.regionMatches(true, 0, keyFunction, 0, keyFunction.length())) { attributeName = attributeName.substring(keyFunction.length(), attributeName.length() - 1); - attribute = extendedManagedType.getAttribute(attributeName); + valuesLikeAttribute = extendedManagedType.getAttribute(attributeName); index = true; - if (attribute.getAttributePath().size() > 1) { - ExtendedAttribute superAttr = extendedManagedType.getAttribute(attribute.getAttributePathString().substring(0, attribute.getAttributePathString().lastIndexOf('.'))); - elementClass = JpaMetamodelUtils.resolveKeyClass(superAttr.getElementClass(), (MapAttribute) attribute.getAttribute()); + if (valuesLikeAttribute.getAttributePath().size() > 1) { + ExtendedAttribute superAttr = extendedManagedType.getAttribute(valuesLikeAttribute.getAttributePathString().substring(0, valuesLikeAttribute.getAttributePathString().lastIndexOf('.'))); + elementClass = JpaMetamodelUtils.resolveKeyClass(superAttr.getElementClass(), (MapAttribute) valuesLikeAttribute.getAttribute()); } else { - elementClass = JpaMetamodelUtils.resolveKeyClass(entityBaseClass, (MapAttribute) attribute.getAttribute()); + elementClass = JpaMetamodelUtils.resolveKeyClass(entityBaseClass, (MapAttribute) valuesLikeAttribute.getAttribute()); } - valueLikeClause = "KEY(" + ((EntityType) extendedManagedType.getType()).getName() + "." + attributeName + ")"; + valuesLikeClause = "KEY(" + ((EntityType) extendedManagedType.getType()).getName() + "." + attributeName + ")"; + qualificationExpression = "KEY"; } else if (attributeName.regionMatches(true, 0, indexFunction, 0, indexFunction.length())) { - attributeName = attributeName.substring(keyFunction.length(), attributeName.length() - 1); - attribute = extendedManagedType.getAttribute(attributeName); + attributeName = attributeName.substring(indexFunction.length(), attributeName.length() - 1); + valuesLikeAttribute = extendedManagedType.getAttribute(attributeName); index = true; elementClass = Integer.class; - valueLikeClause = "INDEX(" + ((EntityType) extendedManagedType.getType()).getName() + "." + attributeName + ")"; + valuesLikeClause = "INDEX(" + ((EntityType) extendedManagedType.getType()).getName() + "." + attributeName + ")"; + qualificationExpression = "INDEX"; } else { - attribute = extendedManagedType.getAttribute(attributeName); - elementClass = attribute.getElementClass(); - valueLikeClause = ((EntityType) extendedManagedType.getType()).getName() + "." + attributeName; - } - if (attribute.getAttribute() instanceof SingularAttribute) { - if (((SingularAttribute) attribute.getAttribute()).getType() instanceof BasicType) { - if (attribute.getColumnTypes().length != 1) { - throw new IllegalArgumentException("Unsupported VALUES clause use with multi-column attribute type " + Arrays.toString(attribute.getColumnTypes()) + "! Consider creating a synthetic type like a @CTE entity to hold this attribute and use that type via fromIdentifiableValues instead!"); + valuesLikeAttribute = extendedManagedType.getAttribute(attributeName); + elementClass = valuesLikeAttribute.getElementClass(); + valuesLikeClause = ((EntityType) extendedManagedType.getType()).getName() + "." + attributeName; + qualificationExpression = null; + } + if (valuesLikeAttribute.getAttribute() instanceof SingularAttribute) { + singular = true; + if (((SingularAttribute) valuesLikeAttribute.getAttribute()).getType() instanceof BasicType) { + if (valuesLikeAttribute.getColumnTypes().length != 1) { + throw new IllegalArgumentException("Unsupported VALUES clause use with multi-column attribute type " + Arrays.toString(valuesLikeAttribute.getColumnTypes()) + "! Consider creating a synthetic type like a @CTE entity to hold this attribute and use that type via fromIdentifiableValues instead!"); } - return fromValues0(elementClass, attribute.getColumnTypes()[0], alias, valueCount, valueLikeClause); + return fromValuesLike(entityBaseClass, elementClass, valuesLikeAttribute.getColumnTypes()[0], alias, valueCount, valuesLikeClause, valuesLikeAttribute, true, null); } } else if (index) { - Map keyColumnTypes = attribute.getJoinTable().getKeyColumnTypes(); + Map keyColumnTypes = valuesLikeAttribute.getJoinTable().getKeyColumnTypes(); if (keyColumnTypes.size() != 1) { throw new IllegalArgumentException("Unsupported VALUES clause use with multi-column attribute type " + keyColumnTypes.values() + "! Consider creating a synthetic type like a @CTE entity to hold this attribute and use that type via fromIdentifiableValues instead!"); } String columnType = keyColumnTypes.values().iterator().next(); - return fromValues0(elementClass, columnType, alias, valueCount, valueLikeClause); + return fromValuesLike(entityBaseClass, elementClass, columnType, alias, valueCount, valuesLikeClause, valuesLikeAttribute, false, qualificationExpression); } else { - if (((PluralAttribute) attribute.getAttribute()).getElementType() instanceof BasicType) { - if (attribute.getColumnTypes().length != 1) { - throw new IllegalArgumentException("Unsupported VALUES clause use with multi-column attribute type " + Arrays.toString(attribute.getColumnTypes()) + "! Consider creating a synthetic type like a @CTE entity to hold this attribute and use that type via fromIdentifiableValues instead!"); + if (((PluralAttribute) valuesLikeAttribute.getAttribute()).getElementType() instanceof BasicType) { + if (valuesLikeAttribute.getColumnTypes().length != 1) { + throw new IllegalArgumentException("Unsupported VALUES clause use with multi-column attribute type " + Arrays.toString(valuesLikeAttribute.getColumnTypes()) + "! Consider creating a synthetic type like a @CTE entity to hold this attribute and use that type via fromIdentifiableValues instead!"); } - return fromValues0(elementClass, attribute.getColumnTypes()[0], alias, valueCount, valueLikeClause); + return fromValuesLike(entityBaseClass, elementClass, valuesLikeAttribute.getColumnTypes()[0], alias, valueCount, valuesLikeClause, valuesLikeAttribute, false, null); } } - return fromValues0(elementClass, null, alias, valueCount, valueLikeClause); + return fromValuesLike(entityBaseClass, elementClass, null, alias, valueCount, valuesLikeClause, valuesLikeAttribute, singular, null); } - private BuilderType fromValues0(Class valueClass, String sqlType, String alias, int valueCount, String valueLikeClause) { - prepareForModification(ClauseType.JOIN); - if (!fromClassExplicitlySet) { - // When from is explicitly called we have to revert the implicit root - if (joinManager.getRoots().size() > 0) { - joinManager.removeRoot(); - } - } - - Class valuesClazz = valueClass; - String valueClazzAttributeName = null; - ManagedType type = mainQuery.metamodel.getManagedType(valueClass); - String typeName = null; - String castedParameter = null; - if (type == null) { - typeName = cbf.getNamedTypes().get(valueClass); - if (typeName == null) { - throw new IllegalArgumentException("Unsupported non-managed type for VALUES clause: " + valueClass.getName() + ". You can register the type via com.blazebit.persistence.spi.CriteriaBuilderConfiguration.registerNamedType. Please report this so that we can add the type definition as well!"); - } - - castedParameter = mainQuery.dbmsDialect.cast("?", sqlType); - valuesClazz = ValuesEntity.class; - } else if (!(type instanceof EntityType)) { - ExtendedManagedType extendedManagedType = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, valueClass); - Map.Entry, String> entry = extendedManagedType.getEmbeddableSingularOwner(); - boolean singular = true; - if (entry == null) { -// singular = false; -// entry = extendedManagedType.getEmbeddablePluralOwner(); - throw new IllegalArgumentException("Unsupported plural-only use of embeddable type [" + valueClass + "] for values clause! Please introduce a CTE entity containing just the embeddable to be able to query it!"); - } - if (entry == null) { - throw new IllegalArgumentException("Unsupported use of embeddable type [" + valueClass + "] for values clause! Use the entity type and fromIdentifiableValues instead or introduce a CTE entity containing just the embeddable to be able to query it!"); - } - valuesClazz = entry.getKey().getJavaType(); - valueClazzAttributeName = entry.getValue(); - } - joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, typeName, castedParameter, false, valueClazzAttributeName, valueLikeClause); + private BuilderType fromValuesLike(Class valueHolderEntityClass, Class valueClass, String sqlType, String alias, int valueCount, String valuesLikeClause, ExtendedAttribute valuesLikeAttribute, boolean valueLikeAttributeSingular, String qualificationExpression) { + prepareFromModification(); + String castedParameter = sqlType == null ? null : mainQuery.dbmsDialect.cast("?", sqlType); + joinManager.addRootValues(valueHolderEntityClass, valueClass, alias, valueCount, null, castedParameter, false, valueLikeAttributeSingular, valuesLikeAttribute.getAttributePathString(), valuesLikeAttribute, valuesLikeClause, qualificationExpression); fromClassExplicitlySet = true; return (BuilderType) this; @@ -850,14 +850,7 @@ private BuilderType from(Class clazz, String alias, DbmsModificationState sta @SuppressWarnings("unchecked") private BuilderType from(EntityType type, String alias, DbmsModificationState state) { - prepareForModification(ClauseType.JOIN); - if (!fromClassExplicitlySet) { - // When from is explicitly called we have to revert the implicit root - if (joinManager.getRoots().size() > 0) { - joinManager.removeRoot(); - } - } - + prepareFromModification(); String finalAlias = joinManager.addRoot(type, alias); fromClassExplicitlySet = true; @@ -876,6 +869,16 @@ private BuilderType from(EntityType type, String alias, DbmsModificationState return (BuilderType) this; } + private void prepareFromModification() { + prepareForModification(ClauseType.JOIN); + if (!fromClassExplicitlySet) { + // When from is explicitly called we have to revert the implicit root + if (joinManager.getRoots().size() > 0) { + joinManager.removeRoot(); + } + } + } + public Set getRoots() { return new LinkedHashSet(joinManager.getRoots()); } @@ -1918,9 +1921,9 @@ protected List getEntityFunctionNodes(Query baseQuery) { for (JoinNode node : joinManager.getEntityFunctionNodes()) { Class clazz = node.getInternalEntityType().getJavaType(); - String valueClazzAttributeName = node.getValueClazzAttributeName(); + String valueClazzAttributeName = node.getValuesLikeAttribute(); int valueCount = node.getValueCount(); - boolean identifiableReference = node.getNodeType() instanceof EntityType && node.getValuesIdName() != null; + boolean identifiableReference = node.getNodeType() instanceof EntityType && node.getValuesIdNames() != null; String rootAlias = node.getAlias(); String castedParameter = node.getValuesCastedParameter(); String[] attributes = node.getValuesAttributes(); @@ -1932,6 +1935,10 @@ protected List getEntityFunctionNodes(Query baseQuery) { String exampleQuerySql = mainQuery.cbf.getExtendedQuerySupport().getSql(mainQuery.em, valuesExampleQuery); String exampleQuerySqlAlias = mainQuery.cbf.getExtendedQuerySupport().getSqlAlias(mainQuery.em, valuesExampleQuery, "e"); + String exampleQueryCollectionSqlAlias = null; + if (!node.isValueClazzAttributeSingular()) { + exampleQueryCollectionSqlAlias = mainQuery.cbf.getExtendedQuerySupport().getSqlAlias(mainQuery.em, valuesExampleQuery, node.getValueClazzAlias("e_")); + } StringBuilder whereClauseSb = new StringBuilder(exampleQuerySql.length()); String filterNullsTableAlias = "fltr_nulls_tbl_als_"; String valuesAliases = getValuesAliases(exampleQuerySqlAlias, attributes.length, exampleQuerySql, whereClauseSb, filterNullsTableAlias, strategy, dummyTable); @@ -1962,13 +1969,33 @@ protected List getEntityFunctionNodes(Query baseQuery) { String valuesClause = valuesSb.toString(); String valuesTableSqlAlias = exampleQuerySqlAlias; + String valuesTableJoin = null; + String pluralCollectionTableAlias = null; + String pluralTableAlias = null; String syntheticPredicate = exampleQuerySql.substring(SqlUtils.indexOfWhere(exampleQuerySql) + " where ".length()); if (baseQuery != null) { valuesTableSqlAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, node.getAlias()); syntheticPredicate = syntheticPredicate.replace(exampleQuerySqlAlias, valuesTableSqlAlias); + if (exampleQueryCollectionSqlAlias != null) { + pluralTableAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, node.getValueClazzAlias(node.getAlias() + "_")); + syntheticPredicate = syntheticPredicate.replace(exampleQueryCollectionSqlAlias, pluralTableAlias); + String baseQuerySql = cbf.getExtendedQuerySupport().getSql(em, baseQuery); + int[] indexRange = SqlUtils.indexOfFullJoin(baseQuerySql, pluralTableAlias); + String baseTableAlias = " " + valuesTableSqlAlias + " "; + int baseTableAliasIndex = baseQuerySql.indexOf(baseTableAlias); + int fullJoinStartIndex = baseTableAliasIndex + baseTableAlias.length(); + if (fullJoinStartIndex != indexRange[0]) { + // TODO: find out pluralCollectionTableAlias + String onClause = " on "; + int onClauseIndex = baseQuerySql.indexOf(onClause, fullJoinStartIndex); + int[] collectionTableIndexRange = SqlUtils.rtrimBackwardsToFirstWhitespace(baseQuerySql, onClauseIndex); + pluralCollectionTableAlias = baseQuerySql.substring(collectionTableIndexRange[0], collectionTableIndexRange[1]); + } + valuesTableJoin = baseQuerySql.substring(fullJoinStartIndex, indexRange[1]); + } } - entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, node.getInternalEntityType().getName(), valuesTableSqlAlias, syntheticPredicate)); + entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, node.getInternalEntityType().getName(), valuesTableSqlAlias, pluralCollectionTableAlias, pluralTableAlias, valuesTableJoin, syntheticPredicate)); } return entityFunctionNodes; } @@ -1995,7 +2022,7 @@ private String getValuesAliases(String tableAlias, int attributeCount, String ex whereClauseSb.append(" where"); String[] columnNames = SqlUtils.getSelectItemColumns(exampleQuerySql, startIndex); - for (int i = 0; i < columnNames.length; i++) { + for (int i = 0; i < attributeCount; i++) { whereClauseSb.append(' '); if (i > 0) { whereClauseSb.append("or "); @@ -2045,46 +2072,73 @@ private Query getValuesExampleQuery(Class clazz, int valueCount, boolean iden attributeParameter[0] = mainQuery.dbmsDialect.needsCastParameters() ? castedParameter : "?"; sb.append(attributes[0]); sb.append(','); - } else if (identifiableReference) { - sb.append("e."); - String[] columnTypes = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, clazz).getAttribute(attributes[0]).getColumnTypes(); - attributeParameter[0] = getCastedParameters(new StringBuilder(), mainQuery.dbmsDialect, columnTypes); - sb.append(attributes[0]); - sb.append(','); } else { - Map mapping = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, clazz).getOwnedSingularAttributes(); + Map mapping = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, clazz).getAttributes(); StringBuilder paramBuilder = new StringBuilder(); for (int i = 0; i < attributes.length; i++) { ExtendedAttribute entry; - if (valueClazzAttributeName == null) { - entry = mapping.get(attributes[i]); + if (valuesNode.isValueClazzSimpleValue()) { + entry = mapping.get(valueClazzAttributeName); } else { - entry = mapping.get(valueClazzAttributeName + "." + attributes[i]); + entry = mapping.get(attributes[i]); } Attribute attribute = entry.getAttribute(); - String[] columnTypes = entry.getColumnTypes(); + String[] columnTypes; + if (valuesNode.getQualificationExpression() == null) { + columnTypes = entry.getColumnTypes(); + } else { + Collection types = entry.getJoinTable().getKeyColumnTypes().values(); + columnTypes = types.toArray(new String[types.size()]); + } attributeParameter[i] = getCastedParameters(paramBuilder, mainQuery.dbmsDialect, columnTypes); // When the class for which we want a VALUES clause has *ToOne relations, we need to put their ids into the select // otherwise we would fetch all of the types attributes, but the VALUES clause can only ever contain the id if (attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC && - attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.EMBEDDED) { - ManagedType managedAttributeType = mainQuery.metamodel.managedType(entry.getElementClass()); - for (Attribute attributeTypeIdAttribute : JpaMetamodelUtils.getIdAttributes((IdentifiableType) managedAttributeType)) { - sb.append("e."); - if (valueClazzAttributeName != null) { - sb.append(valueClazzAttributeName).append('.'); + attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.EMBEDDED && + valuesNode.getQualificationExpression() == null) { + ManagedType managedAttributeType = mainQuery.metamodel.getManagedType(entry.getElementClass()); + if (managedAttributeType == null || mainQuery.jpaProvider.needsElementCollectionIdCutoff() && valuesNode.getValuesLikeAttribute() != null && mapping.get(valuesNode.getValuesLikeAttribute()).getAttribute().getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + sb.append("e"); + if (valuesNode.isValueClazzAttributeSingular()) { + sb.append('.'); + sb.append(attributes[i]); + } else { + sb.append('_'); + sb.append(valueClazzAttributeName.replace('.', '_')); + sb.append(attributes[i], valueClazzAttributeName.length(), attributes[i].length()); + } + } else { + for (Attribute attributeTypeIdAttribute : JpaMetamodelUtils.getIdAttributes((IdentifiableType) managedAttributeType)) { + sb.append("e"); + if (valuesNode.isValueClazzAttributeSingular()) { + sb.append('.'); + sb.append(attributes[i]); + } else { + sb.append('_'); + sb.append(valueClazzAttributeName.replace('.', '_')); + sb.append(attributes[i], valueClazzAttributeName.length(), attributes[i].length()); + } + sb.append('.'); + sb.append(attributeTypeIdAttribute.getName()); } - sb.append(attributes[i]); - sb.append('.'); - sb.append(attributeTypeIdAttribute.getName()); } } else { - sb.append("e."); - if (valueClazzAttributeName != null) { - sb.append(valueClazzAttributeName).append('.'); + if (valuesNode.getQualificationExpression() != null) { + sb.append(valuesNode.getQualificationExpression()).append('('); + } + sb.append("e"); + if (valuesNode.isValueClazzAttributeSingular()) { + sb.append('.'); + sb.append(attributes[i]); + } else { + sb.append('_'); + sb.append(valueClazzAttributeName.replace('.', '_')); + sb.append(attributes[i], valueClazzAttributeName.length(), attributes[i].length()); + } + if (valuesNode.getQualificationExpression() != null) { + sb.append('_').append(valuesNode.getQualificationExpression().toLowerCase()).append(')'); } - sb.append(attributes[i]); } sb.append(','); @@ -2094,7 +2148,25 @@ private Query getValuesExampleQuery(Class clazz, int valueCount, boolean iden sb.setCharAt(sb.length() - 1, ' '); sb.append("FROM "); sb.append(clazz.getName()); - sb.append(" e WHERE "); + sb.append(" e"); + if (!valuesNode.isValueClazzAttributeSingular()) { + sb.append(" LEFT JOIN e."); + if (valuesNode.isValueClazzSimpleValue()) { + sb.append(valueClazzAttributeName); + } else { + sb.append(valuesNode.getValuesLikeAttribute()); + } + sb.append(" e_"); + if (valuesNode.isValueClazzSimpleValue()) { + sb.append(valueClazzAttributeName.replace('.', '_')); + } else { + sb.append(valuesNode.getValuesLikeAttribute().replace('.', '_')); + } + if (valuesNode.getQualificationExpression() != null) { + sb.append('_').append(valuesNode.getQualificationExpression().toLowerCase()); + } + } + sb.append(" WHERE "); joinManager.renderValuesClausePredicate(sb, valuesNode, "e", false); if (strategy == ValuesStrategy.SELECT_VALUES || strategy == ValuesStrategy.VALUES) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/EmbeddableSplittingVisitor.java b/core/impl/src/main/java/com/blazebit/persistence/impl/EmbeddableSplittingVisitor.java index e5d4d9c8e1..8d0c591452 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/EmbeddableSplittingVisitor.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/EmbeddableSplittingVisitor.java @@ -129,7 +129,7 @@ protected boolean collectSplittedOffExpressions(Expression expression) { } else { Map> ownedAttributes; String prefix = field; - if (baseNode.getParentTreeNode() != null && baseNode.getParentTreeNode().getAttribute().getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + if (baseNode.getParentTreeNode() != null && jpaProvider.getJpaMetamodelAccessor().isElementCollection(baseNode.getParentTreeNode().getAttribute())) { String elementCollectionPath = baseNode.getParentTreeNode().getRelationName(); ExtendedManagedType entityManagedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getParent().getEntityType()); ownedAttributes = entityManagedType.getAttributes(); @@ -141,7 +141,7 @@ protected boolean collectSplittedOffExpressions(Expression expression) { } else { ownedAttributes = managedType.getOwnedSingularAttributes(); } - orderedAttributes.addAll(JpaUtils.getEmbeddedPropertyPaths((Map>) ownedAttributes, prefix, false)); + orderedAttributes.addAll(JpaUtils.getEmbeddedPropertyPaths((Map>) ownedAttributes, prefix, false, false)); } // Signal the caller that the expression was eliminated @@ -177,6 +177,8 @@ public Boolean visit(PathExpression expr) { ExtendedManagedType managedType = metamodel.getManagedType(ExtendedManagedType.class, baseNode.getJavaType()); attr = managedType.getAttribute(pathReference.getField()).getAttribute(); + // This kind of happens when we do an entity select where the entity is split into it's component paths + // We don't want to split it here though as it won't end up in a group by clause or anything anyway if (attr instanceof PluralAttribute) { return true; } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java index 5dc4f9e2d1..7f981f254d 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java @@ -17,7 +17,6 @@ package com.blazebit.persistence.impl; import com.blazebit.lang.StringUtils; -import com.blazebit.lang.ValueRetriever; import com.blazebit.persistence.From; import com.blazebit.persistence.JoinOnBuilder; import com.blazebit.persistence.JoinType; @@ -63,8 +62,10 @@ import com.blazebit.persistence.spi.ExtendedManagedType; import com.blazebit.persistence.spi.JpaMetamodelAccessor; import com.blazebit.persistence.spi.JpaProvider; +import com.blazebit.reflection.PropertyPathExpression; import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.BasicType; import javax.persistence.metamodel.EmbeddableType; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.ListAttribute; @@ -83,7 +84,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeSet; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -325,7 +326,7 @@ private void visitKeyOrIndexExpression(PathExpression pathExpression) { JoinNode node = (JoinNode) pathExpression.getBaseNode(); Attribute attribute = node.getParentTreeNode().getAttribute(); // Exclude element collections as they are not problematic - if (attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + if (jpaProvider.getJpaMetamodelAccessor().isElementCollection(attribute)) { // There are weird mappings possible, we have to check if the attribute is a join table if (jpaProvider.getJoinTable(node.getParent().getEntityType(), attribute.getName()) != null) { keyRestrictedLeftJoins.add(node); @@ -334,44 +335,110 @@ private void visitKeyOrIndexExpression(PathExpression pathExpression) { } } - String addRootValues(Class clazz, Class valueClazz, String rootAlias, int valueCount, String typeName, String castedParameter, boolean identifiableReference, String valueClazzAttributeName, String valueLikeClause) { + String addRootValues(Class valueHolderEntityClass, Class valueClass, String rootAlias, int valueCount, String typeName, String castedParameter, boolean identifiableReference, boolean valueClazzAttributeSingular, String valuesClassAttributeName, ExtendedAttribute valuesLikeAttribute, String valueLikeClause, String qualificationExpression) { if (rootAlias == null) { - throw new IllegalArgumentException("Illegal empty alias for the VALUES clause: " + clazz.getName()); + throw new IllegalArgumentException("Illegal empty alias for the VALUES clause: " + valueHolderEntityClass.getName()); } // TODO: we should pad the value count to avoid filling query caches - EntityType entityType = mainQuery.metamodel.getEntity(clazz); - Type type = mainQuery.metamodel.type(valueClazz); - String idAttributeName = valueLikeClause; - Set> attributeSet; + EntityType entityType = mainQuery.metamodel.getEntity(valueHolderEntityClass); + Type type = mainQuery.metamodel.type(valueClass); + + List attributePaths = new ArrayList<>(); + String simpleValueAttributePrefix = valuesClassAttributeName == null ? "" : valuesClassAttributeName + "."; + boolean simpleValue; + Set idAttributeNames; if (identifiableReference) { - SingularAttribute idAttribute = JpaMetamodelUtils.getSingleIdAttribute(entityType); - idAttributeName = idAttribute.getName(); - attributeSet = (Set>) (Set) Collections.singleton(idAttribute); + simpleValue = false; + idAttributeNames = new LinkedHashSet<>(); + Map> attributes = new TreeMap<>(mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityType).getAttributes()); + for (SingularAttribute attribute : JpaMetamodelUtils.getIdAttributes(entityType)) { + idAttributeNames.add(attribute.getName()); + Collection embeddedPropertyPaths = JpaUtils.getEmbeddedPropertyPaths(attributes, attribute.getName(), mainQuery.jpaProvider.needsElementCollectionIdCutoff(), true); + if (embeddedPropertyPaths.isEmpty()) { + attributePaths.add(attribute.getName()); + } else { + for (String embeddedPropertyPath : embeddedPropertyPaths) { + attributePaths.add(attribute.getName() + "." + embeddedPropertyPath); + } + } + } } else { - Set> originalAttributeSet; - if (valueClazzAttributeName == null) { - originalAttributeSet = (Set>) (Set) entityType.getAttributes(); + idAttributeNames = null; + if (valuesLikeAttribute == null) { + // This is a normal values clause + ManagedType managedType = mainQuery.metamodel.getManagedType(valueClass); + Map> attributes; + if (managedType == null) { + // When the values type is basic, entityType is ValuesEntity + simpleValue = true; + attributes = new TreeMap<>(mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityType).getAttributes()); + } else { + // Otherwise we consider all attributes + simpleValue = false; + attributes = new TreeMap<>(mainQuery.metamodel.getManagedType(ExtendedManagedType.class, managedType).getAttributes()); + } + Collection embeddedPropertyPaths = JpaUtils.getEmbeddedPropertyPaths(attributes, valuesClassAttributeName, mainQuery.jpaProvider.needsElementCollectionIdCutoff(), true); + attributePaths.addAll(embeddedPropertyPaths); } else { - originalAttributeSet = (Set>) (Set) mainQuery.metamodel.getManagedType(valueClazz).getAttributes(); - } - attributeSet = new TreeSet<>(JpaMetamodelUtils.ATTRIBUTE_NAME_COMPARATOR); - for (Attribute attr : originalAttributeSet) { - // Filter out collection attributes - if (!attr.isCollection()) { - attributeSet.add(attr); + String prefix = valuesClassAttributeName.substring(0, valuesClassAttributeName.length() - valuesLikeAttribute.getAttribute().getName().length()); + if (qualificationExpression == null) { + Map> attributes = new TreeMap<>(mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityType).getAttributes()); + Collection embeddedPropertyPaths = JpaUtils.getEmbeddedPropertyPaths(attributes, valuesClassAttributeName, mainQuery.jpaProvider.needsElementCollectionIdCutoff(), true); + if (embeddedPropertyPaths.isEmpty()) { + attributePaths.add(valuesClassAttributeName); + } else { + for (String embeddedPropertyPath : embeddedPropertyPaths) { + attributePaths.add(simpleValueAttributePrefix + embeddedPropertyPath); + } + } + } else { + attributePaths.add(prefix + valuesLikeAttribute.getAttribute().getName()); } + simpleValue = type instanceof BasicType; } } - String[][] parameterNames = new String[valueCount][attributeSet.size()]; - ValueRetriever[] pathExpressions = new ValueRetriever[attributeSet.size()]; + String[][] parameterNames = new String[valueCount][attributePaths.size()]; + String[] attributes = new String[attributePaths.size()]; + PropertyPathExpression[] pathExpressions = new PropertyPathExpression[attributePaths.size()]; - String[] attributes = initializeValuesParameter(clazz, valueClazz, identifiableReference, rootAlias, attributeSet, parameterNames, pathExpressions); - parameterManager.registerValuesParameter(rootAlias, valueClazz, parameterNames, pathExpressions, queryBuilder); + for (int i = 0; i < attributePaths.size(); i++) { + String attributeName = attributePaths.get(i); + String parameterPart = attributeName.replace('.', '_'); + attributes[i] = attributeName; + if (simpleValueAttributePrefix.isEmpty()) { + pathExpressions[i] = (PropertyPathExpression) com.blazebit.reflection.ExpressionUtils.getExpression(valueClass, attributeName); + for (int j = 0; j < valueCount; j++) { + parameterNames[j][i] = rootAlias + '_' + parameterPart + '_' + j; + } + } else { + if (attributeName.startsWith(simpleValueAttributePrefix)) { + pathExpressions[i] = (PropertyPathExpression) com.blazebit.reflection.ExpressionUtils.getExpression(valueClass, attributeName.substring(simpleValueAttributePrefix.length())); + for (int j = 0; j < valueCount; j++) { + parameterNames[j][i] = rootAlias + '_' + parameterPart + '_' + j; + } + } else if (simpleValue || attributeName.equals(valuesClassAttributeName)) { + pathExpressions[i] = null; + if (qualificationExpression != null) { + parameterPart += '_' + qualificationExpression.toLowerCase(); + } + for (int j = 0; j < valueCount; j++) { + parameterNames[j][i] = rootAlias + '_' + parameterPart + '_' + j; + } + } else { + pathExpressions[i] = (PropertyPathExpression) com.blazebit.reflection.ExpressionUtils.getExpression(valueClass, attributeName); + for (int j = 0; j < valueCount; j++) { + parameterNames[j][i] = rootAlias + '_' + parameterPart + '_' + j; + } + } + } + } + + parameterManager.registerValuesParameter(rootAlias, valueClass, parameterNames, pathExpressions, queryBuilder); JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, true, true, aliasManager); - JoinNode rootNode = JoinNode.createValuesRootNode(type, entityType, typeName, valueCount, idAttributeName, valueClazzAttributeName, castedParameter, attributes, rootAliasInfo); + JoinNode rootNode = JoinNode.createValuesRootNode(type, entityType, typeName, valueCount, idAttributeNames, valueLikeClause, qualificationExpression, valueClazzAttributeSingular, simpleValue, valuesClassAttributeName, castedParameter, attributes, rootAliasInfo); rootAliasInfo.setJoinNode(rootNode); rootNodes.add(rootNode); // register root alias in aliasManager @@ -380,52 +447,6 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int return rootAlias; } - /** - * @author Christian Beikov - * @since 1.2.0 - */ - static class SimpleValueRetriever implements ValueRetriever { - @Override - public Object getValue(Object target) { - return target; - } - } - - private String[] initializeValuesParameter(Class clazz, Class valueClazz, boolean identifiableReference, String prefix, Set> attributeSet, String[][] parameterNames, ValueRetriever[] pathExpressions) { - int valueCount = parameterNames.length; - String[] attributes = new String[attributeSet.size()]; - - if (clazz == ValuesEntity.class) { - pathExpressions[0] = new SimpleValueRetriever(); - String attributeName = attributeSet.iterator().next().getName(); - attributes[0] = attributeName; - for (int j = 0; j < valueCount; j++) { - parameterNames[j][0] = prefix + '_' + attributeName + '_' + j; - } - } else if (identifiableReference) { - Attribute attribute = attributeSet.iterator().next(); - String attributeName = attribute.getName(); - attributes[0] = attributeName; - pathExpressions[0] = com.blazebit.reflection.ExpressionUtils.getExpression(clazz, attributeName); - for (int j = 0; j < valueCount; j++) { - parameterNames[j][0] = prefix + '_' + attributeName + '_' + j; - } - } else { - Iterator> iter = attributeSet.iterator(); - for (int i = 0; i < attributeSet.size(); i++) { - Attribute attribute = iter.next(); - String attributeName = attribute.getName(); - attributes[i] = attributeName; - pathExpressions[i] = com.blazebit.reflection.ExpressionUtils.getExpression(valueClazz, attributeName); - for (int j = 0; j < valueCount; j++) { - parameterNames[j][i] = prefix + '_' + attributeName + '_' + j; - } - } - } - - return attributes; - } - String addRoot(EntityType entityType, String rootAlias) { if (rootAlias == null) { // TODO: not sure if other JPA providers support case sensitive queries like hibernate @@ -711,9 +732,6 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } else { if (nodeType instanceof EntityType) { sb.append(((EntityType) nodeType).getName()); - if (rootNode.getValuesIdName() != null) { - sb.append('.').append(rootNode.getValuesIdName()); - } } else { // Not sure how safe that is regarding ambiguity sb.append(rootNode.getNodeType().getJavaType().getSimpleName()); @@ -721,10 +739,13 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } sb.append("("); sb.append(rootNode.getValueCount()); + if (rootNode.getValuesIdNames() != null) { + sb.append(" ID "); + } sb.append(" VALUES"); - if (valueType.getJavaType() == ValuesEntity.class && rootNode.getValuesIdName() != null) { + if (rootNode.getValuesLikeClause() != null) { sb.append(" LIKE "); - sb.append(rootNode.getValuesIdName()); + sb.append(rootNode.getValuesLikeClause()); } sb.append(")"); } else if (externalRepresentation && explicitVersionEntities.get(rootNode.getJavaType()) != null) { @@ -762,6 +783,19 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St JoinNode valuesNode = null; if (rootNode.getValueCount() > 0) { + if (!externalRepresentation && !rootNode.isValueClazzAttributeSingular()) { + sb.append(" LEFT JOIN "); + sb.append(rootNode.getAlias()); + sb.append('.'); + sb.append(rootNode.getValuesLikeAttribute()); + sb.append(' '); + sb.append(rootNode.getAlias()); + sb.append('_'); + sb.append(rootNode.getValuesLikeAttribute().replace('.', '_')); + if (rootNode.getQualificationExpression() != null) { + sb.append('_').append(rootNode.getQualificationExpression().toLowerCase()); + } + } valuesNode = rootNode; // We add a synthetic where clause conjuncts for subqueries that are removed later to support the values clause in subqueries if (syntheticSubqueryValuesWhereClauseConjuncts != null) { @@ -869,9 +903,9 @@ void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String ali // The rendering strategy is to render the VALUES clause predicate into JPQL with the values parameters // in the correct order. The whole SQL part of that will be replaced later by the correct SQL int valueCount = rootNode.getValueCount(); - if (valueCount > 0) { + if (!externalRepresentation && valueCount > 0) { String typeName = rootNode.getValuesTypeName() == null ? null : rootNode.getValuesTypeName().toUpperCase(); - String valueClazzAttributeName = externalRepresentation ? null : rootNode.getValueClazzAttributeName(); + String valueClazzAttributeName = rootNode.getValuesLikeAttribute(); String[] attributes = rootNode.getValuesAttributes(); String prefix = rootNode.getAlias(); @@ -886,12 +920,29 @@ void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String ali sb.append(attributes[j]); sb.append(')'); } else { + if (rootNode.getQualificationExpression() != null) { + sb.append(rootNode.getQualificationExpression()).append('('); + } sb.append(alias); - sb.append('.'); - if (valueClazzAttributeName != null) { - sb.append(valueClazzAttributeName).append('.'); + if (rootNode.isValueClazzAttributeSingular()) { + sb.append('.'); + if (rootNode.isValueClazzSimpleValue()) { + sb.append(valueClazzAttributeName); + } else { + sb.append(attributes[j]); + } + } else { + sb.append('_'); + sb.append(valueClazzAttributeName.replace('.', '_')); + if (!rootNode.isValueClazzSimpleValue()) { + sb.append(attributes[j], valueClazzAttributeName.length(), attributes[j].length()); + } + } + if (rootNode.getQualificationExpression() != null) { + sb.append('_'); + sb.append(rootNode.getQualificationExpression().toLowerCase()); + sb.append(')'); } - sb.append(attributes[j]); } sb.append(" = "); @@ -899,7 +950,15 @@ void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String ali sb.append(':'); sb.append(prefix); sb.append('_'); - sb.append(attributes[j]); + if (rootNode.isValueClazzSimpleValue()) { + sb.append(valueClazzAttributeName.replace('.', '_')); + } else { + sb.append(attributes[j].replace('.', '_')); + } + if (rootNode.getQualificationExpression() != null) { + sb.append('_'); + sb.append(rootNode.getQualificationExpression().toLowerCase()); + } sb.append('_').append(i); sb.append(" OR "); } @@ -1005,8 +1064,10 @@ private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNo // This condition will be removed in the final SQL, so no worries about it // It is just there to have parameters at the right position in the final SQL if (valuesNode != null) { - renderValuesClausePredicate(sb, valuesNode, valuesNode.getAlias(), externalRepresentation); - sb.append(" AND "); + if (!externalRepresentation) { + renderValuesClausePredicate(sb, valuesNode, valuesNode.getAlias(), externalRepresentation); + sb.append(" AND "); + } valuesNode = null; } @@ -2075,7 +2136,7 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List) { if (node.getParentTreeNode() == null) { - attributePath = node.getValueClazzAttributeName() + "." + attributePath; + attributePath = node.getValuesLikeAttribute() + "." + attributePath; baseType = node.getValueType(); break; } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java index 4b76a3b575..3733976b76 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java @@ -76,8 +76,11 @@ public class JoinNode implements From, ExpressionModifier, BaseNode { private final String valuesTypeName; private final int valueCount; private final EntityType valueType; - private final String valuesIdName; - private final String valueClazzAttributeName; + private final Set valuesIdNames; + private final String valuesLikeClause; + private final boolean valueClazzAttributeSingular; + private final boolean valueClazzSimpleValue; + private final String valuesLikeAttribute; private final String valuesCastedParameter; private final String[] valuesAttributes; private final String qualificationExpression; @@ -110,8 +113,11 @@ private JoinNode(TreatedJoinAliasInfo treatedJoinAliasInfo) { this.valuesTypeName = treatedJoinNode.valuesTypeName; this.valueCount = treatedJoinNode.valueCount; this.valueType = treatedJoinNode.valueType; - this.valuesIdName = treatedJoinNode.valuesIdName; - this.valueClazzAttributeName = treatedJoinNode.valueClazzAttributeName; + this.valuesIdNames = treatedJoinNode.valuesIdNames; + this.valuesLikeClause = treatedJoinNode.valuesLikeClause; + this.valueClazzAttributeSingular = treatedJoinNode.valueClazzAttributeSingular; + this.valueClazzSimpleValue = treatedJoinNode.valueClazzSimpleValue; + this.valuesLikeAttribute = treatedJoinNode.valuesLikeAttribute; this.valuesCastedParameter = treatedJoinNode.valuesCastedParameter; this.valuesAttributes = treatedJoinNode.valuesAttributes; this.aliasInfo = treatedJoinAliasInfo; @@ -132,8 +138,11 @@ private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType this.valuesTypeName = null; this.valueCount = 0; this.valueType = null; - this.valuesIdName = null; - this.valueClazzAttributeName = null; + this.valuesIdNames = null; + this.valuesLikeClause = null; + this.valueClazzAttributeSingular = false; + this.valueClazzSimpleValue = false; + this.valuesLikeAttribute = null; this.valuesCastedParameter = null; this.valuesAttributes = null; this.qualificationExpression = qualificationExpression; @@ -157,7 +166,7 @@ private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType onUpdate(null); } - private JoinNode(Type nodeType, EntityType valueType, String valuesTypeName, int valueCount, String valuesIdName, String valueClazzAttributeName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { + private JoinNode(Type nodeType, EntityType valueType, String valuesTypeName, int valueCount, Set valuesIdNames, String valuesLikeClause, String valueClazzAttributeQualificationExpression, boolean valueClazzAttributeSingular, boolean valueClazzSimpleValue, String valuesLikeAttribute, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { this.parent = null; this.parentTreeNode = null; this.joinType = null; @@ -168,11 +177,14 @@ private JoinNode(Type nodeType, EntityType valueType, String valuesTypeNam this.valuesTypeName = valuesTypeName; this.valueCount = valueCount; this.valueType = valueType; - this.valuesIdName = valuesIdName; - this.valueClazzAttributeName = valueClazzAttributeName; + this.valuesIdNames = valuesIdNames; + this.valuesLikeClause = valuesLikeClause; + this.valueClazzAttributeSingular = valueClazzAttributeSingular; + this.valueClazzSimpleValue = valueClazzSimpleValue; + this.valuesLikeAttribute = valuesLikeAttribute; this.valuesCastedParameter = valuesCastedParameter; this.valuesAttributes = valuesAttributes; - this.qualificationExpression = null; + this.qualificationExpression = valueClazzAttributeQualificationExpression; this.aliasInfo = aliasInfo; this.joinNodesForTreatConstraint = Collections.emptyList(); onUpdate(null); @@ -182,8 +194,8 @@ public static JoinNode createRootNode(EntityType nodeType, JoinAliasInfo alia return new JoinNode(null, null, null, null, null, nodeType, null, null, aliasInfo); } - public static JoinNode createValuesRootNode(Type nodeType, EntityType valueType, String valuesTypeName, int valueCount, String valuesIdName, String valueClazzAttributeName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { - return new JoinNode(nodeType, valueType, valuesTypeName, valueCount, valuesIdName, valueClazzAttributeName, valuesCastedParameter, valuesAttributes, aliasInfo); + public static JoinNode createValuesRootNode(Type nodeType, EntityType valueType, String valuesTypeName, int valueCount, Set valuesIdName, String valuesLikeClause, String qualificationExpression, boolean valueClazzAttributeSingular, boolean valueClazzSimpleValue, String valuesLikeAttribute, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { + return new JoinNode(nodeType, valueType, valuesTypeName, valueCount, valuesIdName, valuesLikeClause, qualificationExpression, valueClazzAttributeSingular, valueClazzSimpleValue, valuesLikeAttribute, valuesCastedParameter, valuesAttributes, aliasInfo); } public static JoinNode createCorrelationRootNode(JoinNode correlationParent, String correlationPath, Attribute correlatedAttribute, Type nodeType, EntityType treatType, JoinAliasInfo aliasInfo) { @@ -202,7 +214,7 @@ public JoinNode cloneRootNode(JoinAliasInfo aliasInfo) { // NOTE: no cloning of treatedJoinNodes and entityJoinNodes is intentional JoinNode newNode; if (valueCount > 0) { - newNode = createValuesRootNode(nodeType, valueType, valuesTypeName, valueCount, valuesIdName, valueClazzAttributeName, valuesCastedParameter, valuesAttributes, aliasInfo); + newNode = createValuesRootNode(nodeType, valueType, valuesTypeName, valueCount, valuesIdNames, valuesLikeClause, qualificationExpression, valueClazzAttributeSingular, valueClazzSimpleValue, valuesLikeAttribute, valuesCastedParameter, valuesAttributes, aliasInfo); } else if (correlationParent == null) { newNode = createRootNode((EntityType) nodeType, aliasInfo); } else { @@ -613,12 +625,38 @@ public EntityType getValueType() { return valueType; } - public String getValueClazzAttributeName() { - return valueClazzAttributeName; + public boolean isValueClazzAttributeSingular() { + return valueClazzAttributeSingular; } - public String getValuesIdName() { - return valuesIdName; + public boolean isValueClazzSimpleValue() { + return valueClazzSimpleValue; + } + + public String getValuesLikeAttribute() { + return valuesLikeAttribute; + } + + public String getValueClazzAlias(String prefix) { + StringBuilder sb = new StringBuilder(); + appendValueClazzAlias(sb, prefix); + return sb.toString(); + } + + public void appendValueClazzAlias(StringBuilder sb, String prefix) { + if (qualificationExpression == null) { + sb.append(prefix).append(valuesLikeAttribute.replace('.', '_')); + } else { + sb.append(prefix).append(valuesAttributes[0].replace('.', '_')).append('_').append(qualificationExpression.toLowerCase()); + } + } + + public Set getValuesIdNames() { + return valuesIdNames; + } + + public String getValuesLikeClause() { + return valuesLikeClause; } String getValuesTypeName() { @@ -828,11 +866,21 @@ public void appendAlias(StringBuilder sb, boolean renderTreat, boolean externalR sb.append(".value"); sb.append(')'); } - } else if (valueClazzAttributeName != null) { + } else if (valuesLikeAttribute != null) { if (externalRepresentation) { sb.append(aliasInfo.getAlias()); + } else if (valueClazzAttributeSingular) { + sb.append(aliasInfo.getAlias()).append('.').append(valuesLikeAttribute.replace('.', '_')); } else { - sb.append(aliasInfo.getAlias()).append('.').append(valueClazzAttributeName); + if (qualificationExpression != null) { + sb.append(qualificationExpression); + sb.append('('); + sb.append(aliasInfo.getAlias()).append('_').append(valuesLikeAttribute.replace('.', '_')); + sb.append('_').append(qualificationExpression.toLowerCase()); + sb.append(')'); + } else { + sb.append(aliasInfo.getAlias()).append('_').append(valuesLikeAttribute.replace('.', '_')); + } } } else { if (qualificationExpression != null) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java index 724e27fca0..6cbfe0f14c 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java @@ -101,7 +101,7 @@ public static void expandBindings(EntityType bindType, Map b // When binding null, we don't have to adapt anything } else if (selectExpression instanceof PathExpression) { boolean firstBinding = true; - final Collection embeddedPropertyNames = getEmbeddedPropertyPaths(attributeEntries, attributeName, needsElementCollectionIdCutoffForCompositeIdOwner); + final Collection embeddedPropertyNames = getEmbeddedPropertyPaths(attributeEntries, attributeName, needsElementCollectionIdCutoffForCompositeIdOwner, false); PathExpression baseExpression = embeddedPropertyNames.size() > 1 ? ((PathExpression) selectExpression).clone(false) : ((PathExpression) selectExpression); @@ -155,7 +155,7 @@ public static void expandBindings(EntityType bindType, Map b } } } else if (selectExpression instanceof ParameterExpression) { - final Collection embeddedPropertyNames = getEmbeddedPropertyPaths(attributeEntries, attributeName, jpaProvider.needsElementCollectionIdCutoff()); + final Collection embeddedPropertyNames = getEmbeddedPropertyPaths(attributeEntries, attributeName, jpaProvider.needsElementCollectionIdCutoff(), false); if (embeddedPropertyNames.size() > 0) { ParameterExpression parameterExpression = (ParameterExpression) selectExpression; @@ -212,11 +212,26 @@ public static void expandBindings(EntityType bindType, Map b } } - public static Collection getEmbeddedPropertyPaths(Map> attributeEntries, String attributeName, boolean needsElementCollectionIdCutoff) { + public static Collection getEmbeddedPropertyPaths(Map> attributeEntries, String attributeName, boolean needsElementCollectionIdCutoff, boolean filterCollections) { final NavigableSet embeddedPropertyNames = new TreeSet<>(); String prefix = attributeName == null ? "" : attributeName + "."; - for (Map.Entry> entry : attributeEntries.entrySet()) { + int dotCount = -1; + int dotIndex = -1; + do { + dotCount++; + dotIndex = prefix.indexOf('.', dotIndex + 1); + } while (dotIndex != -1); + + OUTER: for (Map.Entry> entry : attributeEntries.entrySet()) { if (entry.getKey().startsWith(prefix)) { + if (filterCollections) { + List> attributePath = entry.getValue().getAttributePath(); + for (int i = dotCount; i < attributePath.size(); i++) { + if (attributePath.get(i).isCollection()) { + continue OUTER; + } + } + } String subAttribute = entry.getKey().substring(prefix.length()); String lower = embeddedPropertyNames.lower(subAttribute); if (lower == null) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterManager.java index 6af38d7dd4..23649ea5f5 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterManager.java @@ -16,15 +16,25 @@ package com.blazebit.persistence.impl; -import java.util.*; +import com.blazebit.persistence.parser.expression.Expression; +import com.blazebit.persistence.parser.expression.ParameterExpression; +import com.blazebit.reflection.PropertyPathExpression; import javax.persistence.Parameter; import javax.persistence.Query; import javax.persistence.TemporalType; - -import com.blazebit.lang.ValueRetriever; -import com.blazebit.persistence.parser.expression.Expression; -import com.blazebit.persistence.parser.expression.ParameterExpression; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * @@ -393,7 +403,7 @@ public void unregisterParameterName(String parameterName, ClauseType clauseType, } } - public void registerValuesParameter(String parameterName, Class type, String[][] parameterNames, ValueRetriever[] pathExpressions, AbstractCommonQueryBuilder queryBuilder) { + public void registerValuesParameter(String parameterName, Class type, String[][] parameterNames, PropertyPathExpression[] pathExpressions, AbstractCommonQueryBuilder queryBuilder) { if (parameterName == null) { throw new NullPointerException("parameterName"); } @@ -793,7 +803,7 @@ static final class ValuesParameterWrapper implements ParameterValue { private final ValuesParameterBinder binder; private Collection value; - public ValuesParameterWrapper(Class type, String[][] parameterNames, ValueRetriever[] pathExpressions) { + public ValuesParameterWrapper(Class type, String[][] parameterNames, PropertyPathExpression[] pathExpressions) { this.type = type; this.binder = new ValuesParameterBinder(parameterNames, pathExpressions); } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ValuesParameterBinder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ValuesParameterBinder.java index 3eff16d5cd..5778a97508 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ValuesParameterBinder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ValuesParameterBinder.java @@ -16,7 +16,7 @@ package com.blazebit.persistence.impl; -import com.blazebit.lang.ValueRetriever; +import com.blazebit.reflection.PropertyPathExpression; import javax.persistence.Query; import java.util.Collection; @@ -30,9 +30,9 @@ public class ValuesParameterBinder { private final String[][] parameterNames; - private final ValueRetriever[] pathExpressions; + private final PropertyPathExpression[] pathExpressions; - public ValuesParameterBinder(String[][] parameterNames, ValueRetriever[] pathExpressions) { + public ValuesParameterBinder(String[][] parameterNames, PropertyPathExpression[] pathExpressions) { this.parameterNames = parameterNames; this.pathExpressions = pathExpressions; } @@ -43,7 +43,11 @@ public void bind(Query query, Collection value) { Object element; if (iterator.hasNext() && (element = iterator.next()) != null) { for (int j = 0; j < parameterNames[i].length; j++) { - query.setParameter(parameterNames[i][j], pathExpressions[j].getValue(element)); + if (pathExpressions[j] == null) { + query.setParameter(parameterNames[i][j], element); + } else { + query.setParameter(parameterNames[i][j], pathExpressions[j].getNullSafeValue(element)); + } } } else { for (int j = 0; j < parameterNames[i].length; j++) { @@ -57,7 +61,7 @@ public String[][] getParameterNames() { return parameterNames; } - public ValueRetriever[] getPathExpressions() { + public PropertyPathExpression[] getPathExpressions() { return pathExpressions; } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/function/entity/EntityFunction.java b/core/impl/src/main/java/com/blazebit/persistence/impl/function/entity/EntityFunction.java index 356ebef673..c95114d187 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/function/entity/EntityFunction.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/function/entity/EntityFunction.java @@ -103,7 +103,7 @@ public void render(FunctionRenderContext functionRenderContext) { sb.replace(subselectIndex, endIndex, entityName); } } - SqlUtils.applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases); + SqlUtils.applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases, null); functionRenderContext.addChunk("("); functionRenderContext.addChunk(sb.toString()); functionRenderContext.addChunk(")"); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java index 7c18115fa9..fbe5ac8cf8 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java @@ -301,7 +301,7 @@ public String extract(StringBuilder sb, int index, int currentPosition) { String sqlAlias = extendedQuerySupport.getSqlAlias(em, baseQuery, tableNameRemappingEntry.getKey()); String newCteName = tableNameRemappingEntry.getValue(); - SqlUtils.applyTableNameRemapping(sqlSb, sqlAlias, newCteName, null); + SqlUtils.applyTableNameRemapping(sqlSb, sqlAlias, newCteName, null, null); } return sb; @@ -418,7 +418,27 @@ protected StringBuilder applySqlTransformations(String sqlQuery) { } } - SqlUtils.applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases); + String newSqlAlias = null; + if (node.getPluralTableJoin() != null) { + newSqlAlias = node.getPluralTableAlias() + " "; + valuesTableSqlAlias += " " + node.getPluralTableJoin(); + + // Replace aliases using the collection table alias + if (node.getPluralCollectionTableAlias() != null) { + String dereference = node.getPluralCollectionTableAlias() + "."; + int fromIndex = SqlUtils.indexOfFrom(sb); + int dereferenceIndex = 0; + while ((dereferenceIndex = sb.indexOf(dereference, dereferenceIndex)) != -1) { + if (dereferenceIndex > fromIndex) { + break; + } + sb.replace(dereferenceIndex, dereferenceIndex + node.getPluralCollectionTableAlias().length(), newSqlAlias); + dereferenceIndex += newSqlAlias.length() - node.getPluralCollectionTableAlias().length(); + } + } + } + + SqlUtils.applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases, newSqlAlias); } return sb; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/EntityFunctionNode.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/EntityFunctionNode.java index 541e684321..567e35ede8 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/EntityFunctionNode.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/EntityFunctionNode.java @@ -27,13 +27,19 @@ public class EntityFunctionNode { private final String valuesAliases; private final String entityName; private final String tableAlias; + private final String pluralCollectionTableAlias; + private final String pluralTableAlias; + private final String pluralTableJoin; private final String syntheticPredicate; - public EntityFunctionNode(String valuesClause, String valuesAliases, String entityName, String tableAlias, String syntheticPredicate) { + public EntityFunctionNode(String valuesClause, String valuesAliases, String entityName, String tableAlias, String pluralCollectionTableAlias, String pluralTableAlias, String pluralTableJoin, String syntheticPredicate) { this.valuesClause = valuesClause; this.valuesAliases = valuesAliases; this.entityName = entityName; this.tableAlias = tableAlias; + this.pluralCollectionTableAlias = pluralCollectionTableAlias; + this.pluralTableAlias = pluralTableAlias; + this.pluralTableJoin = pluralTableJoin; this.syntheticPredicate = syntheticPredicate; } @@ -53,6 +59,18 @@ public String getTableAlias() { return tableAlias; } + public String getPluralCollectionTableAlias() { + return pluralCollectionTableAlias; + } + + public String getPluralTableAlias() { + return pluralTableAlias; + } + + public String getPluralTableJoin() { + return pluralTableJoin; + } + public String getSyntheticPredicate() { return syntheticPredicate; } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java b/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java index a4df9ac26a..991322a389 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/transform/SizeTransformationVisitor.java @@ -194,7 +194,7 @@ private Expression getSizeExpression(ExpressionModifier parentModifier, PathExpr throw new RuntimeException("Attribute [" + property + "] not found on class " + startType.getJavaType().getName()); } final PluralAttribute.CollectionType collectionType = targetAttribute.getCollectionType(); - final boolean isElementCollection = targetAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION; + final boolean isElementCollection = jpaProvider.getJpaMetamodelAccessor().isElementCollection(targetAttribute); boolean subqueryRequired; if (isElementCollection) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/util/SqlUtils.java b/core/impl/src/main/java/com/blazebit/persistence/impl/util/SqlUtils.java index a5d1a1b672..39c9a87206 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/util/SqlUtils.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/util/SqlUtils.java @@ -82,7 +82,7 @@ public String extract(StringBuilder sb, int index, int currentPosition) { private SqlUtils() { } - public static void applyTableNameRemapping(StringBuilder sb, String sqlAlias, String newCteName, String aliasExtension) { + public static void applyTableNameRemapping(StringBuilder sb, String sqlAlias, String newCteName, String aliasExtension, String newSqlAlias) { final String searchAs = " as"; final String searchAlias = " " + sqlAlias; int searchIndex = 0; @@ -91,18 +91,23 @@ public static void applyTableNameRemapping(StringBuilder sb, String sqlAlias, St if (idx < sb.length() && sb.charAt(idx) == '.') { // This is a dereference of the alias, skip this } else { - int[] indexRange; + int[] tableNameIndexRange; if (searchAs.equalsIgnoreCase(sb.substring(searchIndex - searchAs.length(), searchIndex))) { // Uses aliasing with the AS keyword - indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length()); + tableNameIndexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length()); } else { // Uses aliasing without the AS keyword - indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex); + tableNameIndexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex); } - int oldLength = indexRange[1] - indexRange[0]; + if (newSqlAlias != null) { + sb.replace(tableNameIndexRange[1] + 1, tableNameIndexRange[1] + 1 + sqlAlias.length(), newSqlAlias); + searchIndex += newSqlAlias.length() - sqlAlias.length(); + } + + int oldLength = tableNameIndexRange[1] - tableNameIndexRange[0]; // Replace table name with cte name - sb.replace(indexRange[0], indexRange[1], newCteName); + sb.replace(tableNameIndexRange[0], tableNameIndexRange[1], newCteName); if (aliasExtension != null) { sb.insert(searchIndex + searchAlias.length() + (newCteName.length() - oldLength), aliasExtension); @@ -117,7 +122,7 @@ public static void applyTableNameRemapping(StringBuilder sb, String sqlAlias, St } } - public static int[] rtrimBackwardsToFirstWhitespace(StringBuilder sb, int startIndex) { + public static int[] rtrimBackwardsToFirstWhitespace(CharSequence sb, int startIndex) { int tableNameStartIndex; int tableNameEndIndex = startIndex; boolean text = false; @@ -298,6 +303,10 @@ public static int indexOfSelect(CharSequence sql) { return selectIndex; } + public static int indexOfFrom(CharSequence sql) { + return FROM_FINDER.indexIn(sql, 0); + } + /** * Finds the toplevel WHERE keyword in an arbitrary query. * @@ -449,18 +458,60 @@ public static int indexOfTableName(CharSequence sql, String tableName) { return index + 1; } - public static int findJoinStartIndex(StringBuilder sqlSb, int aliasIndex) { + public static int indexOfJoinTableAlias(CharSequence sql, String tableName) { + int startIndex = FROM_FINDER.indexIn(sql, 0); + if (startIndex == -1) { + return -1; + } + startIndex += FROM.length(); + int whereIndex = indexOfWhere(sql); + if (whereIndex == -1) { + whereIndex = sql.length(); + } + + PatternFinder finder = new QuotedIdentifierAwarePatternFinder(new BoyerMooreCaseInsensitiveAsciiFirstPatternFinder(" " + tableName + " on ")); + int index = finder.indexIn(sql, startIndex, whereIndex); + if (index == -1) { + return -1; + } + + return index + 1; + } + + public static int[] indexOfFullJoin(CharSequence sql, String tableAlias) { + int whereIndex = SqlUtils.indexOfWhere(sql); + return indexOfFullJoin(sql, tableAlias, whereIndex); + } + + public static int[] indexOfFullJoin(CharSequence sql, String tableAlias, int whereIndex) { + // For every table alias we found in the select items that we removed due to the cutoff, we delete the joins + String aliasOnPart = " " + tableAlias + " on "; + int aliasIndex = indexOfJoinTableAlias(sql, tableAlias); + if (aliasIndex > -1 && aliasIndex < whereIndex) { + // indexOfJoinTableAlias moves the index to the first char of the alias, so move back + aliasIndex--; + // First, let's find the end of the on clause + int onClauseStart = aliasIndex + aliasOnPart.length(); + int onClauseEnd = SqlUtils.findEndOfOnClause(sql, onClauseStart, whereIndex); + int joinStartIndex = SqlUtils.findJoinStartIndex(sql, aliasIndex); + return new int[] { joinStartIndex, onClauseEnd }; + } + + return null; + } + + public static int findJoinStartIndex(CharSequence sqlSb, int aliasIndex) { // Then we step back token-wise until we have found the tokens "(left|inner|cross)? outer? join" int[] tokenRange = SqlUtils.rtrimBackwardsToFirstWhitespace(sqlSb, aliasIndex); return findJoinStartIndex(sqlSb, tokenRange[0] - 1, EnumSet.of(JoinToken.JOIN)); } - public static int findJoinStartIndex(StringBuilder sqlSb, int tokenEnd, Set allowedTokens) { + public static int findJoinStartIndex(CharSequence sqlSb, int tokenEnd, Set allowedTokens) { int[] tokenRange; do { tokenRange = SqlUtils.rtrimBackwardsToFirstWhitespace(sqlSb, tokenEnd); tokenEnd = tokenRange[0] - 1; - JoinToken token = JoinToken.valueOf(sqlSb.substring(tokenRange[0], tokenRange[1]).trim().toUpperCase()); + JoinToken token = JoinToken.valueOf(sqlSb.subSequence(tokenRange[0], tokenRange[1]).toString().trim().toUpperCase()); if (allowedTokens.contains(token)) { allowedTokens = token.previous(); } else { @@ -498,7 +549,7 @@ Set previous() { } } - public static int findEndOfOnClause(StringBuilder sqlSb, int predicateStartIndex, int whereIndex) { + public static int findEndOfOnClause(CharSequence sqlSb, int predicateStartIndex, int whereIndex) { int joinIndex = JOIN_FINDER.indexIn(sqlSb, predicateStartIndex); int end; if (joinIndex == -1 || joinIndex > whereIndex) { diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/ListIndexAttribute.java b/core/parser/src/main/java/com/blazebit/persistence/parser/ListIndexAttribute.java index 8ba98238b5..bbfb9184af 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/ListIndexAttribute.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/ListIndexAttribute.java @@ -18,6 +18,7 @@ import javax.persistence.metamodel.ListAttribute; import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.Type; import java.lang.reflect.Member; @@ -46,6 +47,11 @@ public ListIndexAttribute(ListAttribute attribute) { this.attribute = attribute; } + @Override + public PluralAttribute getPluralAttribute() { + return attribute; + } + @Override public String getQualificationExpression() { return "INDEX"; diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/MapEntryAttribute.java b/core/parser/src/main/java/com/blazebit/persistence/parser/MapEntryAttribute.java index af096db766..72716d3dc6 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/MapEntryAttribute.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/MapEntryAttribute.java @@ -18,6 +18,7 @@ import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.MapAttribute; +import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.Type; import java.lang.reflect.Member; @@ -47,6 +48,11 @@ public MapEntryAttribute(MapAttribute attribute) { this.attribute = attribute; } + @Override + public PluralAttribute getPluralAttribute() { + return attribute; + } + @Override public String getQualificationExpression() { return "ENTRY"; diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/MapKeyAttribute.java b/core/parser/src/main/java/com/blazebit/persistence/parser/MapKeyAttribute.java index 4f85e4a194..4917c9c664 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/MapKeyAttribute.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/MapKeyAttribute.java @@ -18,6 +18,7 @@ import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.MapAttribute; +import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.Type; import java.lang.reflect.Member; @@ -43,6 +44,11 @@ public MapKeyAttribute(MapAttribute attribute) { .equals(jpaType.getPersistenceType()) ? PersistentAttributeType.EMBEDDED : PersistentAttributeType.BASIC; } + @Override + public PluralAttribute getPluralAttribute() { + return attribute; + } + @Override public String getQualificationExpression() { return "KEY"; diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/QualifiedAttribute.java b/core/parser/src/main/java/com/blazebit/persistence/parser/QualifiedAttribute.java index 6764920a2a..2306b70b53 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/QualifiedAttribute.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/QualifiedAttribute.java @@ -16,6 +16,8 @@ package com.blazebit.persistence.parser; +import javax.persistence.metamodel.PluralAttribute; + /** * Super type for attributes like KEY/VALUE/ENTRY/INDEX * @@ -25,6 +27,8 @@ */ public interface QualifiedAttribute { + public PluralAttribute getPluralAttribute(); + public String getQualificationExpression(); } diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java index 62755b2f18..675cb0d7a0 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java @@ -60,7 +60,7 @@ public class Document extends Ownable implements Serializable { private Long version; private Object someTransientField; private NameObject nameObject = new NameObject(); - private NameObjectContainer nameContainer = new NameObjectContainer(); + private NameObjectContainer2 nameContainer = new NameObjectContainer2(); private Set versions = new HashSet<>(); private Set partners = new HashSet<>(); private IntIdEntity intIdEntity; @@ -158,12 +158,12 @@ public void setNameObject(NameObject nameObject) { } @Embedded - public NameObjectContainer getNameContainer() { + public NameObjectContainer2 getNameContainer() { return nameContainer; } - public void setNameContainer(NameObjectContainer nameContainer) { - this.nameContainer = nameContainer; + public void setNameContainer(NameObjectContainer2 nameObjectContainer) { + this.nameContainer = nameObjectContainer; } @OneToMany(mappedBy = "document", cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/NameObjectContainer2.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/NameObjectContainer2.java new file mode 100644 index 0000000000..b9669a184e --- /dev/null +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/NameObjectContainer2.java @@ -0,0 +1,88 @@ +/* + * 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.testsuite.entity; + +import javax.persistence.AssociationOverride; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.JoinColumn; +import java.io.Serializable; + +@Embeddable +public class NameObjectContainer2 implements Serializable { + + private String name; + private NameObject nameObject = new NameObject(); + + public NameObjectContainer2() { + } + + public NameObjectContainer2(String name, NameObject nameObject) { + this.name = name; + this.nameObject = nameObject; + } + + @Column(name = "container_name", length = 30) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "primaryName", column = @Column(name = "container_primary_name", length = 30)), + @AttributeOverride(name = "secondaryName", column = @Column(name = "container_secondary_name", length = 30)) + }) + @AssociationOverride(name = "intIdEntity", joinColumns = @JoinColumn(name = "container_int_id_entity")) + public NameObject getNameObject() { + return nameObject; + } + + public void setNameObject(NameObject nameObject) { + this.nameObject = nameObject; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NameObjectContainer2)) { + return false; + } + + NameObjectContainer2 that = (NameObjectContainer2) o; + + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + return getNameObject() != null ? getNameObject().equals(that.getNameObject()) : that.getNameObject() == null; + } + + @Override + public int hashCode() { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getNameObject() != null ? getNameObject().hashCode() : 0); + return result; + } +} diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java index 980f5ff3a1..86c130d8e3 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java @@ -131,8 +131,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodes(INDEX(indexedNodes), id, indexedNodes.id)\n" + "SELECT 1, 1, 4" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -164,8 +163,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodes(INDEX(indexedNodes), id, indexedNodes.id)\n" + "SELECT 1, 1, 4" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); ReturningResult returningResult = criteria.executeWithReturning("indexedNodes.id"); Root r = getRoot(em); @@ -194,8 +192,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodesMany(INDEX(indexedNodesMany), id, indexedNodesMany.id)\n" + "SELECT 1, 1, 4" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -223,8 +220,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodesManyDuplicate(INDEX(indexedNodesManyDuplicate), id, indexedNodesManyDuplicate.id)\n" + "SELECT 1, 1, 4" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -253,8 +249,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodesElementCollection(INDEX(indexedNodesElementCollection), id, indexedNodesElementCollection.value, indexedNodesElementCollection.value2)\n" + "SELECT 1, 1, 'B', 'P'" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -283,8 +278,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodes(KEY(keyedNodes), id, keyedNodes.id)\n" + "SELECT 'b', 1, 5" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -312,8 +306,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodesMany(KEY(keyedNodesMany), id, keyedNodesMany.id)\n" + "SELECT 'b', 1, 5" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -341,8 +334,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodesManyDuplicate(KEY(keyedNodesManyDuplicate), id, keyedNodesManyDuplicate.id)\n" + "SELECT 'b', 1, 5" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -371,8 +363,7 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodesElementCollection(KEY(keyedNodesElementCollection), id, keyedNodesElementCollection.value, keyedNodesElementCollection.value2)\n" + "SELECT 'b', 1, 'B', 'P'" - + " FROM Integer(1 VALUES) valuesAlias" - + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/GroupByTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/GroupByTest.java index 759c602d4f..b8cbcbcde0 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/GroupByTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/GroupByTest.java @@ -16,11 +16,6 @@ package com.blazebit.persistence.testsuite; -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.junit.experimental.categories.Category; - import com.blazebit.persistence.CriteriaBuilder; import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus; import com.blazebit.persistence.testsuite.base.jpa.category.NoFirebird; @@ -29,6 +24,10 @@ import com.blazebit.persistence.testsuite.base.jpa.category.NoPostgreSQL; import com.blazebit.persistence.testsuite.base.jpa.category.NoSQLite; import com.blazebit.persistence.testsuite.entity.Document; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static org.junit.Assert.assertEquals; /** * diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java index 87b16f38dd..dc7b2fe3d5 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java @@ -40,6 +40,7 @@ import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -75,6 +76,7 @@ public void work(EntityManager em) { p1 = new Person("p1"); d1 = new Document("doc1", 1); d1.setNameObject(new NameObject("123", "abc")); + d1.getNameContainers().add(new NameObjectContainer("test", new NameObject("123", "abc"))); d1.setOwner(p1); em.persist(p1); @@ -100,7 +102,7 @@ public void testValuesEntityFunction() { cb.select("allowedAge"); String expected = "" - + "SELECT doc.name, allowedAge FROM Document doc, Long(1 VALUES) allowedAge WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 AND doc.age = allowedAge"; + + "SELECT doc.name, allowedAge FROM Document doc, Long(1 VALUES) allowedAge WHERE doc.age = allowedAge"; assertEquals(expected, cb.getQueryString()); List resultList = cb.getResultList(); @@ -122,8 +124,7 @@ public void testValuesEntityFunctionWithEmbeddable() { String expected = "" + "SELECT doc.name, embeddable FROM Document doc, NameObject(1 VALUES) embeddable" + - " WHERE embeddable.intIdEntity = :embeddable_intIdEntity_0 OR embeddable.primaryName = :embeddable_primaryName_0 OR embeddable.secondaryName = :embeddable_secondaryName_0" + - " AND doc.nameObject.primaryName = embeddable.secondaryName AND doc.nameObject.secondaryName = embeddable.primaryName"; + " WHERE doc.nameObject.primaryName = embeddable.secondaryName AND doc.nameObject.secondaryName = embeddable.primaryName"; assertEquals(expected, cb.getQueryString()); List resultList = cb.getResultList(); @@ -132,6 +133,185 @@ public void testValuesEntityFunctionWithEmbeddable() { assertEquals(new NameObject("abc", "123"), resultList.get(0).get(1)); } + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionWithPluralOnlyEmbeddable() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(NameObjectContainer.class, "embeddable", Collections.singleton(new NameObjectContainer("test", new NameObject("abc", "123")))); + cb.from(Document.class, "doc"); + cb.where("doc.nameContainers.name").eqExpression("embeddable.name"); + cb.where("doc.nameContainers.nameObject.primaryName").eqExpression("embeddable.nameObject.secondaryName"); + cb.where("doc.nameContainers.nameObject.secondaryName").eqExpression("embeddable.nameObject.primaryName"); + cb.select("doc.name"); + cb.select("embeddable"); + + String expected = "" + + "SELECT doc.name, embeddable FROM Document doc LEFT JOIN doc.nameContainers nameContainers_1, NameObjectContainer(1 VALUES) embeddable" + + " WHERE nameContainers_1.name = embeddable.name AND nameContainers_1.nameObject.primaryName = embeddable.nameObject.secondaryName AND nameContainers_1.nameObject.secondaryName = embeddable.nameObject.primaryName"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("doc1", resultList.get(0).get(0)); + assertEquals(new NameObjectContainer("test", new NameObject("abc", "123")), resultList.get(0).get(1)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikeBasic() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "name", "n", Collections.singleton("someName")); + cb.select("n"); + + String expected = "SELECT n FROM String(1 VALUES LIKE Document.name) n"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("someName", resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikeEnum() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "documentType", "t", Collections.singleton(DocumentType.NOVEL)); + cb.select("t"); + + String expected = "SELECT t FROM DocumentType(1 VALUES LIKE Document.documentType) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals(DocumentType.NOVEL, resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikeCalendar() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + Calendar cal = Calendar.getInstance(); + cb.fromValues(Document.class, "creationDate", "t", Collections.singleton(cal)); + cb.select("t"); + + String expected = "SELECT t FROM Calendar(1 VALUES LIKE Document.creationDate) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + // creationDate is just a DATE + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.HOUR_OF_DAY, 0); + assertEquals(cal, resultList.get(0).get(0, Calendar.class)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikeEmbeddable() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "nameObject", "t", Collections.singleton(new NameObject("123", "abc"))); + cb.select("t"); + + String expected = "SELECT t FROM NameObject(1 VALUES LIKE Document.nameObject) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals(new NameObject("123", "abc"), resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikeEntity() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "owner", "t", Collections.singleton(new Person(1L))); + cb.select("t"); + + String expected = "SELECT t FROM Person(1 VALUES LIKE Document.owner) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals(new Person(1L), resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikePluralBasic() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "strings", "t", Collections.singleton("test")); + cb.select("t"); + + String expected = "SELECT t FROM String(1 VALUES LIKE Document.strings) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("test", resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikePluralEmbeddable() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "names", "t", Collections.singleton(new NameObject("123", "abc"))); + cb.select("t"); + + String expected = "SELECT t FROM NameObject(1 VALUES LIKE Document.names) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals(new NameObject("123", "abc"), resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikePluralEntity() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "partners", "t", Collections.singleton(new Person(1L))); + cb.select("t.id"); + + String expected = "SELECT t.id FROM Person(1 VALUES LIKE Document.partners) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals(1L, resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikePluralIndex() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "INDEX(people)", "t", Collections.singleton(1)); + cb.select("t"); + + String expected = "SELECT t FROM Integer(1 VALUES LIKE INDEX(Document.people)) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals(1, resultList.get(0).get(0)); + } + + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionLikePluralKey() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Document.class, "KEY(stringMap)", "t", Collections.singleton("key")); + cb.select("t"); + + String expected = "SELECT t FROM String(1 VALUES LIKE KEY(Document.stringMap)) t"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("key", resultList.get(0).get(0)); + } + // Test for #305 @Test @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) @@ -145,7 +325,7 @@ public void testValuesEntityFunctionWithParameterInSelect() { String expected = "" + "SELECT CASE WHEN doc.name = :param THEN doc.name ELSE '' END, allowedAge FROM Document doc, Long(2 VALUES) allowedAge " + - "WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_1 AND doc.age = allowedAge"; + "WHERE doc.age = allowedAge"; assertEquals(expected, cb.getQueryString()); cb.setParameter("param", "doc1"); @@ -216,7 +396,7 @@ public void testValuesEntityFunctionLeftJoin() { String expected = "" + "SELECT allowedAge, doc.name FROM Long(3 VALUES) allowedAge LEFT JOIN Document doc" + - onClause("TREAT_LONG(allowedAge.value) = :allowedAge_value_0 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_1 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_2 AND doc.age = allowedAge") + + onClause("doc.age = allowedAge") + " ORDER BY allowedAge ASC"; assertEquals(expected, cb.getQueryString()); @@ -251,7 +431,7 @@ public void testValuesEntityFunctionWithEntity() { String expected = "" + "SELECT intEntity.name, doc.name FROM IntIdEntity(2 VALUES) intEntity LEFT JOIN Document doc" + - onClause("intEntity.id = :intEntity_id_0 OR intEntity.name = :intEntity_name_0 OR intEntity.value = :intEntity_value_0 OR intEntity.id = :intEntity_id_1 OR intEntity.name = :intEntity_name_1 OR intEntity.value = :intEntity_value_1 AND doc.name = intEntity.name") + + onClause("doc.name = intEntity.name") + " ORDER BY " + renderNullPrecedence("intEntity.name", "ASC", "LAST"); assertEquals(expected, cb.getQueryString()); @@ -280,7 +460,7 @@ public void testValuesEntityFunctionParameter() { String expected = "" + "SELECT intEntity.name, doc.name FROM IntIdEntity(1 VALUES) intEntity LEFT JOIN Document doc" + - onClause("intEntity.id = :intEntity_id_0 OR intEntity.name = :intEntity_name_0 OR intEntity.value = :intEntity_value_0 AND doc.name = intEntity.name") + + onClause("doc.name = intEntity.name") + " ORDER BY " + renderNullPrecedence("intEntity.name", "ASC", "LAST"); assertEquals(expected, cb.getQueryString()); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java index eb1884dec6..24e9243c4e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java @@ -35,6 +35,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -43,6 +45,8 @@ */ public abstract class AttributeMapping implements EntityViewAttributeMapping { + private static final Logger LOG = Logger.getLogger("com.blazebit.persistence.view.SUBTYPE_INFERENCE"); + protected final ViewMapping viewMapping; protected final Annotation mapping; protected final MetamodelBootContext context; @@ -603,6 +607,12 @@ public void circularDependencyError(Set> dependencies) { context.addError("A circular dependency is introduced at the " + getErrorLocation() + " in the following dependency set: " + Arrays.deepToString(dependencies.toArray())); } + public void circularDependencyDebug(ViewMapping viewMapping, Set> dependencies) { + if (LOG.isLoggable(Level.FINEST)) { + LOG.finest("Removing allowed subtype '" + viewMapping.getEntityViewClass() + "' because of a possible circular dependency at the " + getErrorLocation() + " in the following dependency set: " + Arrays.deepToString(dependencies.toArray())); + } + } + public void unknownSubviewType(Class subviewClass) { context.addError("An unknown or unregistered subview type '" + subviewClass.getName() + "' is used at the " + getErrorLocation() + "!"); } 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 bf529a27f2..a6f6741572 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 @@ -321,9 +321,15 @@ public void initializeViewMappings(MetamodelBuildingContext context) { // Also see AbstractMethodPluralAttribute#determineUpdatable() for the same logic if (attributeViewMapping != null && (isUpdatable == Boolean.TRUE || getDeclaringView().isUpdatable())) { if (hasSetter || isCollection && (cascadeTypes.contains(CascadeType.PERSIST) || attributeViewMapping.isCreatable())) { + boolean allowUpdatable; + if (isCollection) { + allowUpdatable = true; + } else { + allowUpdatable = !disallowOwnedUpdatableSubview || attributeViewMapping.isUpdatable(); + } // But only if the attribute is explicitly or implicitly updatable - this.readOnlySubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), true); - this.cascadeSubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), false); + this.readOnlySubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), true, false); + this.cascadeSubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), false, allowUpdatable); } else { this.cascadeSubtypeMappings = Collections.emptyMap(); } @@ -494,7 +500,7 @@ private Map initializeDependentSubtypeMappings(MetamodelBu return subtypeMappings; } - private Map initializeDependentSubtypeMappingsAuto(final MetamodelBuildingContext context, final Class clazz, boolean readOnly) { + private Map initializeDependentSubtypeMappingsAuto(final MetamodelBuildingContext context, final Class clazz, boolean readOnly, boolean allowUpdatable) { Set> subtypes = context.findSubtypes(clazz); if (subtypes.size() == 0) { return Collections.emptyMap(); @@ -505,7 +511,7 @@ private Map initializeDependentSubtypeMappingsAuto(final M final ViewMapping subtypeMapping = context.getViewMapping(type); if (subtypeMapping == null) { unknownSubviewType(type); - } else if (!readOnly || !subtypeMapping.isUpdatable() && !subtypeMapping.isCreatable()) { + } else if (!readOnly && (allowUpdatable && subtypeMapping.isUpdatable() || !subtypeMapping.isUpdatable()) || !subtypeMapping.isUpdatable() && !subtypeMapping.isCreatable()) { // We can't initialize a potential subtype mapping here immediately, but have to wait until the current view mapping is fully initialized // This avoids access to partly initialized view mappings viewMapping.onInitializeViewMappingsFinished(new Runnable() { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java index 5c6b3ef3fa..c07edc44f5 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java @@ -412,6 +412,8 @@ public boolean validateDependencies(MetamodelBuildingContext context, Set attribute, String parentAttrib if (mapAttribute.isKeySubview()) { featuresFound[FEATURE_SUBVIEWS] = true; ManagedViewTypeImpl managedViewType = (ManagedViewTypeImpl) mapAttribute.getKeyType(); - applySubviewMapping(mappingAttribute, attributePath, tupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, true); + applySubviewMapping(mappingAttribute, attributePath, tupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, true, true); mapValueStartIndex = tupleOffset + (mapperBuilder.mapperIndex() - startIndex) + 1; } else { applyCollectionFunctionMapping("KEY", "_KEY", mappingAttribute, mapperBuilder, EMPTY); @@ -526,7 +526,7 @@ private void applyMapping(AbstractAttribute attribute, String parentAttrib } else { MappingAttribute mappingAttribute = (MappingAttribute) attribute; ManagedViewTypeImplementor managedViewType = (ManagedViewTypeImplementor) pluralAttribute.getElementType(); - applySubviewMapping(mappingAttribute, attributePath, newTupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, false); + applySubviewMapping(mappingAttribute, attributePath, newTupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, false, true); } } else if (mapKey) { MappingAttribute mappingAttribute = (MappingAttribute) attribute; @@ -581,7 +581,7 @@ private void applyMapping(AbstractAttribute attribute, String parentAttrib } else { MappingAttribute mappingAttribute = (MappingAttribute) attribute; ManagedViewTypeImplementor managedViewType = (ManagedViewTypeImplementor) ((SingularAttribute) attribute).getType(); - applySubviewMapping(mappingAttribute, attributePath, tupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, false); + applySubviewMapping(mappingAttribute, attributePath, tupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, false, managedViewType instanceof ViewType); } } else { if (attribute.isCorrelated() || attribute.getFetchStrategy() != FetchStrategy.JOIN) { @@ -610,10 +610,6 @@ private void applySubviewIdMapping(MappingAttribute mappingAttribu applySubviewMapping(mappingAttribute, attributePath, tupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, isKey, true); } - private void applySubviewMapping(MappingAttribute mappingAttribute, String attributePath, TupleIdDescriptor tupleIdDescriptor, ManagedViewTypeImplementor managedViewType, TupleElementMapperBuilder mapperBuilder, EmbeddingViewJpqlMacro embeddingViewJpqlMacro, boolean isKey) { - applySubviewMapping(mappingAttribute, attributePath, tupleIdDescriptor, managedViewType, mapperBuilder, embeddingViewJpqlMacro, isKey, false); - } - @SuppressWarnings("unchecked") private void applySubviewMapping(MappingAttribute mappingAttribute, String subviewAttributePath, TupleIdDescriptor tupleIdDescriptor, ManagedViewTypeImplementor managedViewType, TupleElementMapperBuilder mapperBuilder, EmbeddingViewJpqlMacro embeddingViewJpqlMacro, boolean isKey, boolean nullIfEmpty) { String subviewAliasPrefix = mapperBuilder.getAlias(mappingAttribute, isKey); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/UpdatableSubviewTupleTransformer.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/UpdatableSubviewTupleTransformer.java index a883c6cfae..4466ca8876 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/UpdatableSubviewTupleTransformer.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/objectbuilder/transformer/UpdatableSubviewTupleTransformer.java @@ -67,6 +67,9 @@ public Object[] transform(Object[] tuple, UpdatableViewMap updatableViewMap) { updatableViewMap.put(key, o); tuple[template.getTupleOffset()] = o; } + } else { + // In case the null check object index differs from the tupleOffset like it is the case when using inheritance + tuple[template.getTupleOffset()] = null; } for (int i = consumeStartIndex; i < consumeEndIndex; i++) { tuple[i] = TupleReuse.CONSUMED; diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java index 74d9c8289f..0691ff8d9f 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java @@ -57,8 +57,6 @@ protected void prepareData(EntityManager em) { doc1.getNameObject().setPrimaryName("doc1"); doc1.getNames().add(new NameObject("doc1", "doc1")); doc1.getNameMap().put("doc1", new NameObject("doc1", "doc1")); - doc1.getNameContainer().setName("doc1"); - doc1.getNameContainer().getNameObject().setPrimaryName("doc1"); doc1.getNameContainers().add(new NameObjectContainer("doc1", new NameObject("doc1", "doc1"))); doc1.getNameContainerMap().put("doc1", new NameObjectContainer("doc1", new NameObject("doc1", "doc1"))); doc2 = new Document("doc2"); @@ -67,8 +65,6 @@ protected void prepareData(EntityManager em) { doc2.setNameObject(new NameObject("doc2", "doc2")); doc2.getNames().add(new NameObject("doc2", "doc2")); doc2.getNameMap().put("doc1", new NameObject("doc2", "doc2")); - doc2.getNameContainer().setName("doc2"); - doc2.getNameContainer().getNameObject().setPrimaryName("doc2"); doc2.getNameContainers().add(new NameObjectContainer("doc2", new NameObject("doc2", "doc2"))); doc2.getNameContainerMap().put("doc2", new NameObjectContainer("doc2", new NameObject("doc2", "doc2"))); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java index 54fc79478a..e4595dd12c 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java @@ -26,6 +26,7 @@ import com.blazebit.persistence.view.testsuite.update.AbstractEntityViewUpdateDocumentTest; import com.blazebit.persistence.view.testsuite.update.flatview.nested.mutable.model.UpdatableDocumentView; import com.blazebit.persistence.view.testsuite.update.flatview.nested.mutable.model.UpdatableNameObjectContainerView; +import com.blazebit.persistence.view.testsuite.update.flatview.nested.mutable.model.UpdatableNameObjectContainerView2; import com.blazebit.persistence.view.testsuite.update.flatview.nested.mutable.model.UpdatableNameObjectView; import org.junit.Assert; import org.junit.Test; @@ -55,7 +56,7 @@ public static Object[][] combinations() { @Override protected void registerViewTypes(EntityViewConfiguration cfg) { cfg.addEntityView(UpdatableNameObjectView.class); - cfg.addEntityView(UpdatableNameObjectContainerView.class); + cfg.addEntityView(UpdatableNameObjectContainerView2.class); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableDocumentView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableDocumentView.java index 9be64fc2f9..39f8f2d712 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableDocumentView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableDocumentView.java @@ -37,7 +37,7 @@ public interface UpdatableDocumentView { public Long getVersion(); @UpdatableMapping - public UpdatableNameObjectContainerView getNameContainer(); + public UpdatableNameObjectContainerView2 getNameContainer(); - public void setNameContainer(UpdatableNameObjectContainerView nameContainer); + public void setNameContainer(UpdatableNameObjectContainerView2 nameContainer); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableNameObjectContainerView2.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableNameObjectContainerView2.java new file mode 100644 index 0000000000..cdfea698e3 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/model/UpdatableNameObjectContainerView2.java @@ -0,0 +1,39 @@ +/* + * 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.update.flatview.nested.mutable.model; + +import com.blazebit.persistence.testsuite.entity.NameObjectContainer2; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.UpdatableEntityView; +import com.blazebit.persistence.view.UpdatableMapping; + +/** + * + * @author Christian Beikov + * @since 1.2.0 + */ +@UpdatableEntityView +@EntityView(NameObjectContainer2.class) +public interface UpdatableNameObjectContainerView2 { + + public String getName(); + + @UpdatableMapping + public UpdatableNameObjectView getNameObject(); + + public void setNameObject(UpdatableNameObjectView nameObject); +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/AbstractEntityViewRemoveDocumentTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/AbstractEntityViewRemoveDocumentTest.java index d08bb12513..fa1f767e0d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/AbstractEntityViewRemoveDocumentTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/AbstractEntityViewRemoveDocumentTest.java @@ -65,8 +65,6 @@ protected void prepareData(EntityManager em) { doc1.getNameObject().setPrimaryName("doc1"); doc1.getNames().add(new NameObject("doc1", "doc1")); doc1.getNameMap().put("doc1", new NameObject("doc1", "doc1")); - doc1.getNameContainer().setName("doc1"); - doc1.getNameContainer().getNameObject().setPrimaryName("doc1"); doc1.getNameContainers().add(new NameObjectContainer("doc1", new NameObject("doc1", "doc1"))); doc1.getNameContainerMap().put("doc1", new NameObjectContainer("doc1", new NameObject("doc1", "doc1"))); doc2 = new Document("doc2"); @@ -75,8 +73,6 @@ protected void prepareData(EntityManager em) { doc2.setNameObject(new NameObject("doc2", "doc2")); doc2.getNames().add(new NameObject("doc2", "doc2")); doc2.getNameMap().put("doc1", new NameObject("doc2", "doc2")); - doc2.getNameContainer().setName("doc2"); - doc2.getNameContainer().getNameObject().setPrimaryName("doc2"); doc2.getNameContainers().add(new NameObjectContainer("doc2", new NameObject("doc2", "doc2"))); doc2.getNameContainerMap().put("doc2", new NameObjectContainer("doc2", new NameObject("doc2", "doc2"))); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java index 3e887c7c19..9a63fcb957 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java @@ -67,8 +67,6 @@ protected void prepareData(EntityManager em) { doc1.getNameObject().setPrimaryName("doc1"); doc1.getNames().add(new NameObject("doc1", "doc1")); doc1.getNameMap().put("doc1", new NameObject("doc1", "doc1")); - doc1.getNameContainer().setName("doc1"); - doc1.getNameContainer().getNameObject().setPrimaryName("doc1"); doc1.getNameContainers().add(new NameObjectContainer("doc1", new NameObject("doc1", "doc1"))); doc1.getNameContainerMap().put("doc1", new NameObjectContainer("doc1", new NameObject("doc1", "doc1"))); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/model/UpdatableDocumentForOneToOneView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/model/UpdatableDocumentForOneToOneView.java index cb63ee38a9..d35c015253 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/model/UpdatableDocumentForOneToOneView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/model/UpdatableDocumentForOneToOneView.java @@ -38,7 +38,7 @@ public interface UpdatableDocumentForOneToOneView extends DocumentForOneToOneIdV void setName(String name); @MappingInverse(removeStrategy = InverseRemoveStrategy.SET_NULL) - @UpdatableMapping + @UpdatableMapping(subtypes = { UpdatableDocumentInfoView.class }) DocumentInfoIdView getDocumentInfo(); void setDocumentInfo(DocumentInfoIdView documentInfo); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java index d9be78755f..97848b5331 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.testsuite.update.subview.inverse.unmapped.model; import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.view.AllowUpdatableEntityViews; import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -40,6 +41,7 @@ public interface UpdatableDocumentView extends DocumentIdView { public void setName(String name); @UpdatableMapping + @AllowUpdatableEntityViews PersonIdView getOwner(); void setOwner(PersonIdView owner); diff --git a/entity-view/testsuite/src/test/resources/logging.properties b/entity-view/testsuite/src/test/resources/logging.properties index 497c77b711..fb1c5e4c71 100644 --- a/entity-view/testsuite/src/test/resources/logging.properties +++ b/entity-view/testsuite/src/test/resources/logging.properties @@ -34,6 +34,7 @@ org.eclipse.persistence.level = SEVERE #org.eclipse.persistence.session.default.sql.level = ALL com.blazebit.persistence.spi.EntityManagerFactoryIntegrator.level = OFF +#com.blazebit.persistence.view.SUBTYPE_INFERENCE.level = ALL com.blazebit.persistence.impl.datanucleus.function.DataNucleusEntityManagerFactoryIntegrator.level = OFF # Enable connection pool error logging diff --git a/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaMetamodelAccessor.java b/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaMetamodelAccessor.java index 9600624078..af41ac6fcd 100644 --- a/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaMetamodelAccessor.java +++ b/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaMetamodelAccessor.java @@ -69,4 +69,23 @@ public boolean isCompositeNode(Attribute attr) { || attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE; } + @Override + public boolean isElementCollection(Attribute attribute) { + if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + return true; + } + // Datanucleus kinda messes up the metamodel for some reason + if (attribute instanceof PluralAttribute) { + Type.PersistenceType persistenceType = ((PluralAttribute) attribute).getElementType().getPersistenceType(); + //CHECKSTYLE:OFF: FallThrough + switch (persistenceType) { + case BASIC: + case EMBEDDABLE: + return true; + } + //CHECKSTYLE:ON: FallThrough + } + return false; + } + } diff --git a/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaMetamodelAccessor.java b/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaMetamodelAccessor.java index b17624cb7d..44321250c1 100644 --- a/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaMetamodelAccessor.java +++ b/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaMetamodelAccessor.java @@ -34,7 +34,6 @@ public class DataNucleusJpaMetamodelAccessor extends JpaMetamodelAccessorImpl { private DataNucleusJpaMetamodelAccessor() { } - @Override public boolean isJoinable(Attribute attr) { if (attr.isCollection()) { @@ -69,4 +68,22 @@ public boolean isCompositeNode(Attribute attr) { || attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE; } + @Override + public boolean isElementCollection(Attribute attribute) { + if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { + return true; + } + // Datanucleus kinda messes up the metamodel for some reason + if (attribute instanceof PluralAttribute) { + Type.PersistenceType persistenceType = ((PluralAttribute) attribute).getElementType().getPersistenceType(); + //CHECKSTYLE:OFF: FallThrough + switch (persistenceType) { + case BASIC: + case EMBEDDABLE: + return true; + } + //CHECKSTYLE:ON: FallThrough + } + return false; + } } diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java index 53daaaab8f..a5d5f7820b 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java @@ -671,28 +671,43 @@ public String[] getColumnNames(EntityType ownerType, String elementCollection ComponentType elementType = (ComponentType) persister.getElementType(); String[] propertyNames = elementType.getPropertyNames(); Type[] subtypes = elementType.getSubtypes(); - int dotIndex = -1; - do { - String propertyName; - if (dotIndex == -1) { - propertyName = subAttributeName; - } else { - propertyName = subAttributeName.substring(0, dotIndex); - } - int offset = 0; + String[] propertyParts = subAttributeName.split("\\."); + int offset = 0; + for (int j = 0; j < propertyParts.length - 1; j++) { + String propertyName = propertyParts[j]; + for (int i = 0; i < propertyNames.length; i++) { int span = subtypes[i].getColumnSpan(persister.getFactory()); if (propertyName.equals(propertyNames[i])) { - String[] columnNames = new String[span]; - String[] elementColumnNames = persister.getElementColumnNames(); - System.arraycopy(elementColumnNames, offset, columnNames, 0, span); - return columnNames; + if (subtypes[i] instanceof ComponentType) { + elementType = (ComponentType) subtypes[i]; + propertyNames = elementType.getPropertyNames(); + subtypes = elementType.getSubtypes(); + break; + } else { + String[] columnNames = new String[span]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + return columnNames; + } } else { offset += span; } } - // Component types do not store entries for the id properties of associations so we need to look for a sub-part of the attribute name - } while ((dotIndex = subAttributeName.indexOf('.', dotIndex + 1)) != -1); + } + + String propertyName = propertyParts[propertyParts.length - 1]; + for (int i = 0; i < propertyNames.length; i++) { + int span = subtypes[i].getColumnSpan(persister.getFactory()); + if (propertyName.equals(propertyNames[i])) { + String[] columnNames = new String[span]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + return columnNames; + } else { + offset += span; + } + } } else if (persister.getElementType() instanceof org.hibernate.type.EntityType) { AbstractEntityPersister elementPersister = (AbstractEntityPersister) entityPersisters.get(((org.hibernate.type.EntityType) persister.getElementType()).getAssociatedEntityName()); Type identifierType = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyType(persister.getFactory()); @@ -856,20 +871,37 @@ public String[] getColumnTypes(EntityType ownerType, String elementCollection ComponentType elementType = (ComponentType) persister.getElementType(); String[] propertyNames = elementType.getPropertyNames(); Type[] subtypes = elementType.getSubtypes(); - int dotIndex = -1; - do { - String propertyName; - if (dotIndex == -1) { - propertyName = subAttributeName; - } else { - propertyName = subAttributeName.substring(0, dotIndex); + String[] propertyParts = subAttributeName.split("\\."); + int offset = 0; + for (int j = 0; j < propertyParts.length - 1; j++) { + String propertyName = propertyParts[j]; + + for (int i = 0; i < propertyNames.length; i++) { + int span = subtypes[i].getColumnSpan(persister.getFactory()); + if (propertyName.equals(propertyNames[i])) { + if (subtypes[i] instanceof ComponentType) { + elementType = (ComponentType) subtypes[i]; + propertyNames = elementType.getPropertyNames(); + subtypes = elementType.getSubtypes(); + break; + } else { + columnNames = new String[span]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + break; + } + } else { + offset += span; + } } - int offset = 0; + } + + if (columnNames == null) { + String propertyName = propertyParts[propertyParts.length - 1]; for (int i = 0; i < propertyNames.length; i++) { int span = subtypes[i].getColumnSpan(persister.getFactory()); if (propertyName.equals(propertyNames[i])) { columnNames = new String[span]; - propertyType = subtypes[i]; String[] elementColumnNames = persister.getElementColumnNames(); System.arraycopy(elementColumnNames, offset, columnNames, 0, span); break; @@ -877,8 +909,7 @@ public String[] getColumnTypes(EntityType ownerType, String elementCollection offset += span; } } - // Component type do not store entries for the id properties of associations so we need to look for a sub-part of the attribute name - } while (propertyType == null && (dotIndex = subAttributeName.indexOf('.', dotIndex + 1)) != -1); + } } else if (persister.getElementType() instanceof org.hibernate.type.EntityType) { Type identifierType = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyType(persister.getFactory()); String identifierOrUniqueKeyPropertyName = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyPropertyName(persister.getFactory()); diff --git a/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java b/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java index b6348e7c71..66678c248a 100644 --- a/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java +++ b/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java @@ -255,4 +255,9 @@ public boolean isCompositeNode(Attribute attr) { return attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE || attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE; } + + @Override + public boolean isElementCollection(Attribute attribute) { + return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION; + } }