From 23a6756687aaea2e733deaaacea2360567d70751 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Sat, 21 Jul 2018 15:38:45 +0200 Subject: [PATCH] [#305] Make use of a different entity function node rendering technique to prevent parameter order confusion. Fixes #305 --- CHANGELOG.md | 2 +- .../persistence/spi/ExtendedQuerySupport.java | 6 +- .../impl/AbstractCommonQueryBuilder.java | 302 ++++++++++-- ...stractDeleteCollectionCriteriaBuilder.java | 6 +- .../impl/AbstractFullQueryBuilder.java | 6 +- ...stractInsertCollectionCriteriaBuilder.java | 2 +- .../AbstractModificationCriteriaBuilder.java | 4 +- ...stractUpdateCollectionCriteriaBuilder.java | 6 +- .../impl/BaseDeleteCriteriaBuilderImpl.java | 2 +- .../BaseFinalSetOperationBuilderImpl.java | 2 +- .../impl/BaseInsertCriteriaBuilderImpl.java | 2 +- .../impl/BaseUpdateCriteriaBuilderImpl.java | 2 +- .../CriteriaBuilderConfigurationImpl.java | 7 + .../persistence/impl/JoinManager.java | 434 +++++++----------- .../blazebit/persistence/impl/JoinNode.java | 47 +- .../impl/PaginatedCriteriaBuilderImpl.java | 30 +- .../persistence/impl/ParameterManager.java | 3 +- .../persistence/impl/PredicateManager.java | 39 +- .../impl/ResolvingQueryGenerator.java | 86 ++-- .../impl/function/entity/EntityFunction.java | 125 +++++ .../plan/CustomModificationQueryPlan.java | 6 +- .../CustomReturningModificationQueryPlan.java | 8 +- .../impl/query/CTEQuerySpecification.java | 2 +- ...nInsertModificationQuerySpecification.java | 2 +- .../impl/query/CustomQuerySpecification.java | 100 +--- .../impl/query/EntityFunctionNode.java | 12 +- .../query/ModificationQuerySpecification.java | 4 +- ...nDeleteModificationQuerySpecification.java | 4 +- ...nInsertModificationQuerySpecification.java | 4 +- ...nUpdateModificationQuerySpecification.java | 4 +- ...turningModificationQuerySpecification.java | 4 +- .../persistence/impl/util/SqlUtils.java | 67 +++ .../parser/SimpleQueryGenerator.java | 8 +- .../testsuite/CollectionRoleInsertTest.java | 27 +- .../testsuite/ValuesClauseTest.java | 53 ++- .../core/manual/en_US/04_from_clause.adoc | 2 - .../core/manual/en_US/18_jpql_functions.adoc | 8 + .../DataNucleus51ExtendedQuerySupport.java | 4 +- .../DataNucleusExtendedQuerySupport.java | 4 +- .../base/HibernateExtendedQuerySupport.java | 16 +- ...laze-persistence-1.3.0-Alpha2-release.adoc | 6 +- 41 files changed, 923 insertions(+), 535 deletions(-) create mode 100644 core/impl/src/main/java/com/blazebit/persistence/impl/function/entity/EntityFunction.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd738a1ab..6a2d3c79a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ None ### Bug fixes -None +* Problems with the use of the `VALUES` clause and parameters in the select clause have been fixed ### Backwards-incompatible changes diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedQuerySupport.java b/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedQuerySupport.java index cb1c61ef82..ee9510b0c1 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedQuerySupport.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedQuerySupport.java @@ -116,20 +116,22 @@ public interface ExtendedQuerySupport { * * @param serviceProvider The service provider to access {@linkplain EntityManager} and others * @param participatingQueries The list of participating queries from which to combine parameters + * @param baseQuery The base query which represents the original modification query * @param query The main query to execute * @param sqlOverride The actual SQL query to execute instead of the query's original SQL * @return The update count of the query */ - public int executeUpdate(ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride); + public int executeUpdate(ServiceProvider serviceProvider, List participatingQueries, Query baseQuery, Query query, String sqlOverride); /** * Executes and returns the returning result of the Query by replacing the SQL with the given overriding SQL query. * * @param serviceProvider The service provider to access {@linkplain EntityManager} and others * @param participatingQueries The list of participating queries from which to combine parameters + * @param baseQuery The base query which represents the original modification query * @param exampleQuery The example query providing the result type structure * @param sqlOverride The actual SQL query to execute instead of the query's original SQL * @return The returning result of the query */ - public ReturningResult executeReturning(ServiceProvider serviceProvider, List participatingQueries, Query exampleQuery, String sqlOverride); + public ReturningResult executeReturning(ServiceProvider serviceProvider, List participatingQueries, Query baseQuery, Query exampleQuery, String sqlOverride); } 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 0bba98876d..7600754f82 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 @@ -38,9 +38,11 @@ import com.blazebit.persistence.SubqueryBuilder; import com.blazebit.persistence.SubqueryInitiator; import com.blazebit.persistence.WhereOrBuilder; +import com.blazebit.persistence.impl.util.SqlUtils; import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.parser.expression.ExpressionFactory; import com.blazebit.persistence.parser.expression.PathExpression; +import com.blazebit.persistence.parser.expression.Subquery; import com.blazebit.persistence.parser.expression.SubqueryExpressionFactory; import com.blazebit.persistence.parser.expression.VisitorAdapter; import com.blazebit.persistence.impl.function.entity.ValuesEntity; @@ -66,16 +68,19 @@ import com.blazebit.persistence.impl.transform.SizeTransformationVisitor; import com.blazebit.persistence.impl.transform.SizeTransformerGroup; import com.blazebit.persistence.impl.transform.SubqueryRecursiveExpressionVisitor; +import com.blazebit.persistence.parser.util.JpaMetamodelUtils; import com.blazebit.persistence.spi.ConfigurationSource; import com.blazebit.persistence.spi.DbmsDialect; import com.blazebit.persistence.spi.DbmsModificationState; import com.blazebit.persistence.spi.DbmsStatementType; +import com.blazebit.persistence.spi.ExtendedAttribute; import com.blazebit.persistence.spi.ExtendedManagedType; import com.blazebit.persistence.spi.JpaProvider; import com.blazebit.persistence.spi.JpqlFunction; import com.blazebit.persistence.spi.JpqlMacro; import com.blazebit.persistence.spi.ServiceProvider; import com.blazebit.persistence.spi.SetOperationType; +import com.blazebit.persistence.spi.ValuesStrategy; import javax.persistence.EntityManager; import javax.persistence.Parameter; @@ -83,6 +88,7 @@ import javax.persistence.TemporalType; import javax.persistence.Tuple; import javax.persistence.TypedQuery; +import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.IdentifiableType; import javax.persistence.metamodel.ManagedType; @@ -226,7 +232,7 @@ protected AbstractCommonQueryBuilder(MainQuery mainQuery, boolean isMainQuery, D this.aliasManager = new AliasManager(aliasManager); this.expressionFactory = expressionFactory; - this.queryGenerator = new ResolvingQueryGenerator(this.aliasManager, parameterManager, mainQuery.parameterTransformerFactory, mainQuery.metamodel, jpaProvider, registeredFunctions); + this.queryGenerator = new ResolvingQueryGenerator(this.aliasManager, parameterManager, mainQuery.parameterTransformerFactory, jpaProvider, registeredFunctions); this.joinManager = new JoinManager(mainQuery, queryGenerator, this.aliasManager, parentJoinManager, expressionFactory); if (implicitFromClause) { @@ -1460,6 +1466,15 @@ protected void applyImplicitJoins(JoinVisitor parentVisitor) { return; } + // The first thing we need to do, is reorder values clauses without joins to the end of the from clause roots + // This is an ugly integration detail, but to me, this seems to be the only way to support the values clause in all situations + // We put the values clauses without joins to the end because we will add synthetic predicates to the where clause + // This is necessary in order to have a single query that has all parameters which will simplify a lot + // If a values clause has a join, we will inject the synthetic predicate into that joins on clause which will preserve the correct order + // We just have to hope that Hibernate will not reorder roots when translating to SQL, if it does, this will break.. + // NOTE: If it turns out to be problematic, I can imagine introducing a synthetic SELECT item that is removed in the end for this purpose + joinManager.reorderSimpleValuesClauses(); + final JoinVisitor joinVisitor = new JoinVisitor(mainQuery, parentVisitor, joinManager, parameterManager, !jpaProvider.supportsSingleValuedAssociationIdExpressions()); final List fetchableNodes = new ArrayList<>(); final JoinNodeVisitor joinNodeVisitor = new OnClauseJoinNodeVisitor(joinVisitor) { @@ -1619,7 +1634,7 @@ protected TypedQuery getTypedQuery() { List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, Collections.EMPTY_SET); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new CustomQuerySpecification( this, baseQuery, parameterManager.getParameters(), parameterListNames, limit, offset, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes ); @@ -1653,21 +1668,239 @@ protected List getKeyRestrictedLeftJoinAliases(Query baseQuery, Set getEntityFunctionNodes(Query baseQuery) { List entityFunctionNodes = new ArrayList(); + + ValuesStrategy strategy = mainQuery.dbmsDialect.getValuesStrategy(); + String dummyTable = mainQuery.dbmsDialect.getDummyTable(); + for (JoinNode node : joinManager.getEntityFunctionNodes()) { - String valuesClause = node.getValuesClause(); - String valuesAliases = node.getValuesAliases(); + Class clazz = node.getJavaType(); + int valueCount = node.getValueCount(); + boolean identifiableReference = node.getValuesIdName() != null; + String rootAlias = node.getAlias(); + String castedParameter = node.getValuesCastedParameter(); + String[] attributes = node.getValuesAttributes(); + + // We construct an example query representing the values clause with a SELECT clause that selects the fields in the right order which we need to construct SQL + // that uses proper aliases and filters null values which are there in the first place to pad up parameters in case we don't reach the desired value count + StringBuilder valuesSb = new StringBuilder(20 + valueCount * attributes.length * 3); + Query valuesExampleQuery = getValuesExampleQuery(clazz, valueCount, identifiableReference, rootAlias, castedParameter, attributes, valuesSb, strategy, dummyTable, node); + + String exampleQuerySql = mainQuery.cbf.getExtendedQuerySupport().getSql(mainQuery.em, valuesExampleQuery); + String exampleQuerySqlAlias = mainQuery.cbf.getExtendedQuerySupport().getSqlAlias(mainQuery.em, valuesExampleQuery, "e"); + StringBuilder whereClauseSb = new StringBuilder(exampleQuerySql.length()); + String filterNullsTableAlias = "fltr_nulls_tbl_als_"; + String valuesAliases = getValuesAliases(exampleQuerySqlAlias, attributes.length, exampleQuerySql, whereClauseSb, filterNullsTableAlias, strategy, dummyTable); + + if (strategy == ValuesStrategy.SELECT_VALUES) { + valuesSb.insert(0, valuesAliases); + valuesSb.append(')'); + valuesAliases = null; + } else if (strategy == ValuesStrategy.SELECT_UNION) { + valuesSb.insert(0, valuesAliases); + mainQuery.dbmsDialect.appendExtendedSql(valuesSb, DbmsStatementType.SELECT, true, true, null, Integer.toString(valueCount + 1), "1", null, null); + valuesSb.append(')'); + valuesAliases = null; + } + + boolean filterNulls = mainQuery.getQueryConfiguration().isValuesClauseFilterNullsEnabled(); + if (filterNulls) { + valuesSb.insert(0, "(select * from "); + valuesSb.append(' '); + valuesSb.append(filterNullsTableAlias); + if (valuesAliases != null) { + valuesSb.append(valuesAliases); + valuesAliases = null; + } + valuesSb.append(whereClauseSb); + valuesSb.append(')'); + } + + String valuesClause = valuesSb.toString(); + String valuesTableSqlAlias = exampleQuerySqlAlias; + 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); + } - String valuesTableSqlAlias = cbf.getExtendedQuerySupport().getSqlAlias(em, baseQuery, node.getAlias()); - entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, node.getJavaType(), valuesTableSqlAlias, node.getValueQuery())); + entityFunctionNodes.add(new EntityFunctionNode(valuesClause, valuesAliases, clazz, valuesTableSqlAlias, syntheticPredicate)); } return entityFunctionNodes; } + private String getValuesAliases(String tableAlias, int attributeCount, String exampleQuerySql, StringBuilder whereClauseSb, String filterNullsTableAlias, ValuesStrategy strategy, String dummyTable) { + int startIndex = SqlUtils.indexOfSelect(exampleQuerySql); + int endIndex = exampleQuerySql.indexOf(" from "); + + StringBuilder sb; + + if (strategy == ValuesStrategy.VALUES) { + sb = new StringBuilder((endIndex - startIndex) - (tableAlias.length() + 3) * attributeCount); + sb.append('('); + } else if (strategy == ValuesStrategy.SELECT_VALUES) { + sb = new StringBuilder(endIndex - startIndex); + sb.append("(select "); + } else if (strategy == ValuesStrategy.SELECT_UNION) { + sb = new StringBuilder((endIndex - startIndex) - (tableAlias.length() + 3) * attributeCount); + sb.append("(select "); + } else { + throw new IllegalArgumentException("Unsupported values strategy: " + strategy); + } + + whereClauseSb.append(" where"); + String[] columnNames = SqlUtils.getSelectItemColumns(exampleQuerySql, startIndex); + + for (int i = 0; i < columnNames.length; i++) { + whereClauseSb.append(' '); + if (i > 0) { + whereClauseSb.append("or "); + } + whereClauseSb.append(filterNullsTableAlias); + whereClauseSb.append('.'); + whereClauseSb.append(columnNames[i]); + whereClauseSb.append(" is not null"); + + if (strategy == ValuesStrategy.SELECT_VALUES) { + // TODO: This naming is actually H2 specific + sb.append('c'); + sb.append(i + 1); + sb.append(' '); + } else if (strategy == ValuesStrategy.SELECT_UNION) { + sb.append("null as "); + } + + sb.append(columnNames[i]); + sb.append(','); + } + + if (strategy == ValuesStrategy.VALUES) { + sb.setCharAt(sb.length() - 1, ')'); + } else if (strategy == ValuesStrategy.SELECT_VALUES) { + sb.setCharAt(sb.length() - 1, ' '); + sb.append(" from "); + } else if (strategy == ValuesStrategy.SELECT_UNION) { + sb.setCharAt(sb.length() - 1, ' '); + if (dummyTable != null) { + sb.append(" from "); + sb.append(dummyTable); + } + } + + return sb.toString(); + } + + private Query getValuesExampleQuery(Class clazz, int valueCount, boolean identifiableReference, String prefix, String castedParameter, String[] attributes, StringBuilder valuesSb, ValuesStrategy strategy, String dummyTable, JoinNode valuesNode) { + String[] attributeParameter = new String[attributes.length]; + // This size estimation roughly assumes a maximum attribute name length of 15 + StringBuilder sb = new StringBuilder(50 + valueCount * prefix.length() * attributes.length * 50); + sb.append("SELECT "); + + if (clazz == ValuesEntity.class) { + sb.append("e."); + 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).getAttributes(); + StringBuilder paramBuilder = new StringBuilder(); + for (int i = 0; i < attributes.length; i++) { + sb.append("e."); + ExtendedAttribute entry = mapping.get(attributes[i]); + Attribute attribute = entry.getAttribute(); + String[] columnTypes = entry.getColumnTypes(); + attributeParameter[i] = getCastedParameters(paramBuilder, mainQuery.dbmsDialect, columnTypes); + sb.append(attributes[i]); + + // 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()); + Attribute attributeTypeIdAttribute = JpaMetamodelUtils.getIdAttribute((IdentifiableType) managedAttributeType); + sb.append('.'); + sb.append(attributeTypeIdAttribute.getName()); + } + + sb.append(','); + } + } + + sb.setCharAt(sb.length() - 1, ' '); + sb.append("FROM "); + sb.append(clazz.getName()); + sb.append(" e WHERE "); + joinManager.renderValuesClausePredicate(sb, valuesNode, "e"); + + if (strategy == ValuesStrategy.SELECT_VALUES || strategy == ValuesStrategy.VALUES) { + valuesSb.append("(VALUES "); + } else if (strategy == ValuesStrategy.SELECT_UNION) { + // Nothing to do here + } else { + throw new IllegalArgumentException("Unsupported values strategy: " + strategy); + } + + for (int i = 0; i < valueCount; i++) { + if (strategy == ValuesStrategy.SELECT_UNION) { + valuesSb.append(" union all select "); + } else { + valuesSb.append('('); + } + + for (int j = 0; j < attributes.length; j++) { + valuesSb.append(attributeParameter[j]); + valuesSb.append(','); + } + + if (strategy == ValuesStrategy.SELECT_UNION) { + valuesSb.setCharAt(valuesSb.length() - 1, ' '); + if (dummyTable != null) { + valuesSb.append("from "); + valuesSb.append(dummyTable); + valuesSb.append(' '); + } + } else { + valuesSb.setCharAt(valuesSb.length() - 1, ')'); + valuesSb.append(','); + } + } + + if (strategy == ValuesStrategy.SELECT_UNION) { + valuesSb.setCharAt(valuesSb.length() - 1, ' '); + } else { + valuesSb.setCharAt(valuesSb.length() - 1, ')'); + } + + String exampleQueryString = sb.toString(); + return mainQuery.em.createQuery(exampleQueryString); + } + + private static String getCastedParameters(StringBuilder sb, DbmsDialect dbmsDialect, String[] types) { + sb.setLength(0); + if (dbmsDialect.needsCastParameters()) { + for (int i = 0; i < types.length; i++) { + sb.append(dbmsDialect.cast("?", types[i])); + sb.append(','); + } + } else { + for (int i = 0; i < types.length; i++) { + sb.append("?,"); + } + } + + return sb.substring(0, sb.length() - 1); + } + protected boolean renderCteNodes(boolean isSubquery) { return isMainQuery && !isSubquery; } - protected List getCteNodes(Query baseQuery, boolean isSubquery) { + protected List getCteNodes(boolean isSubquery) { List cteNodes = new ArrayList(); // NOTE: Delete statements could cause CTEs to be generated for the cascading deletes if (!isMainQuery || isSubquery || !dbmsDialect.supportsWithClause() || !mainQuery.cteManager.hasCtes() && statementType != DbmsStatementType.DELETE || statementType != DbmsStatementType.SELECT && !mainQuery.dbmsDialect.supportsWithClauseInModificationQuery()) { @@ -1878,7 +2111,7 @@ protected void prepareAndCheck() { public void visit(JoinNode node) { Class cteType = node.getJavaType(); // Except for VALUES clause from nodes, every cte type must be defined - if (node.getValueQuery() == null && mainQuery.metamodel.getCte(cteType) != null) { + if (node.getValueCount() == 0 && mainQuery.metamodel.getCte(cteType) != null) { if (mainQuery.cteManager.getCte(cteType) == null) { throw new IllegalStateException("Usage of CTE '" + cteType.getName() + "' without definition!"); } @@ -1917,16 +2150,23 @@ protected String buildBaseQueryString(boolean externalRepresentation) { } protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) { - appendSelectClause(sbSelectFrom); - List whereClauseConjuncts = appendFromClause(sbSelectFrom, externalRepresentation); - appendWhereClause(sbSelectFrom, whereClauseConjuncts); - appendGroupByClause(sbSelectFrom); - appendOrderByClause(sbSelectFrom); - if (externalRepresentation && !isMainQuery) { - // Don't render the LIMIT clause for subqueries, but let the parent render it in a LIMIT function - if (!(this instanceof SubqueryInternalBuilder)) { - applyJpaLimit(sbSelectFrom); + boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation(); + queryGenerator.setExternalRepresentation(externalRepresentation); + try { + appendSelectClause(sbSelectFrom); + List whereClauseEndConjuncts = this instanceof Subquery ? new ArrayList() : null; + List whereClauseConjuncts = appendFromClause(sbSelectFrom, whereClauseEndConjuncts, externalRepresentation); + appendWhereClause(sbSelectFrom, whereClauseConjuncts, whereClauseEndConjuncts); + appendGroupByClause(sbSelectFrom); + appendOrderByClause(sbSelectFrom); + if (externalRepresentation && !isMainQuery) { + // Don't render the LIMIT clause for subqueries, but let the parent render it in a LIMIT function + if (!(this instanceof SubqueryInternalBuilder)) { + applyJpaLimit(sbSelectFrom); + } } + } finally { + queryGenerator.setExternalRepresentation(originalExternalRepresentation); } } @@ -1947,34 +2187,40 @@ protected void appendSelectClause(StringBuilder sbSelectFrom) { selectManager.buildSelect(sbSelectFrom, false); } - protected List appendFromClause(StringBuilder sbSelectFrom, boolean externalRepresentation) { + protected List appendFromClause(StringBuilder sbSelectFrom, List whereClauseEndConjuncts, boolean externalRepresentation) { List whereClauseConjuncts = new ArrayList<>(); - joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, nodesToFetch); + joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, whereClauseConjuncts, whereClauseEndConjuncts, explicitVersionEntities, nodesToFetch); return whereClauseConjuncts; } - protected void appendWhereClause(StringBuilder sbSelectFrom) { - appendWhereClause(sbSelectFrom, Collections.emptyList()); + protected void appendWhereClause(StringBuilder sbSelectFrom, boolean externalRepresentation) { + boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation(); + queryGenerator.setExternalRepresentation(externalRepresentation); + try { + appendWhereClause(sbSelectFrom, Collections.emptyList(), Collections.emptyList()); + } finally { + queryGenerator.setExternalRepresentation(originalExternalRepresentation); + } } - protected void appendWhereClause(StringBuilder sbSelectFrom, List whereClauseConjuncts) { + protected void appendWhereClause(StringBuilder sbSelectFrom, List whereClauseConjuncts, List whereClauseEndConjuncts) { KeysetLink keysetLink = keysetManager.getKeysetLink(); if (keysetLink == null || keysetLink.getKeysetMode() == KeysetMode.NONE) { - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, whereClauseEndConjuncts); } else { sbSelectFrom.append(" WHERE "); + if (whereManager.hasPredicates()) { + whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, whereClauseEndConjuncts); + sbSelectFrom.append(" AND "); + } + int positionalOffset = parameterManager.getPositionalOffset(); if (mainQuery.getQueryConfiguration().isOptimizedKeysetPredicateRenderingEnabled()) { keysetManager.buildOptimizedKeysetPredicate(sbSelectFrom, positionalOffset); } else { keysetManager.buildKeysetPredicate(sbSelectFrom, positionalOffset); } - - if (whereManager.hasPredicates()) { - sbSelectFrom.append(" AND "); - whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts); - } } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractDeleteCollectionCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractDeleteCollectionCriteriaBuilder.java index 56f2caecad..b8c7ee4db7 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractDeleteCollectionCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractDeleteCollectionCriteriaBuilder.java @@ -66,7 +66,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external sbSelectFrom.append(entityType.getName()); sbSelectFrom.append('(').append(collectionName).append(") "); sbSelectFrom.append(entityAlias); - appendWhereClause(sbSelectFrom); + appendWhereClause(sbSelectFrom, externalRepresentation); } else { // The internal representation is just a "hull" to hold the parameters at the appropriate positions sbSelectFrom.append("SELECT 1 FROM "); @@ -76,7 +76,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external sbSelectFrom.append(" LEFT JOIN "); sbSelectFrom.append(entityAlias).append('.').append(collectionName) .append(' ').append(CollectionDeleteModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS); - appendWhereClause(sbSelectFrom); + appendWhereClause(sbSelectFrom, externalRepresentation); } } @@ -120,7 +120,7 @@ private QuerySpecification getQuerySpecification(Query baseQuery, Query exam boolean isEmbedded = this instanceof ReturningBuilder; boolean shouldRenderCteNodes = renderCteNodes(isEmbedded); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, isEmbedded) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(isEmbedded) : Collections.EMPTY_LIST; // Prepare a Map // This is used to replace references to id columns properly in the final sql query diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java index d2f95fdd4c..15019b4ad8 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java @@ -139,11 +139,11 @@ private String buildPageCountQueryString(StringBuilder sbSelectFrom, boolean ext // The count query does not have any fetch owners Set countNodesToFetch = Collections.emptySet(); // Collect usage of collection join nodes to optimize away the count distinct - Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT), null, true, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch); + Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT), null, true, externalRepresentation, whereClauseConjuncts, null, explicitVersionEntities, countNodesToFetch); // TODO: Maybe we can improve this and treat array access joins like non-collection join nodes boolean hasCollectionJoinUsages = collectionJoinNodes.size() > 0; - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); // Count distinct is obviously unnecessary if we have no collection joins if (!hasCollectionJoinUsages) { @@ -189,7 +189,7 @@ public TypedQuery getCountQuery() { List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT)); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new CustomQuerySpecification( this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes ); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java index 205210a04c..c292a025f0 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java @@ -162,7 +162,7 @@ private QuerySpecification getQuerySpecification(Query baseQuery, Query exam boolean isEmbedded = this instanceof ReturningBuilder; boolean shouldRenderCteNodes = renderCteNodes(isEmbedded); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, isEmbedded) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(isEmbedded) : Collections.EMPTY_LIST; ExtendedQuerySupport extendedQuerySupport = getService(ExtendedQuerySupport.class); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java index 5c045a3a77..f94f40d320 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java @@ -177,7 +177,7 @@ protected Query getQuery(Map includedModification boolean isEmbedded = this instanceof ReturningBuilder; String[] returningColumns = getReturningColumns(); boolean shouldRenderCteNodes = renderCteNodes(isEmbedded); - List ctes = shouldRenderCteNodes ? getCteNodes(query, isEmbedded) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(isEmbedded) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new ModificationQuerySpecification( this, @@ -345,7 +345,7 @@ public TypedQuery> getWithReturningQuery(ReturningObjectB protected TypedQuery> getExecuteWithReturningQuery(TypedQuery exampleQuery, Query baseQuery, String[] returningColumns, ReturningObjectBuilder objectBuilder) { Set parameterListNames = parameterManager.getParameterListNames(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new ReturningModificationQuerySpecification( this, baseQuery, exampleQuery, parameterManager.getParameters(), parameterListNames, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes, returningColumns, objectBuilder ); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java index e5f82c64f0..88f14c3367 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java @@ -110,7 +110,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external sbSelectFrom.append('(').append(collectionName).append(") "); sbSelectFrom.append(entityAlias); appendSetClause(sbSelectFrom); - appendWhereClause(sbSelectFrom); + appendWhereClause(sbSelectFrom, externalRepresentation); } else { // The internal representation is just a "hull" to hold the parameters at the appropriate positions sbSelectFrom.append("SELECT 1 FROM "); @@ -120,7 +120,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external sbSelectFrom.append(" LEFT JOIN "); sbSelectFrom.append(entityAlias).append('.').append(collectionName) .append(' ').append(CollectionUpdateModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS); - appendWhereClause(sbSelectFrom); + appendWhereClause(sbSelectFrom, externalRepresentation); // Create the select query strings that are used for the set items // The idea is to encode a set item as an equality predicate in a dedicated query @@ -205,7 +205,7 @@ private QuerySpecification getQuerySpecification(Query baseQuery, Query exam boolean isEmbedded = this instanceof ReturningBuilder; boolean shouldRenderCteNodes = renderCteNodes(isEmbedded); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, isEmbedded) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(isEmbedded) : Collections.EMPTY_LIST; ExtendedQuerySupport extendedQuerySupport = getService(ExtendedQuerySupport.class); String sql = extendedQuerySupport.getSql(em, baseQuery); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseDeleteCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseDeleteCriteriaBuilderImpl.java index 99f72a782b..501402a1b1 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseDeleteCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseDeleteCriteriaBuilderImpl.java @@ -36,7 +36,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external sbSelectFrom.append("DELETE FROM "); sbSelectFrom.append(entityType.getName()).append(' '); sbSelectFrom.append(entityAlias); - appendWhereClause(sbSelectFrom); + appendWhereClause(sbSelectFrom, externalRepresentation); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseFinalSetOperationBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseFinalSetOperationBuilderImpl.java index b61ef2163c..a38aa8cd48 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseFinalSetOperationBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseFinalSetOperationBuilderImpl.java @@ -260,7 +260,7 @@ protected TypedQuery getTypedQuery() { List keyRestrictedLeftJoinAliases = Collections.emptyList(); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new SetOperationQuerySpecification( this, leftMostQuery, diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java index c7c910c77e..eb7551957a 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java @@ -167,7 +167,7 @@ protected Query getQuery(Map includedModification boolean isEmbedded = this instanceof ReturningBuilder; String[] returningColumns = getReturningColumns(); boolean shouldRenderCteNodes = renderCteNodes(isEmbedded); - List ctes = shouldRenderCteNodes ? getCteNodes(query, isEmbedded) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(isEmbedded) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new ModificationQuerySpecification( this, diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java index 47baf0b415..473aecce07 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java @@ -172,7 +172,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external sbSelectFrom.append(entityType.getName()).append(' '); sbSelectFrom.append(entityAlias); appendSetClause(sbSelectFrom); - appendWhereClause(sbSelectFrom); + appendWhereClause(sbSelectFrom, externalRepresentation); } protected void appendSetClause(StringBuilder sbSelectFrom) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderConfigurationImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderConfigurationImpl.java index b446f13acd..ee8f7c0cf6 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderConfigurationImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/CriteriaBuilderConfigurationImpl.java @@ -24,6 +24,7 @@ import com.blazebit.persistence.impl.dialect.MySQLDbmsDialect; import com.blazebit.persistence.impl.dialect.OracleDbmsDialect; import com.blazebit.persistence.impl.dialect.PostgreSQLDbmsDialect; +import com.blazebit.persistence.impl.function.entity.EntityFunction; import com.blazebit.persistence.impl.function.subquery.SubqueryFunction; import com.blazebit.persistence.parser.expression.ConcurrentHashMapExpressionCache; import com.blazebit.persistence.impl.function.cast.CastFunction; @@ -220,6 +221,12 @@ private void loadFunctions() { jpqlFunctionGroup.add("sybase", new TransactSQLPagePositionFunction()); jpqlFunctionGroup.add("microsoft", new TransactSQLPagePositionFunction()); registerFunction(jpqlFunctionGroup); + + // entity_function + + jpqlFunctionGroup = new JpqlFunctionGroup(EntityFunction.FUNCTION_NAME, false); + jpqlFunctionGroup.add(null, new EntityFunction()); + registerFunction(jpqlFunctionGroup); // set operations 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 7ae72c3a3b..884c6ab19a 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 @@ -57,16 +57,10 @@ import com.blazebit.persistence.impl.transform.ExpressionModifierVisitor; import com.blazebit.persistence.parser.util.ExpressionUtils; import com.blazebit.persistence.parser.util.JpaMetamodelUtils; -import com.blazebit.persistence.impl.util.SqlUtils; -import com.blazebit.persistence.spi.DbmsDialect; import com.blazebit.persistence.spi.DbmsModificationState; -import com.blazebit.persistence.spi.DbmsStatementType; -import com.blazebit.persistence.spi.ExtendedAttribute; import com.blazebit.persistence.spi.ExtendedManagedType; import com.blazebit.persistence.spi.JpaProvider; -import com.blazebit.persistence.spi.ValuesStrategy; -import javax.persistence.Query; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.EmbeddableType; import javax.persistence.metamodel.EntityType; @@ -79,6 +73,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; @@ -86,6 +81,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -97,13 +93,19 @@ public class JoinManager extends AbstractManager { private static final Logger LOG = Logger.getLogger(JoinManager.class.getName()); + private static final Comparator> ATTRIBUTE_NAME_COMPARATOR = new Comparator>() { + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getName().compareTo(o2.getName()); + } + }; // we might have multiple nodes that depend on the same unresolved alias, // hence we need a List of NodeInfos. // e.g. SELECT a.X, a.Y FROM A a // a is unresolved for both X and Y private final List rootNodes = new ArrayList(1); - private final Set entityFunctionNodes = new LinkedHashSet(); + private final Set entityFunctionNodes = new LinkedHashSet<>(); // root entity class private final String joinRestrictionKeyword; private final MainQuery mainQuery; @@ -138,7 +140,7 @@ void applyFrom(JoinManager joinManager) { for (JoinNode node : joinManager.rootNodes) { JoinNode rootNode = applyFrom(node); - if (node.getValueQuery() != null) { + if (node.getValueCount() > 0) { // TODO: At the moment the value type is without meaning ParameterManager.ParameterImpl param = joinManager.parameterManager.getParameter(node.getAlias()); ValuesParameterBinder binder = ((ParameterManager.ValuesParameterWrapper) param.getParameterValue()).getBinder(); @@ -274,11 +276,7 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int if (rootAlias == null) { throw new IllegalArgumentException("Illegal empty alias for the VALUES clause: " + clazz.getName()); } - - ValuesStrategy strategy = mainQuery.dbmsDialect.getValuesStrategy(); - String dummyTable = mainQuery.dbmsDialect.getDummyTable(); - - // TODO: we should do batching to avoid filling query caches + // TODO: we should pad the value count to avoid filling query caches ManagedType managedType = mainQuery.metamodel.getManagedType(clazz); String idAttributeName = null; Set> attributeSet; @@ -289,7 +287,7 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int attributeSet = (Set>) (Set) Collections.singleton(idAttribute); } else { Set> originalAttributeSet = (Set>) (Set) managedType.getAttributes(); - attributeSet = new LinkedHashSet<>(originalAttributeSet.size()); + attributeSet = new TreeSet<>(ATTRIBUTE_NAME_COMPARATOR); for (Attribute attr : originalAttributeSet) { // Filter out collection attributes if (!attr.isCollection()) { @@ -301,44 +299,11 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int String[][] parameterNames = new String[valueCount][attributeSet.size()]; ValueRetriever[] pathExpressions = new ValueRetriever[attributeSet.size()]; - StringBuilder valuesSb = new StringBuilder(20 + valueCount * attributeSet.size() * 3); - Query valuesExampleQuery = getValuesExampleQuery(clazz, identifiableReference, rootAlias, typeName, castedParameter, attributeSet, parameterNames, pathExpressions, valuesSb, strategy, dummyTable); + String[] attributes = initializeValuesParameter(clazz, identifiableReference, rootAlias, attributeSet, parameterNames, pathExpressions); parameterManager.registerValuesParameter(rootAlias, valueClazz, parameterNames, pathExpressions); - String exampleQuerySql = mainQuery.cbf.getExtendedQuerySupport().getSql(mainQuery.em, valuesExampleQuery); - String exampleQuerySqlAlias = mainQuery.cbf.getExtendedQuerySupport().getSqlAlias(mainQuery.em, valuesExampleQuery, "e"); - StringBuilder whereClauseSb = new StringBuilder(exampleQuerySql.length()); - String filterNullsTableAlias = "fltr_nulls_tbl_als_"; - String valuesAliases = getValuesAliases(exampleQuerySqlAlias, attributeSet.size(), exampleQuerySql, whereClauseSb, filterNullsTableAlias, strategy, dummyTable); - - if (strategy == ValuesStrategy.SELECT_VALUES) { - valuesSb.insert(0, valuesAliases); - valuesSb.append(')'); - valuesAliases = null; - } else if (strategy == ValuesStrategy.SELECT_UNION) { - valuesSb.insert(0, valuesAliases); - mainQuery.dbmsDialect.appendExtendedSql(valuesSb, DbmsStatementType.SELECT, true, true, null, Integer.toString(valueCount + 1), "1", null, null); - valuesSb.append(')'); - valuesAliases = null; - } - - boolean filterNulls = mainQuery.getQueryConfiguration().isValuesClauseFilterNullsEnabled(); - if (filterNulls) { - valuesSb.insert(0, "(select * from "); - valuesSb.append(' '); - valuesSb.append(filterNullsTableAlias); - if (valuesAliases != null) { - valuesSb.append(valuesAliases); - valuesAliases = null; - } - valuesSb.append(whereClauseSb); - valuesSb.append(')'); - } - - String valuesClause = valuesSb.toString(); - JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, true, true, aliasManager); - JoinNode rootNode = JoinNode.createValuesRootNode(managedType, typeName, valueCount, idAttributeName, valuesExampleQuery, valuesClause, valuesAliases, rootAliasInfo); + JoinNode rootNode = JoinNode.createValuesRootNode(managedType, typeName, valueCount, idAttributeName, castedParameter, attributes, rootAliasInfo); rootAliasInfo.setJoinNode(rootNode); rootNodes.add(rootNode); // register root alias in aliasManager @@ -347,67 +312,6 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int return rootAlias; } - private String getValuesAliases(String tableAlias, int attributeCount, String exampleQuerySql, StringBuilder whereClauseSb, String filterNullsTableAlias, ValuesStrategy strategy, String dummyTable) { - int startIndex = SqlUtils.indexOfSelect(exampleQuerySql); - int endIndex = exampleQuerySql.indexOf(" from "); - - StringBuilder sb; - - if (strategy == ValuesStrategy.VALUES) { - sb = new StringBuilder((endIndex - startIndex) - (tableAlias.length() + 3) * attributeCount); - sb.append('('); - } else if (strategy == ValuesStrategy.SELECT_VALUES) { - sb = new StringBuilder(endIndex - startIndex); - sb.append("(select "); - } else if (strategy == ValuesStrategy.SELECT_UNION) { - sb = new StringBuilder((endIndex - startIndex) - (tableAlias.length() + 3) * attributeCount); - sb.append("(select "); - } else { - throw new IllegalArgumentException("Unsupported values strategy: " + strategy); - } - - whereClauseSb.append(" where"); - String[] columnNames = SqlUtils.getSelectItemColumns(exampleQuerySql, startIndex); - - for (int i = 0; i < columnNames.length; i++) { - whereClauseSb.append(' '); - if (i > 0) { - whereClauseSb.append("or "); - } - whereClauseSb.append(filterNullsTableAlias); - whereClauseSb.append('.'); - whereClauseSb.append(columnNames[i]); - whereClauseSb.append(" is not null"); - - if (strategy == ValuesStrategy.SELECT_VALUES) { - // TODO: This naming is actually H2 specific - sb.append('c'); - sb.append(i + 1); - sb.append(' '); - } else if (strategy == ValuesStrategy.SELECT_UNION) { - sb.append("null as "); - } - - sb.append(columnNames[i]); - sb.append(','); - } - - if (strategy == ValuesStrategy.VALUES) { - sb.setCharAt(sb.length() - 1, ')'); - } else if (strategy == ValuesStrategy.SELECT_VALUES) { - sb.setCharAt(sb.length() - 1, ' '); - sb.append(" from "); - } else if (strategy == ValuesStrategy.SELECT_UNION) { - sb.setCharAt(sb.length() - 1, ' '); - if (dummyTable != null) { - sb.append(" from "); - sb.append(dummyTable); - } - } - - return sb.toString(); - } - /** * @author Christian Beikov * @since 1.2.0 @@ -419,148 +323,39 @@ public Object getValue(Object target) { } } - private static String getCastedParameters(StringBuilder sb, DbmsDialect dbmsDialect, String[] types) { - sb.setLength(0); - if (dbmsDialect.needsCastParameters()) { - for (int i = 0; i < types.length; i++) { - sb.append(dbmsDialect.cast("?", types[i])); - sb.append(','); - } - } else { - for (int i = 0; i < types.length; i++) { - sb.append("?,"); - } - } - - return sb.substring(0, sb.length() - 1); - } - - private Query getValuesExampleQuery(Class clazz, boolean identifiableReference, String prefix, String typeName, String castedParameter, Set> attributeSet, String[][] parameterNames, ValueRetriever[] pathExpressions, StringBuilder valuesSb, ValuesStrategy strategy, String dummyTable) { + private String[] initializeValuesParameter(Class clazz, boolean identifiableReference, String prefix, Set> attributeSet, String[][] parameterNames, ValueRetriever[] pathExpressions) { int valueCount = parameterNames.length; String[] attributes = new String[attributeSet.size()]; - String[] attributeParameter = new String[attributeSet.size()]; - // This size estimation roughly assumes a maximum attribute name length of 15 - StringBuilder sb = new StringBuilder(50 + valueCount * prefix.length() * attributeSet.size() * 50); - sb.append("SELECT "); if (clazz == ValuesEntity.class) { - sb.append("e."); - attributes[0] = attributeSet.iterator().next().getName(); - attributeParameter[0] = mainQuery.dbmsDialect.needsCastParameters() ? castedParameter : "?"; pathExpressions[0] = new SimpleValueRetriever(); - sb.append(attributes[0]); - sb.append(','); + 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) { - sb.append("e."); Attribute attribute = attributeSet.iterator().next(); - attributes[0] = attribute.getName(); - String[] columnTypes = metamodel.getManagedType(ExtendedManagedType.class, clazz).getAttribute(attribute.getName()).getColumnTypes(); - attributeParameter[0] = getCastedParameters(new StringBuilder(), mainQuery.dbmsDialect, columnTypes); - pathExpressions[0] = com.blazebit.reflection.ExpressionUtils.getExpression(clazz, attributes[0]); - sb.append(attributes[0]); - sb.append(','); + 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(); - Map mapping = metamodel.getManagedType(ExtendedManagedType.class, clazz).getAttributes(); - StringBuilder paramBuilder = new StringBuilder(); - for (int i = 0; i < attributes.length; i++) { - sb.append("e."); + for (int i = 0; i < attributeSet.size(); i++) { Attribute attribute = iter.next(); - attributes[i] = attribute.getName(); - ExtendedAttribute entry = mapping.get(attribute.getName()); - String[] columnTypes = entry.getColumnTypes(); - attributeParameter[i] = getCastedParameters(paramBuilder, mainQuery.dbmsDialect, columnTypes); - pathExpressions[i] = com.blazebit.reflection.ExpressionUtils.getExpression(clazz, attributes[i]); - sb.append(attributes[i]); - - // 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 = metamodel.managedType(entry.getElementClass()); - Attribute attributeTypeIdAttribute = JpaMetamodelUtils.getIdAttribute((IdentifiableType) managedAttributeType); - sb.append('.'); - sb.append(attributeTypeIdAttribute.getName()); + String attributeName = attribute.getName(); + attributes[i] = attributeName; + pathExpressions[i] = com.blazebit.reflection.ExpressionUtils.getExpression(clazz, attributeName); + for (int j = 0; j < valueCount; j++) { + parameterNames[j][i] = prefix + '_' + attributeName + '_' + j; } - - sb.append(','); } } - sb.setCharAt(sb.length() - 1, ' '); - sb.append("FROM "); - sb.append(clazz.getName()); - sb.append(" e WHERE 1=1"); - - if (strategy == ValuesStrategy.SELECT_VALUES || strategy == ValuesStrategy.VALUES) { - valuesSb.append("(VALUES "); - } else if (strategy == ValuesStrategy.SELECT_UNION) { - // Nothing to do here - } else { - throw new IllegalArgumentException("Unsupported values strategy: " + strategy); - } - - for (int i = 0; i < valueCount; i++) { - if (strategy == ValuesStrategy.SELECT_UNION) { - valuesSb.append(" union all select "); - } else { - valuesSb.append('('); - } - - for (int j = 0; j < attributes.length; j++) { - sb.append(" OR "); - if (typeName != null) { - sb.append("TREAT_"); - sb.append(typeName.toUpperCase()); - sb.append('('); - sb.append("e."); - sb.append(attributes[j]); - sb.append(')'); - } else { - sb.append("e."); - sb.append(attributes[j]); - } - - sb.append(" = "); - - sb.append(':'); - int start = sb.length(); - - sb.append(prefix); - sb.append('_'); - sb.append(attributes[j]); - sb.append('_').append(i); - - String paramName = sb.substring(start, sb.length()); - parameterNames[i][j] = paramName; - - valuesSb.append(attributeParameter[j]); - valuesSb.append(','); - } - - if (strategy == ValuesStrategy.SELECT_UNION) { - valuesSb.setCharAt(valuesSb.length() - 1, ' '); - if (dummyTable != null) { - valuesSb.append("from "); - valuesSb.append(dummyTable); - valuesSb.append(' '); - } - } else { - valuesSb.setCharAt(valuesSb.length() - 1, ')'); - valuesSb.append(','); - } - } - - if (strategy == ValuesStrategy.SELECT_UNION) { - valuesSb.setCharAt(valuesSb.length() - 1, ' '); - } else { - valuesSb.setCharAt(valuesSb.length() - 1, ')'); - } - - String exampleQueryString = sb.toString(); - Query q = mainQuery.em.createQuery(exampleQueryString); - - return q; + return attributes; } String addRoot(EntityType entityType, String rootAlias) { @@ -799,13 +594,34 @@ public SubqueryInitiatorFactory getSubqueryInitFactory() { return subqueryInitFactory; } - Set buildClause(StringBuilder sb, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean externalRepresenation, List whereConjuncts, Map, Map> explicitVersionEntities, Set nodesToFetch) { + void reorderSimpleValuesClauses() { + List newRootNodes = new ArrayList<>(); + List noJoinValuesNodes = new ArrayList<>(); + for (JoinNode rootNode : rootNodes) { + if (isNoJoinValuesNode(rootNode)) { + noJoinValuesNodes.add(rootNode); + } else { + newRootNodes.add(rootNode); + } + } + + newRootNodes.addAll(noJoinValuesNodes); + rootNodes.clear(); + rootNodes.addAll(newRootNodes); + } + + private static boolean isNoJoinValuesNode(JoinNode rootNode) { + return rootNode.getValueCount() > 0 && rootNode.getNodes().isEmpty() && rootNode.getTreatedJoinNodes().isEmpty() && rootNode.getEntityJoinNodes().isEmpty(); + } + + Set buildClause(StringBuilder sb, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean externalRepresenation, List whereConjuncts, List syntheticSubqueryValuesWhereClauseConjuncts, Map, Map> explicitVersionEntities, Set nodesToFetch) { final boolean renderFetches = !clauseExclusions.contains(ClauseType.SELECT); StringBuilder tempSb = null; collectionJoinNodes.clear(); renderedJoins.clear(); sb.append(" FROM "); + StringBuilder noJoinValuesNodesSb = new StringBuilder(); // TODO: we might have dependencies to other from clause elements which should also be accounted for List nodes = rootNodes; int size = nodes.size(); @@ -864,15 +680,38 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St // TODO: not sure if needed since applyImplicitJoins will already invoke that rootNode.registerDependencies(); - applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + + JoinNode valuesNode = null; + if (rootNode.getValueCount() > 0) { + 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) { + if (syntheticSubqueryValuesWhereClauseConjuncts.isEmpty()) { + syntheticSubqueryValuesWhereClauseConjuncts.add("1=1"); + } + String exampleAttributeName = "value"; + if (rootNode.getType() instanceof IdentifiableType) { + exampleAttributeName = JpaMetamodelUtils.getIdAttribute(rootNode.getEntityType()).getName(); + } + syntheticSubqueryValuesWhereClauseConjuncts.add(rootNode.getAlias() + "." + exampleAttributeName + " IS NULL"); + } + } + if (!rootNode.getNodes().isEmpty()) { + applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = null; + } for (JoinNode treatedNode : rootNode.getTreatedJoinNodes().values()) { - applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + if (!treatedNode.getNodes().isEmpty()) { + applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = null; + } } if (!rootNode.getEntityJoinNodes().isEmpty()) { // TODO: Fix this with #216 boolean isCollection = true; if (mainQuery.jpaProvider.supportsEntityJoin() && !emulateJoins) { - applyJoins(sb, rootNode.getAliasInfo(), new ArrayList<>(rootNode.getEntityJoinNodes()), isCollection, clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + applyJoins(sb, rootNode.getAliasInfo(), new ArrayList<>(rootNode.getEntityJoinNodes()), isCollection, clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = null; } else { Set entityNodes = rootNode.getEntityJoinNodes(); for (JoinNode entityNode : entityNodes) { @@ -907,6 +746,13 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } else { tempSb.setLength(0); } + + if (valuesNode != null) { + renderValuesClausePredicate(tempSb, valuesNode, valuesNode.getAlias()); + whereConjuncts.add(tempSb.toString()); + tempSb.setLength(0); + valuesNode = null; + } queryGenerator.setClauseType(ClauseType.JOIN); queryGenerator.setQueryBuffer(tempSb); SimpleQueryGenerator.BooleanLiteralRenderingContext oldBooleanLiteralRenderingContext = queryGenerator.setBooleanLiteralRenderingContext(SimpleQueryGenerator.BooleanLiteralRenderingContext.PREDICATE); @@ -917,18 +763,73 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } renderedJoins.add(entityNode); - applyJoins(sb, entityNode.getAliasInfo(), entityNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + if (!entityNode.getNodes().isEmpty()) { + applyJoins(sb, entityNode.getAliasInfo(), entityNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = null; + } for (JoinNode treatedNode : entityNode.getTreatedJoinNodes().values()) { - applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = null; } } } } + + if (valuesNode != null) { + if (noJoinValuesNodesSb.length() != 0) { + noJoinValuesNodesSb.append(" AND "); + } + renderValuesClausePredicate(noJoinValuesNodesSb, valuesNode, valuesNode.getAlias()); + } + } + + if (noJoinValuesNodesSb.length() != 0) { + whereConjuncts.add(0, noJoinValuesNodesSb.toString()); } return collectionJoinNodes; } + void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String alias) { + // 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) { + String typeName = rootNode.getValuesTypeName() == null ? null : rootNode.getValuesTypeName().toUpperCase(); + String[] attributes = rootNode.getValuesAttributes(); + String prefix = rootNode.getAlias(); + + for (int i = 0; i < valueCount; i++) { + for (int j = 0; j < attributes.length; j++) { + if (typeName != null) { + sb.append("TREAT_"); + sb.append(typeName); + sb.append('('); + sb.append(alias); + sb.append('.'); + sb.append(attributes[j]); + sb.append(')'); + } else { + sb.append(alias); + sb.append('.'); + sb.append(attributes[j]); + } + + sb.append(" = "); + + sb.append(':'); + sb.append(prefix); + sb.append('_'); + sb.append(attributes[j]); + sb.append('_').append(i); + sb.append(" OR "); + } + } + + sb.setLength(sb.length() - " OR ".length()); + } + } + void verifyBuilderEnded() { joinOnBuilderListener.verifyBuilderEnded(); } @@ -968,7 +869,7 @@ public void apply(ExpressionModifierVisitor visitor) } } - private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts) { + private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode) { if (!renderedJoins.contains(node)) { // We determine the nodes that should be fetched by analyzing the fetch owners during implicit joining final boolean fetch = nodesToFetch.contains(node) && renderFetches; @@ -1013,13 +914,23 @@ private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode n sb.append(node.getAliasInfo().getAlias()); renderedJoins.add(node); + boolean onClause = valuesNode != null || node.getOnPredicate() != null && !node.getOnPredicate().getChildren().isEmpty() || onCondition != null; - if (node.getOnPredicate() != null && !node.getOnPredicate().getChildren().isEmpty()) { + if (onClause) { sb.append(joinRestrictionKeyword); // Always render the ON condition in parenthesis to workaround an EclipseLink bug in entity join parsing sb.append('('); + } + // 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()); + sb.append(" AND "); + } + + if (node.getOnPredicate() != null && !node.getOnPredicate().getChildren().isEmpty()) { if (onCondition != null) { sb.append(onCondition).append(" AND "); } @@ -1032,11 +943,11 @@ private void renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode n queryGenerator.setRenderedJoinNodes(null); queryGenerator.setBooleanLiteralRenderingContext(oldBooleanLiteralRenderingContext); queryGenerator.setClauseType(null); - sb.append(')'); } else if (onCondition != null) { - sb.append(joinRestrictionKeyword); - sb.append('('); sb.append(onCondition); + } + + if (onClause) { sb.append(')'); } } @@ -1142,9 +1053,11 @@ private void renderAlias(StringBuilder sb, JoinNode baseNode, boolean supportsTr } } - private void renderReverseDependency(StringBuilder sb, JoinNode dependency, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts) { + private void renderReverseDependency(StringBuilder sb, JoinNode dependency, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode) { if (dependency.getParent() != null) { - renderReverseDependency(sb, dependency.getParent(), aliasPrefix, renderFetches, nodesToFetch, whereConjuncts); + if (dependency.getParent() != valuesNode) { + renderReverseDependency(sb, dependency.getParent(), aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + } if (!dependency.getDependencies().isEmpty()) { markedJoinNodes.add(dependency); try { @@ -1165,27 +1078,29 @@ private void renderReverseDependency(StringBuilder sb, JoinNode dependency, Stri throw new IllegalStateException(errorSb.toString()); } // render reverse dependencies - renderReverseDependency(sb, dep, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts); + renderReverseDependency(sb, dep, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); } } finally { markedJoinNodes.remove(dependency); } } - renderJoinNode(sb, dependency.getParent().getAliasInfo(), dependency, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts); + if (dependency.getParent() != valuesNode) { + renderJoinNode(sb, dependency.getParent().getAliasInfo(), dependency, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, null); + } } } - private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, Map nodes, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, Set nodesToFetch, List whereConjuncts) { + private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, Map nodes, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode) { for (Map.Entry nodeEntry : nodes.entrySet()) { JoinTreeNode treeNode = nodeEntry.getValue(); List stack = new ArrayList(); stack.addAll(treeNode.getJoinNodes().descendingMap().values()); - applyJoins(sb, joinBase, stack, treeNode.isCollection(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + applyJoins(sb, joinBase, stack, treeNode.isCollection(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); } } - private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List stack, boolean isCollection, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, Set nodesToFetch, List whereConjuncts) { + private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List stack, boolean isCollection, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode) { while (!stack.isEmpty()) { JoinNode node = stack.remove(stack.size() - 1); // If the clauses in which a join node occurs are all excluded or the join node is not mandatory for the cardinality, we skip it @@ -1198,7 +1113,7 @@ private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List // We have to render any dependencies this join node has before actually rendering itself if (!node.getDependencies().isEmpty()) { - renderReverseDependency(sb, node, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts); + renderReverseDependency(sb, node, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); } // Collect the join nodes referring to collections @@ -1207,11 +1122,12 @@ private void applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List } // Finally render this join node - renderJoinNode(sb, joinBase, node, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts); + renderJoinNode(sb, joinBase, node, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = null; // Render child nodes recursively if (!node.getNodes().isEmpty()) { - applyJoins(sb, node.getAliasInfo(), node.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts); + applyJoins(sb, node.getAliasInfo(), node.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, nodesToFetch, whereConjuncts, valuesNode); } } } 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 5b2f04c707..39c57a2e7d 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 @@ -36,7 +36,6 @@ import com.blazebit.persistence.parser.predicate.Predicate; import com.blazebit.persistence.parser.util.JpaMetamodelUtils; -import javax.persistence.Query; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.ManagedType; @@ -76,9 +75,8 @@ public class JoinNode implements From, ExpressionModifier, BaseNode { private final String valuesTypeName; private final int valueCount; private final String valuesIdName; - private final Query valueQuery; - private final String valuesClause; - private final String valuesAliases; + private final String valuesCastedParameter; + private final String[] valuesAttributes; private final String qualificationExpression; private final JoinAliasInfo aliasInfo; private final List joinNodesForTreatConstraint; @@ -109,9 +107,8 @@ private JoinNode(TreatedJoinAliasInfo treatedJoinAliasInfo) { this.valuesTypeName = treatedJoinNode.valuesTypeName; this.valueCount = treatedJoinNode.valueCount; this.valuesIdName = treatedJoinNode.valuesIdName; - this.valueQuery = treatedJoinNode.valueQuery; - this.valuesClause = treatedJoinNode.valuesClause; - this.valuesAliases = treatedJoinNode.valuesAliases; + this.valuesCastedParameter = treatedJoinNode.valuesCastedParameter; + this.valuesAttributes = treatedJoinNode.valuesAttributes; this.aliasInfo = treatedJoinAliasInfo; List joinNodesForTreatConstraint = new ArrayList<>(treatedJoinNode.joinNodesForTreatConstraint.size() + 1); joinNodesForTreatConstraint.addAll(treatedJoinNode.joinNodesForTreatConstraint); @@ -130,9 +127,8 @@ private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType this.valuesTypeName = null; this.valueCount = 0; this.valuesIdName = null; - this.valueQuery = null; - this.valuesClause = null; - this.valuesAliases = null; + this.valuesCastedParameter = null; + this.valuesAttributes = null; this.qualificationExpression = qualificationExpression; this.aliasInfo = aliasInfo; if (treatType != null) { @@ -154,7 +150,7 @@ private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType onUpdate(null); } - private JoinNode(ManagedType nodeType, String valuesTypeName, int valueCount, String valuesIdName, Query valueQuery, String valuesClause, String valuesAliases, JoinAliasInfo aliasInfo) { + private JoinNode(ManagedType nodeType, String valuesTypeName, int valueCount, String valuesIdName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { this.parent = null; this.parentTreeNode = null; this.joinType = null; @@ -165,9 +161,8 @@ private JoinNode(ManagedType nodeType, String valuesTypeName, int valueCount, this.valuesTypeName = valuesTypeName; this.valueCount = valueCount; this.valuesIdName = valuesIdName; - this.valueQuery = valueQuery; - this.valuesClause = valuesClause; - this.valuesAliases = valuesAliases; + this.valuesCastedParameter = valuesCastedParameter; + this.valuesAttributes = valuesAttributes; this.qualificationExpression = null; this.aliasInfo = aliasInfo; this.joinNodesForTreatConstraint = Collections.emptyList(); @@ -178,8 +173,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(ManagedType nodeType, String valuesTypeName, int valueCount, String valuesIdName, Query valueQuery, String valuesClause, String valuesAliases, JoinAliasInfo aliasInfo) { - return new JoinNode(nodeType, valuesTypeName, valueCount, valuesIdName, valueQuery, valuesClause, valuesAliases, aliasInfo); + public static JoinNode createValuesRootNode(ManagedType nodeType, String valuesTypeName, int valueCount, String valuesIdName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { + return new JoinNode(nodeType, valuesTypeName, valueCount, valuesIdName, valuesCastedParameter, valuesAttributes, aliasInfo); } public static JoinNode createCorrelationRootNode(JoinNode correlationParent, String correlationPath, Type nodeType, EntityType treatType, JoinAliasInfo aliasInfo) { @@ -197,8 +192,8 @@ public static JoinNode createAssociationJoinNode(JoinNode parent, JoinTreeNode p public JoinNode cloneRootNode(JoinAliasInfo aliasInfo) { // NOTE: no cloning of treatedJoinNodes and entityJoinNodes is intentional JoinNode newNode; - if (valueQuery != null) { - newNode = createValuesRootNode((ManagedType) nodeType, valuesTypeName, valueCount, valuesIdName, valueQuery, valuesClause, valuesAliases, aliasInfo); + if (valueCount > 0) { + newNode = createValuesRootNode((ManagedType) nodeType, valuesTypeName, valueCount, valuesIdName, valuesCastedParameter, valuesAttributes, aliasInfo); } else if (joinType == null) { newNode = createRootNode((EntityType) nodeType, aliasInfo); } else { @@ -527,20 +522,16 @@ public String getValuesIdName() { return valuesIdName; } - public Query getValueQuery() { - return valueQuery; - } - - public String getValuesClause() { - return valuesClause; + String getValuesTypeName() { + return valuesTypeName; } - public String getValuesAliases() { - return valuesAliases; + public String getValuesCastedParameter() { + return valuesCastedParameter; } - String getValuesTypeName() { - return valuesTypeName; + public String[] getValuesAttributes() { + return valuesAttributes; } public JoinNode getCorrelationParent() { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java index 299a8d6f91..17ca389d96 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java @@ -200,7 +200,7 @@ private TypedQuery getCountQuery(String countQueryString, Class result List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT)); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new CustomQuerySpecification( this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes ); @@ -411,7 +411,7 @@ private Map.Entry, KeysetExtractionObjectBuilder> getObjectQuer List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.noneOf(ClauseType.class)); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new CustomQuerySpecification( this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes ); @@ -464,7 +464,7 @@ private TypedQuery getIdQuery(String idQueryString, boolean normalQuer List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.of(ClauseType.SELECT)); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; QuerySpecification querySpecification = new CustomQuerySpecification( this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes ); @@ -498,7 +498,7 @@ private TypedQuery getObjectQueryById(boolean normalQueryMode, Set List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, EnumSet.complementOf(EnumSet.of(ClauseType.SELECT, ClauseType.ORDER_BY))); List entityFunctionNodes = getEntityFunctionNodes(baseQuery); boolean shouldRenderCteNodes = renderCteNodes(false); - List ctes = shouldRenderCteNodes ? getCteNodes(baseQuery, false) : Collections.EMPTY_LIST; + List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST; Set> parameters = new HashSet<>(parameterManager.getParameters()); parameters.add(baseQuery.getParameter(ID_PARAM_NAME)); QuerySpecification querySpecification = new CustomQuerySpecification( @@ -555,11 +555,11 @@ private String buildPageCountQueryString(StringBuilder sbSelectFrom, boolean ext // The count query does not have any fetch owners Set countNodesToFetch = Collections.emptySet(); // Collect usage of collection join nodes to optimize away the count distinct - Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT), null, true, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch); + Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT), null, true, externalRepresentation, whereClauseConjuncts, null, explicitVersionEntities, countNodesToFetch); // TODO: Maybe we can improve this and treat array access joins like non-collection join nodes boolean hasCollectionJoinUsages = collectionJoinNodes.size() > 0; - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); // Count distinct is obviously unnecessary if we have no collection joins if (!hasCollectionJoinUsages) { @@ -598,8 +598,8 @@ private String appendSimplePageIdQueryString(StringBuilder sbSelectFrom) { List whereClauseConjuncts = new ArrayList<>(); // The id query does not have any fetch owners Set idNodesToFetch = Collections.emptySet(); - joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.SELECT), PAGE_POSITION_ID_QUERY_ALIAS_PREFIX, false, false, whereClauseConjuncts, explicitVersionEntities, idNodesToFetch); - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts); + joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.SELECT), PAGE_POSITION_ID_QUERY_ALIAS_PREFIX, false, false, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); boolean inverseOrder = false; @@ -640,10 +640,10 @@ private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean extern List whereClauseConjuncts = new ArrayList<>(); // The id query does not have any fetch owners Set idNodesToFetch = Collections.emptySet(); - joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.SELECT), null, false, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, idNodesToFetch); + joinManager.buildClause(sbSelectFrom, EnumSet.of(ClauseType.SELECT), null, false, externalRepresentation, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch); if (keysetMode == KeysetMode.NONE) { - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); } else { sbSelectFrom.append(" WHERE "); @@ -656,7 +656,7 @@ private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean extern if (whereManager.hasPredicates() || !whereClauseConjuncts.isEmpty()) { sbSelectFrom.append(" AND "); - whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, null); } } @@ -700,7 +700,7 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external * ORDER_BY clause do not depend on */ List whereClauseConjuncts = new ArrayList<>(); - joinManager.buildClause(sbSelectFrom, EnumSet.complementOf(EnumSet.of(ClauseType.SELECT, ClauseType.ORDER_BY)), null, false, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, nodesToFetch); + joinManager.buildClause(sbSelectFrom, EnumSet.complementOf(EnumSet.of(ClauseType.SELECT, ClauseType.ORDER_BY)), null, false, externalRepresentation, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch); sbSelectFrom.append(" WHERE "); rootNode.appendDeReference(sbSelectFrom, idName); sbSelectFrom.append(" IN :").append(ID_PARAM_NAME).append(""); @@ -763,10 +763,10 @@ private String buildObjectQueryString(StringBuilder sbSelectFrom, boolean extern } List whereClauseConjuncts = new ArrayList<>(); - joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, whereClauseConjuncts, explicitVersionEntities, nodesToFetch); + joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch); if (keysetMode == KeysetMode.NONE) { - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); } else { sbSelectFrom.append(" WHERE "); @@ -779,7 +779,7 @@ private String buildObjectQueryString(StringBuilder sbSelectFrom, boolean extern if (whereManager.hasPredicates() || !whereClauseConjuncts.isEmpty()) { sbSelectFrom.append(" AND "); - whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts); + whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, 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 4ac06ac107..c0ae954b06 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 @@ -92,7 +92,8 @@ void collectParameterListNames(Query q, Set parameterListNames, Set parameter = getParameter(name); + if (parameter != null && parameter.isCollectionValued()) { parameterListNames.add(name); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java index 824c2ee84e..e72ac6b055 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java @@ -211,53 +211,44 @@ boolean hasPredicates() { } void buildClause(StringBuilder sb) { - buildClause(sb, Collections.emptyList()); + buildClause(sb, Collections.emptyList(), null); } - void buildClause(StringBuilder sb, List additionalConjuncts) { + void buildClause(StringBuilder sb, List additionalConjuncts, List endConjuncts) { if (!hasPredicates() && additionalConjuncts.isEmpty()) { return; } - queryGenerator.setClauseType(getClauseType()); - queryGenerator.setQueryBuffer(sb); + int initialLength = sb.length(); sb.append(' ').append(getClauseName()).append(' '); int oldLength = sb.length(); - applyPredicate(queryGenerator); - queryGenerator.setClauseType(null); - int size = additionalConjuncts.size(); - if (sb.length() != oldLength && size > 0) { - sb.append(" AND "); + buildClausePredicate(sb, additionalConjuncts, endConjuncts); + if (sb.length() == oldLength) { + sb.setLength(initialLength); } + } + void buildClausePredicate(StringBuilder sb, List additionalConjuncts, List endConjuncts) { + int size = additionalConjuncts.size(); for (int i = 0; i < size; i++) { - if (i != 0) { - sb.append(" AND "); - } sb.append(additionalConjuncts.get(i)); + sb.append(" AND "); } - } - - void buildClausePredicate(StringBuilder sb) { - buildClausePredicate(sb, Collections.emptyList()); - } - void buildClausePredicate(StringBuilder sb, List additionalConjuncts) { queryGenerator.setClauseType(getClauseType()); queryGenerator.setQueryBuffer(sb); int oldLength = sb.length(); applyPredicate(queryGenerator); queryGenerator.setClauseType(null); - int size = additionalConjuncts.size(); - if (sb.length() != oldLength && size > 0) { - sb.append(" AND "); + if (sb.length() == oldLength && size > 0) { + sb.setLength(sb.length() - " AND ".length()); } - for (int i = 0; i < size; i++) { - if (i != 0) { + if (endConjuncts != null && !endConjuncts.isEmpty()) { + for (String endConjunct : endConjuncts) { sb.append(" AND "); + sb.append(endConjunct); } - sb.append(additionalConjuncts.get(i)); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java index a61dd7a9cf..9faa77fb55 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java @@ -17,7 +17,7 @@ package com.blazebit.persistence.impl; import com.blazebit.persistence.BaseFinalSetOperationBuilder; -import com.blazebit.persistence.parser.EntityMetamodel; +import com.blazebit.persistence.impl.query.EntityFunctionNode; import com.blazebit.persistence.parser.SimpleQueryGenerator; import com.blazebit.persistence.parser.expression.AggregateExpression; import com.blazebit.persistence.parser.expression.ArithmeticExpression; @@ -78,21 +78,20 @@ public class ResolvingQueryGenerator extends SimpleQueryGenerator { protected String aliasPrefix; private boolean resolveSelectAliases = true; + private boolean externalRepresentation; private Set renderedJoinNodes; private ClauseType clauseType; private Map treatedJoinNodesForConstraints; private final AliasManager aliasManager; private final ParameterManager parameterManager; - private final EntityMetamodel metamodel; private final AssociationParameterTransformerFactory parameterTransformerFactory; private final JpaProvider jpaProvider; private final Map registeredFunctions; private final Map registeredFunctionsNames; - public ResolvingQueryGenerator(AliasManager aliasManager, ParameterManager parameterManager, AssociationParameterTransformerFactory parameterTransformerFactory, EntityMetamodel metamodel, JpaProvider jpaProvider, Map registeredFunctions) { + public ResolvingQueryGenerator(AliasManager aliasManager, ParameterManager parameterManager, AssociationParameterTransformerFactory parameterTransformerFactory, JpaProvider jpaProvider, Map registeredFunctions) { this.aliasManager = aliasManager; this.parameterManager = parameterManager; - this.metamodel = metamodel; this.parameterTransformerFactory = parameterTransformerFactory; this.jpaProvider = jpaProvider; this.registeredFunctions = registeredFunctions; @@ -177,28 +176,41 @@ protected void renderCountStar() { @Override public void visit(SubqueryExpression expression) { - sb.append('('); - if (expression.getSubquery() instanceof SubqueryInternalBuilder) { - final SubqueryInternalBuilder subquery = (SubqueryInternalBuilder) expression.getSubquery(); + final AbstractCommonQueryBuilder subquery = (AbstractCommonQueryBuilder) expression.getSubquery(); final boolean hasFirstResult = subquery.getFirstResult() != 0; final boolean hasMaxResults = subquery.getMaxResults() != Integer.MAX_VALUE; final boolean hasLimit = hasFirstResult || hasMaxResults; final boolean hasSetOperations = subquery instanceof BaseFinalSetOperationBuilder; - final boolean isSimple = !hasLimit && !hasSetOperations; + final boolean hasEntityFunctions = subquery.joinManager.hasEntityFunctions(); + final boolean isSimple = !hasLimit && !hasSetOperations && !hasEntityFunctions; if (isSimple) { + sb.append('('); sb.append(subquery.getQueryString()); - } else if (hasSetOperations) { - asExpression((AbstractCommonQueryBuilder) subquery).accept(this); + sb.append(')'); } else { - asExpression((AbstractCommonQueryBuilder) subquery).accept(this); + asExpression(subquery).accept(this); } } else { + sb.append('('); sb.append(expression.getSubquery().getQueryString()); + sb.append(')'); } - - sb.append(')'); + } + + @Override + protected boolean isSimpleSubquery(SubqueryExpression expression) { + if (expression.getSubquery() instanceof SubqueryInternalBuilder) { + final AbstractCommonQueryBuilder subquery = (AbstractCommonQueryBuilder) expression.getSubquery(); + final boolean hasFirstResult = subquery.getFirstResult() != 0; + final boolean hasMaxResults = subquery.getMaxResults() != Integer.MAX_VALUE; + final boolean hasLimit = hasFirstResult || hasMaxResults; + final boolean hasSetOperations = subquery instanceof BaseFinalSetOperationBuilder; + final boolean hasEntityFunctions = subquery.joinManager.hasEntityFunctions(); + return !hasLimit && !hasSetOperations && !hasEntityFunctions; + } + return super.isSimpleSubquery(expression); } protected Expression asExpression(AbstractCommonQueryBuilder queryBuilder) { @@ -211,11 +223,8 @@ protected Expression asExpression(AbstractCommonQueryBuilder quer } List setOperationArgs = new ArrayList(operationManager.getSetOperations().size() + 2); - StringBuilder nameSb = new StringBuilder(); // Use prefix because hibernate uses UNION as keyword - nameSb.append("SET_"); - nameSb.append(operationManager.getOperator().name()); - setOperationArgs.add(new StringLiteral(nameSb.toString())); + setOperationArgs.add(new StringLiteral("SET_" + operationManager.getOperator().name())); setOperationArgs.add(asExpression(operationManager.getStartQueryBuilder())); List> setOperands = operationManager.getSetOperations(); @@ -230,9 +239,7 @@ protected Expression asExpression(AbstractCommonQueryBuilder quer int orderByElementsSize = orderByElements.size(); for (int i = 0; i < orderByElementsSize; i++) { - StringBuilder argSb = new StringBuilder(20); - argSb.append(orderByElements.get(i).toString()); - setOperationArgs.add(new StringLiteral(argSb.toString())); + setOperationArgs.add(new StringLiteral(orderByElements.get(i).toString())); } } @@ -247,20 +254,39 @@ protected Expression asExpression(AbstractCommonQueryBuilder quer } } - Expression functionExpr = new FunctionExpression("FUNCTION", setOperationArgs); - return functionExpr; + return new FunctionExpression("FUNCTION", setOperationArgs); } - String queryString = queryBuilder.getQueryString(); - final StringBuilder subquerySb = new StringBuilder(queryString.length() + 2); - subquerySb.append(queryString); + final String queryString = queryBuilder.buildBaseQueryString(externalRepresentation); Expression expression = new SubqueryExpression(new Subquery() { @Override public String getQueryString() { - return subquerySb.toString(); + return queryString; } }); + if (queryBuilder.joinManager.hasEntityFunctions()) { + for (EntityFunctionNode node : queryBuilder.getEntityFunctionNodes(null)) { + List arguments = new ArrayList(2); + arguments.add(new StringLiteral("ENTITY_FUNCTION")); + arguments.add(expression); + + String valuesClause = node.getValuesClause(); + String valuesAliases = node.getValuesAliases(); + String syntheticPredicate = node.getSyntheticPredicate(); + + // TODO: this is a hibernate specific integration detail + // Replace the subview subselect that is generated for this subselect + String entityName = node.getEntityClass().getSimpleName(); + arguments.add(new StringLiteral(entityName)); + arguments.add(new StringLiteral(valuesClause)); + arguments.add(new StringLiteral(valuesAliases == null ? "" : valuesAliases)); + arguments.add(new StringLiteral(syntheticPredicate)); + + expression = new FunctionExpression("FUNCTION", arguments); + } + } + if (queryBuilder.hasLimit()) { final boolean hasFirstResult = queryBuilder.getFirstResult() != 0; final boolean hasMaxResults = queryBuilder.getMaxResults() != Integer.MAX_VALUE; @@ -528,6 +554,14 @@ public void setClauseType(ClauseType clauseType) { this.clauseType = clauseType; } + public boolean isExternalRepresentation() { + return externalRepresentation; + } + + public void setExternalRepresentation(boolean externalRepresentation) { + this.externalRepresentation = externalRepresentation; + } + @Override public void visit(ArrayExpression expression) { } 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 new file mode 100644 index 0000000000..7d983fe6aa --- /dev/null +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/function/entity/EntityFunction.java @@ -0,0 +1,125 @@ +/* + * 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.impl.function.entity; + +import com.blazebit.persistence.impl.util.SqlUtils; +import com.blazebit.persistence.spi.FunctionRenderContext; +import com.blazebit.persistence.spi.JpqlFunction; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +public class EntityFunction implements JpqlFunction { + + public static final String FUNCTION_NAME = "entity_function"; + + @Override + public boolean hasArguments() { + return true; + } + + @Override + public boolean hasParenthesesIfNoArguments() { + return true; + } + + @Override + public Class getReturnType(Class firstArgumentType) { + return firstArgumentType; + } + + @Override + public void render(FunctionRenderContext functionRenderContext) { + if (functionRenderContext.getArgumentsSize() == 0) { + throw new RuntimeException("The ENTITY_FUNCTION function needs at least one argument ! args=" + functionRenderContext); + } + + // com.blazebit.persistence.impl.JoinManager.buildClause adds synthetic where clause conjuncts and starts with 1=1 so we can determine it properly + // We remove the synthetic predicate here and later extract the values clause alias of it so we can insert the proper SQL values clause at the right place + String subquery = functionRenderContext.getArgument(0); + StringBuilder sb = new StringBuilder(); + int subqueryEndIndex = subquery.lastIndexOf(" and 1=1"); + int aliasEndIndex = subquery.indexOf('.', subqueryEndIndex) ; + int aliasStartIndex = aliasEndIndex - 1; + while (aliasStartIndex > subqueryEndIndex) { + if (!SqlUtils.isIdentifier(subquery.charAt(aliasStartIndex))) { + aliasStartIndex++; + break; + } + aliasStartIndex--; + } + + sb.append(subquery, 1, subqueryEndIndex); + String entityName = unquote(functionRenderContext.getArgument(1)); + String valuesClause = unquote(functionRenderContext.getArgument(2)); + String valuesAliases = unquote(functionRenderContext.getArgument(3)); + String syntheticPredicate = unquote(functionRenderContext.getArgument(4)); + String exampleQuerySqlAlias = syntheticPredicate.substring(0, syntheticPredicate.indexOf('.')); + String valuesTableSqlAlias = subquery.substring(aliasStartIndex, aliasEndIndex); + + syntheticPredicate = syntheticPredicate.replace(exampleQuerySqlAlias, valuesTableSqlAlias); + + // TODO: this is a hibernate specific integration detail + // Replace the subview subselect that is generated for this subselect + final String subselect = "( select * from " + entityName + " )"; + int subselectIndex = sb.indexOf(subselect, 0); + if (subselectIndex == -1) { + // this is probably a VALUES clause for an entity type + int syntheticPredicateStart = sb.indexOf(syntheticPredicate, SqlUtils.indexOfWhere(sb)); + sb.replace(syntheticPredicateStart, syntheticPredicateStart + syntheticPredicate.length(), "1=1"); + } else { + while ((subselectIndex = sb.indexOf(subselect, subselectIndex)) > -1) { + int endIndex = subselectIndex + subselect.length(); + int syntheticPredicateStart = sb.indexOf(syntheticPredicate, endIndex); + sb.replace(syntheticPredicateStart, syntheticPredicateStart + syntheticPredicate.length(), "1=1"); + sb.replace(subselectIndex, endIndex, entityName); + } + } + SqlUtils.applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases); + functionRenderContext.addChunk("("); + functionRenderContext.addChunk(sb.toString()); + functionRenderContext.addChunk(")"); + } + + private static String unquote(String s) { + StringBuilder sb = new StringBuilder(s.length()); + boolean quote = false; + for (int i = 1; i < s.length() - 1; i++) { + final char c = s.charAt(i); + if (quote) { + quote = false; + if (c != '\'') { + sb.append('\''); + } + sb.append(c); + } else { + if (c == '\'') { + quote = true; + } else { + sb.append(c); + } + } + } + if (quote) { + sb.append('\''); + } + return sb.toString(); + } + +} diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomModificationQueryPlan.java b/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomModificationQueryPlan.java index 858381d216..30b489ba0e 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomModificationQueryPlan.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomModificationQueryPlan.java @@ -31,13 +31,15 @@ public class CustomModificationQueryPlan implements ModificationQueryPlan { private final ExtendedQuerySupport extendedQuerySupport; private final ServiceProvider serviceProvider; + private final Query baseQuery; private final Query delegate; private final List participatingQueries; private final String sql; - public CustomModificationQueryPlan(ExtendedQuerySupport extendedQuerySupport, ServiceProvider serviceProvider, Query delegate, List participatingQueries, String sql) { + public CustomModificationQueryPlan(ExtendedQuerySupport extendedQuerySupport, ServiceProvider serviceProvider, Query baseQuery, Query delegate, List participatingQueries, String sql) { this.extendedQuerySupport = extendedQuerySupport; this.serviceProvider = serviceProvider; + this.baseQuery = baseQuery; this.delegate = delegate; this.participatingQueries = participatingQueries; this.sql = sql; @@ -45,7 +47,7 @@ public CustomModificationQueryPlan(ExtendedQuerySupport extendedQuerySupport, Se @Override public int executeUpdate() { - return extendedQuerySupport.executeUpdate(serviceProvider, participatingQueries, delegate, sql); + return extendedQuerySupport.executeUpdate(serviceProvider, participatingQueries, baseQuery, delegate, sql); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomReturningModificationQueryPlan.java b/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomReturningModificationQueryPlan.java index 34a4839342..d6d2e8521e 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomReturningModificationQueryPlan.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/plan/CustomReturningModificationQueryPlan.java @@ -38,6 +38,7 @@ public class CustomReturningModificationQueryPlan implements ModificationQuer private final ExtendedQuerySupport extendedQuerySupport; private final ServiceProvider serviceProvider; private final DbmsDialect dbmsDialect; + private final Query modificationBaseQuery; private final Query delegate; private final ReturningObjectBuilder objectBuilder; private final List participatingQueries; @@ -46,10 +47,11 @@ public class CustomReturningModificationQueryPlan implements ModificationQuer private final int maxResults; private final boolean requiresWrapping; - public CustomReturningModificationQueryPlan(ExtendedQuerySupport extendedQuerySupport, ServiceProvider serviceProvider, Query delegate, ReturningObjectBuilder objectBuilder, List participatingQueries, String sql, int firstResult, int maxResults, boolean requiresWrapping) { + public CustomReturningModificationQueryPlan(ExtendedQuerySupport extendedQuerySupport, ServiceProvider serviceProvider, Query modificationBaseQuery, Query delegate, ReturningObjectBuilder objectBuilder, List participatingQueries, String sql, int firstResult, int maxResults, boolean requiresWrapping) { this.extendedQuerySupport = extendedQuerySupport; this.serviceProvider = serviceProvider; this.dbmsDialect = serviceProvider.getService(DbmsDialect.class); + this.modificationBaseQuery = modificationBaseQuery; this.delegate = delegate; this.objectBuilder = objectBuilder; this.participatingQueries = participatingQueries; @@ -64,7 +66,7 @@ public int executeUpdate() { Query baseQuery = participatingQueries.get(0); baseQuery.setFirstResult(firstResult); baseQuery.setMaxResults(maxResults); - ReturningResult result = extendedQuerySupport.executeReturning(serviceProvider, participatingQueries, delegate, sql); + ReturningResult result = extendedQuerySupport.executeReturning(serviceProvider, participatingQueries, modificationBaseQuery, delegate, sql); return result.getUpdateCount(); } @@ -79,7 +81,7 @@ public ReturningResult getSingleResult() { baseQuery.setFirstResult(firstResult); baseQuery.setMaxResults(maxResults); - ReturningResult result = extendedQuerySupport.executeReturning(serviceProvider, participatingQueries, delegate, sql); + ReturningResult result = extendedQuerySupport.executeReturning(serviceProvider, participatingQueries, modificationBaseQuery, delegate, sql); List resultList = result.getResultList(); final int updateCount = result.getUpdateCount(); if (requiresWrapping) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CTEQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CTEQuerySpecification.java index 317d150d5f..c36a461688 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CTEQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CTEQuerySpecification.java @@ -53,7 +53,7 @@ protected void initialize() { } String sqlQuery = extendedQuerySupport.getSql(em, baseQuery); - StringBuilder sqlSb = applySqlTransformations(baseQuery, sqlQuery, participatingQueries); + StringBuilder sqlSb = applySqlTransformations(sqlQuery); // Need to inline LIMIT and OFFSET dbmsDialect.appendExtendedSql(sqlSb, statementType, false, true, null, limit, offset, null, null); participatingQueries.add(baseQuery); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CollectionInsertModificationQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CollectionInsertModificationQuerySpecification.java index 2b81aec4fc..59e10893f9 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CollectionInsertModificationQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CollectionInsertModificationQuerySpecification.java @@ -54,7 +54,7 @@ protected void initialize() { } String sql = extendedQuerySupport.getSql(em, baseQuery); - StringBuilder sqlSb = applySqlTransformations(baseQuery, sql, participatingQueries); + StringBuilder sqlSb = applySqlTransformations(sql); sqlSb.insert(0, insertSql); sqlSb.insert(insertSql.length(), ' '); 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 9445e61b57..cb11c79c9d 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 @@ -157,7 +157,7 @@ protected void initialize() { } String sqlQuery = extendedQuerySupport.getSql(em, baseQuery); - StringBuilder sqlSb = applySqlTransformations(baseQuery, sqlQuery, participatingQueries); + StringBuilder sqlSb = applySqlTransformations(sqlQuery); StringBuilder withClause = applyCtes(sqlSb, baseQuery, participatingQueries); Map addedCtes = applyExtendedSql(sqlSb, false, false, withClause, null, null); participatingQueries.add(baseQuery); @@ -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(); - applyTableNameRemapping(sqlSb, sqlAlias, newCteName, null); + SqlUtils.applyTableNameRemapping(sqlSb, sqlAlias, newCteName, null); } return sb; @@ -365,7 +365,7 @@ private boolean applyCascadingDelete(Query baseQuery, List participatingQ return firstCte; } - protected StringBuilder applySqlTransformations(Query baseQuery, String sqlQuery, List participatingQueries) { + protected StringBuilder applySqlTransformations(String sqlQuery) { if (entityFunctionNodes.isEmpty() && keyRestrictedLeftJoinAliases.isEmpty()) { return new StringBuilder(sqlQuery); } @@ -386,18 +386,27 @@ protected StringBuilder applySqlTransformations(Query baseQuery, String sqlQuery String valuesTableSqlAlias = node.getTableAlias(); String valuesClause = node.getValuesClause(); String valuesAliases = node.getValuesAliases(); + String syntheticPredicate = node.getSyntheticPredicate(); // TODO: this is a hibernate specific integration detail // Replace the subview subselect that is generated for this subselect String entityName = node.getEntityClass().getSimpleName(); final String subselect = "( select * from " + entityName + " )"; - int subselectIndex = 0; - while ((subselectIndex = sb.indexOf(subselect, subselectIndex)) > -1) { - sb.replace(subselectIndex, subselectIndex + subselect.length(), entityName); + int subselectIndex = sb.indexOf(subselect, 0); + if (subselectIndex == -1) { + // this is probably a VALUES clause for an entity type + int syntheticPredicateStart = sb.indexOf(syntheticPredicate, SqlUtils.indexOfWhere(sb)); + sb.replace(syntheticPredicateStart, syntheticPredicateStart + syntheticPredicate.length(), "1=1"); + } else { + while ((subselectIndex = sb.indexOf(subselect, subselectIndex)) > -1) { + int endIndex = subselectIndex + subselect.length(); + int syntheticPredicateStart = sb.indexOf(syntheticPredicate, endIndex); + sb.replace(syntheticPredicateStart, syntheticPredicateStart + syntheticPredicate.length(), "1=1"); + sb.replace(subselectIndex, endIndex, entityName); + } } - applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases); - participatingQueries.add(node.getValueQuery()); + SqlUtils.applyTableNameRemapping(sb, valuesTableSqlAlias, valuesClause, valuesAliases); } return sb; @@ -415,10 +424,10 @@ private void applyLeftJoinSubqueryRewrite(StringBuilder sb, String sqlAlias) { int[] indexRange; if (searchAs.equalsIgnoreCase(sb.substring(searchIndex - searchAs.length(), searchIndex))) { // Uses aliasing with the AS keyword - indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length()); + indexRange = SqlUtils.rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length()); } else { // Uses aliasing without the AS keyword - indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex); + indexRange = SqlUtils.rtrimBackwardsToFirstWhitespace(sb, searchIndex); } // Jump back two left joins to further inspect the join table @@ -537,7 +546,7 @@ private List getColumnExpressions(StringBuilder sb, String tableAlias, i columnExpressionSb.append(columnExpressionStart); expressionIndex += columnExpressionStart.length(); char keyChar; - while (isIdentifier(keyChar = sb.charAt(expressionIndex))) { + while (SqlUtils.isIdentifier(keyChar = sb.charAt(expressionIndex))) { columnExpressionSb.append(keyChar); expressionIndex++; } @@ -551,7 +560,7 @@ private List getColumnExpressions(StringBuilder sb, String tableAlias, i private int replaceExpressionUntil(int searchIndex, int endIndex, int lengthDifference, StringBuilder sb, String oldExpression, String newExpression) { int diff = 0; while ((searchIndex = sb.indexOf(oldExpression, searchIndex + 1)) > 0 && searchIndex < endIndex) { - if (isIdentifierStart(sb.charAt(searchIndex - 1)) || isIdentifier(sb.charAt(searchIndex + oldExpression.length()))) { + if (SqlUtils.isIdentifierStart(sb.charAt(searchIndex - 1)) || SqlUtils.isIdentifier(sb.charAt(searchIndex + oldExpression.length()))) { continue; } sb.replace(searchIndex, searchIndex + oldExpression.length(), newExpression); @@ -562,73 +571,6 @@ private int replaceExpressionUntil(int searchIndex, int endIndex, int lengthDiff return diff; } - private boolean isIdentifierStart(char c) { - return Character.isLetter(c) || c == '_'; - } - - private boolean isIdentifier(char c) { - return Character.isLetterOrDigit(c) || c == '_'; - } - - private void applyTableNameRemapping(StringBuilder sb, String sqlAlias, String newCteName, String aliasExtension) { - final String searchAs = " as"; - final String searchAlias = " " + sqlAlias; - int searchIndex = 0; - while ((searchIndex = sb.indexOf(searchAlias, searchIndex)) > -1) { - int idx = searchIndex + searchAlias.length(); - if (idx < sb.length() && sb.charAt(idx) == '.') { - // This is a dereference of the alias, skip this - } else { - int[] indexRange; - if (searchAs.equalsIgnoreCase(sb.substring(searchIndex - searchAs.length(), searchIndex))) { - // Uses aliasing with the AS keyword - indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length()); - } else { - // Uses aliasing without the AS keyword - indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex); - } - - int oldLength = indexRange[1] - indexRange[0]; - // Replace table name with cte name - sb.replace(indexRange[0], indexRange[1], newCteName); - - if (aliasExtension != null) { - sb.insert(searchIndex + searchAlias.length() + (newCteName.length() - oldLength), aliasExtension); - searchIndex += aliasExtension.length(); - } - - // Adjust index after replacing - searchIndex += newCteName.length() - oldLength; - } - - searchIndex = searchIndex + 1; - } - } - - private int[] rtrimBackwardsToFirstWhitespace(StringBuilder sb, int startIndex) { - int tableNameStartIndex; - int tableNameEndIndex = startIndex; - boolean text = false; - for (tableNameStartIndex = tableNameEndIndex; tableNameStartIndex >= 0; tableNameStartIndex--) { - if (text) { - final char c = sb.charAt(tableNameStartIndex); - if (Character.isWhitespace(c) || c == ',') { - tableNameStartIndex++; - break; - } - } else { - if (Character.isWhitespace(sb.charAt(tableNameStartIndex))) { - tableNameEndIndex--; - } else { - text = true; - tableNameEndIndex++; - } - } - } - - return new int[]{ tableNameStartIndex, tableNameEndIndex }; - } - private String getSql(Query query) { if (query instanceof CustomSQLQuery) { return ((CustomSQLQuery) query).getSql(); 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 3fee5dd03c..2ef582d19d 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 @@ -16,8 +16,6 @@ package com.blazebit.persistence.impl.query; -import javax.persistence.Query; - /** * * @author Christian Beikov @@ -29,14 +27,14 @@ public class EntityFunctionNode { private final String valuesAliases; private final Class entityClass; private final String tableAlias; - private final Query valueQuery; + private final String syntheticPredicate; - public EntityFunctionNode(String valuesClause, String valuesAliases, Class entityClass, String tableAlias, Query valueQuery) { + public EntityFunctionNode(String valuesClause, String valuesAliases, Class entityClass, String tableAlias, String syntheticPredicate) { this.valuesClause = valuesClause; this.valuesAliases = valuesAliases; this.entityClass = entityClass; this.tableAlias = tableAlias; - this.valueQuery = valueQuery; + this.syntheticPredicate = syntheticPredicate; } public String getValuesClause() { @@ -55,7 +53,7 @@ public String getTableAlias() { return tableAlias; } - public Query getValueQuery() { - return valueQuery; + public String getSyntheticPredicate() { + return syntheticPredicate; } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ModificationQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ModificationQuerySpecification.java index 0dfd6cfaef..1781db2dc4 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ModificationQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ModificationQuerySpecification.java @@ -74,7 +74,7 @@ public ModificationQueryPlan createModificationPlan(int firstResult, int maxResu } else { finalSql = sql; } - return new CustomModificationQueryPlan(extendedQuerySupport, serviceProvider, query, participatingQueries, finalSql); + return new CustomModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, query, participatingQueries, finalSql); } @Override @@ -96,7 +96,7 @@ protected void initialize() { } String sqlQuery = extendedQuerySupport.getSql(em, baseQuery); - StringBuilder sqlSb = applySqlTransformations(baseQuery, sqlQuery, participatingQueries); + StringBuilder sqlSb = applySqlTransformations(sqlQuery); StringBuilder withClause = applyCtes(sqlSb, baseQuery, participatingQueries); // NOTE: CTEs will only be added, if this is a subquery Map addedCtes = applyExtendedSql(sqlSb, false, isEmbedded, withClause, returningColumns, includedModificationStates); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionDeleteModificationQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionDeleteModificationQuerySpecification.java index fe2397fc5d..9316831e39 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionDeleteModificationQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionDeleteModificationQuerySpecification.java @@ -49,13 +49,13 @@ public ReturningCollectionDeleteModificationQuerySpecification(AbstractCommonQue @Override public ModificationQueryPlan createModificationPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } @Override public SelectQueryPlan> createSelectPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionInsertModificationQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionInsertModificationQuerySpecification.java index 30347e2c41..072f230861 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionInsertModificationQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionInsertModificationQuerySpecification.java @@ -48,13 +48,13 @@ public ReturningCollectionInsertModificationQuerySpecification(AbstractCommonQue @Override public ModificationQueryPlan createModificationPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } @Override public SelectQueryPlan> createSelectPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionUpdateModificationQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionUpdateModificationQuerySpecification.java index 3f74deadbc..cac320e5e1 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionUpdateModificationQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningCollectionUpdateModificationQuerySpecification.java @@ -49,13 +49,13 @@ public ReturningCollectionUpdateModificationQuerySpecification(AbstractCommonQue @Override public ModificationQueryPlan createModificationPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } @Override public SelectQueryPlan> createSelectPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningModificationQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningModificationQuerySpecification.java index fbccfcd65e..d4bcc09ade 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningModificationQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/ReturningModificationQuerySpecification.java @@ -53,13 +53,13 @@ public ReturningModificationQuerySpecification(AbstractCommonQueryBuilder(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } @Override public SelectQueryPlan createSelectPlan(int firstResult, int maxResults) { final String sql = getSql(); - return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); + return new CustomReturningModificationQueryPlan(extendedQuerySupport, serviceProvider, baseQuery, exampleQuery, objectBuilder, participatingQueries, sql, firstResult, maxResults, returningColumns.length == 1 && objectBuilder != null); } @Override 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 e6dfc8f589..1461981739 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 @@ -78,6 +78,73 @@ public String extract(StringBuilder sb, int index, int currentPosition) { private SqlUtils() { } + public static void applyTableNameRemapping(StringBuilder sb, String sqlAlias, String newCteName, String aliasExtension) { + final String searchAs = " as"; + final String searchAlias = " " + sqlAlias; + int searchIndex = 0; + while ((searchIndex = sb.indexOf(searchAlias, searchIndex)) > -1) { + int idx = searchIndex + searchAlias.length(); + if (idx < sb.length() && sb.charAt(idx) == '.') { + // This is a dereference of the alias, skip this + } else { + int[] indexRange; + if (searchAs.equalsIgnoreCase(sb.substring(searchIndex - searchAs.length(), searchIndex))) { + // Uses aliasing with the AS keyword + indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex - searchAs.length()); + } else { + // Uses aliasing without the AS keyword + indexRange = rtrimBackwardsToFirstWhitespace(sb, searchIndex); + } + + int oldLength = indexRange[1] - indexRange[0]; + // Replace table name with cte name + sb.replace(indexRange[0], indexRange[1], newCteName); + + if (aliasExtension != null) { + sb.insert(searchIndex + searchAlias.length() + (newCteName.length() - oldLength), aliasExtension); + searchIndex += aliasExtension.length(); + } + + // Adjust index after replacing + searchIndex += newCteName.length() - oldLength; + } + + searchIndex = searchIndex + 1; + } + } + + public static int[] rtrimBackwardsToFirstWhitespace(StringBuilder sb, int startIndex) { + int tableNameStartIndex; + int tableNameEndIndex = startIndex; + boolean text = false; + for (tableNameStartIndex = tableNameEndIndex; tableNameStartIndex >= 0; tableNameStartIndex--) { + if (text) { + final char c = sb.charAt(tableNameStartIndex); + if (Character.isWhitespace(c) || c == ',') { + tableNameStartIndex++; + break; + } + } else { + if (Character.isWhitespace(sb.charAt(tableNameStartIndex))) { + tableNameEndIndex--; + } else { + text = true; + tableNameEndIndex++; + } + } + } + + return new int[]{ tableNameStartIndex, tableNameEndIndex }; + } + + public static boolean isIdentifierStart(char c) { + return Character.isLetter(c) || c == '_'; + } + + public static boolean isIdentifier(char c) { + return Character.isLetterOrDigit(c) || c == '_'; + } + /** * Extracts the select item aliases of an arbitrary SELECT query. * diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/SimpleQueryGenerator.java b/core/parser/src/main/java/com/blazebit/persistence/parser/SimpleQueryGenerator.java index ff1933bfd7..12d3c57756 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/SimpleQueryGenerator.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/SimpleQueryGenerator.java @@ -357,10 +357,10 @@ public void visit(final InPredicate predicate) { if (predicate.getRight().size() == 1) { // NOTE: other cases are handled by ResolvingQueryGenerator Expression singleRightExpression = predicate.getRight().get(0); - if (singleRightExpression instanceof ParameterExpression && ((ParameterExpression) singleRightExpression).isCollectionValued() || singleRightExpression instanceof SubqueryExpression) { + if (singleRightExpression instanceof ParameterExpression && ((ParameterExpression) singleRightExpression).isCollectionValued()) { paranthesisRequired = false; } else { - paranthesisRequired = true; + paranthesisRequired = !(singleRightExpression instanceof SubqueryExpression) || !isSimpleSubquery((SubqueryExpression) singleRightExpression); } } else { paranthesisRequired = true; @@ -382,6 +382,10 @@ public void visit(final InPredicate predicate) { setParameterRenderingMode(oldParameterRenderingMode); } + protected boolean isSimpleSubquery(SubqueryExpression expression) { + return true; + } + @Override public void visit(final ExistsPredicate predicate) { if (predicate.isNegated()) { 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 4d96056cee..b20a5a7c8d 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,7 +131,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodes(INDEX(_collection), _collection.id, root.id)\n" + "SELECT 1, 4, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -163,7 +164,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodes(INDEX(_collection), _collection.id, root.id)\n" + "SELECT 1, 4, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); ReturningResult returningResult = criteria.executeWithReturning("indexedNodes.id"); Root r = getRoot(em); @@ -192,7 +194,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodesMany(INDEX(_collection), _collection.id, root.id)\n" + "SELECT 1, 4, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -220,7 +223,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodesManyDuplicate(INDEX(_collection), _collection.id, root.id)\n" + "SELECT 1, 4, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -249,7 +253,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.indexedNodesElementCollection(INDEX(_collection), _collection.value, _collection.value2, root.id)\n" + "SELECT 1, 'B', 'P', 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -278,7 +283,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodes(KEY(_collection), _collection.id, root.id)\n" + "SELECT 'b', 5, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -306,7 +312,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodesMany(KEY(_collection), _collection.id, root.id)\n" + "SELECT 'b', 5, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -334,7 +341,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodesManyDuplicate(KEY(_collection), _collection.id, root.id)\n" + "SELECT 'b', 5, 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -363,7 +371,8 @@ public void work(EntityManager em) { assertEquals("INSERT INTO Root.keyedNodesElementCollection(KEY(_collection), _collection.value, _collection.value2, root.id)\n" + "SELECT 'b', 'B', 'P', 1" - + " FROM Integer(1 VALUES) valuesAlias", criteria.getQueryString()); + + " FROM Integer(1 VALUES) valuesAlias" + + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); 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 be9b8cea55..cc5c056f45 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 @@ -18,6 +18,7 @@ import com.blazebit.persistence.CriteriaBuilder; import com.blazebit.persistence.impl.ConfigurationProperties; +import com.blazebit.persistence.spi.ValuesStrategy; import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus; import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink; import com.blazebit.persistence.testsuite.base.jpa.category.NoH2; @@ -99,7 +100,7 @@ public void testValuesEntityFunction() { cb.select("allowedAge.value"); String expected = "" - + "SELECT doc.name, TREAT_LONG(allowedAge.value) FROM Long(1 VALUES) allowedAge, Document doc WHERE doc.age = TREAT_LONG(allowedAge.value)"; + + "SELECT doc.name, TREAT_LONG(allowedAge.value) FROM Document doc, Long(1 VALUES) allowedAge WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 AND doc.age = TREAT_LONG(allowedAge.value)"; assertEquals(expected, cb.getQueryString()); List resultList = cb.getResultList(); @@ -108,6 +109,50 @@ public void testValuesEntityFunction() { assertEquals(1L, resultList.get(0).get(1)); } + // Test for #305 + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionWithParameterInSelect() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(Long.class, "allowedAge", Arrays.asList(1L, 2L)); + cb.from(Document.class, "doc"); + cb.where("doc.age").eqExpression("allowedAge.value"); + cb.select("CASE WHEN doc.name = :param THEN doc.name ELSE '' END"); + cb.select("allowedAge.value"); + + String expected = "" + + "SELECT CASE WHEN doc.name = :param THEN doc.name ELSE '' END, TREAT_LONG(allowedAge.value) 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 = TREAT_LONG(allowedAge.value)"; + + assertEquals(expected, cb.getQueryString()); + cb.setParameter("param", "doc1"); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("doc1", resultList.get(0).get(0)); + assertEquals(1L, resultList.get(0).get(1)); + } + + // Test for #305 + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionWithParameterInSelectSubquery() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.from(Document.class, "doc"); + cb.select("CASE WHEN doc.name = :param THEN doc.name ELSE '' END"); + cb.selectSubquery() + .fromValues(Long.class, "allowedAge", Arrays.asList(1L, 2L)) + .select("CASE WHEN doc.name = :param THEN allowedAge.value ELSE 2L END") + .where("doc.age").eqExpression("allowedAge.value") + .end(); + + // We can't check the JPQL here because it contains SQL as literal text :| + cb.setParameter("param", "doc1"); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("doc1", resultList.get(0).get(0)); + assertEquals(1L, resultList.get(0).get(1)); + } + @Test @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) public void testValuesEntityFunctionParameters() { @@ -148,7 +193,7 @@ public void testValuesEntityFunctionLeftJoin() { String expected = "" + "SELECT TREAT_LONG(allowedAge.value), doc.name FROM Long(3 VALUES) allowedAge LEFT JOIN Document doc" + - onClause("doc.age = TREAT_LONG(allowedAge.value)") + + 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 = TREAT_LONG(allowedAge.value)") + " ORDER BY " + renderNullPrecedence("TREAT_LONG(allowedAge.value)", "ASC", "LAST"); assertEquals(expected, cb.getQueryString()); @@ -183,7 +228,7 @@ public void testValuesEntityFunctionWithEntity() { String expected = "" + "SELECT intEntity.name, doc.name FROM IntIdEntity(2 VALUES) intEntity LEFT JOIN Document doc" + - onClause("doc.name = intEntity.name") + + 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") + " ORDER BY " + renderNullPrecedence("intEntity.name", "ASC", "LAST"); assertEquals(expected, cb.getQueryString()); @@ -212,7 +257,7 @@ public void testValuesEntityFunctionParameter() { String expected = "" + "SELECT intEntity.name, doc.name FROM IntIdEntity(1 VALUES) intEntity LEFT JOIN Document doc" + - onClause("doc.name = intEntity.name") + + onClause("intEntity.id = :intEntity_id_0 OR intEntity.name = :intEntity_name_0 OR intEntity.value = :intEntity_value_0 AND doc.name = intEntity.name") + " ORDER BY " + renderNullPrecedence("intEntity.name", "ASC", "LAST"); assertEquals(expected, cb.getQueryString()); diff --git a/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc b/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc index d40f81d000..c2da4e0f73 100644 --- a/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc +++ b/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc @@ -438,8 +438,6 @@ CriteriaBuilder cb = cbf.create(em, String.class) .setParameter("myValue", valueCollection); ---- -WARNING: Currently it is not possible to use the `VALUES` when using parameters in the `SELECT` clause due to https://github.com/Blazebit/blaze-persistence/issues/305[#305] - NOTE: For some cases it might be better to make use of <> instead of a `VALUES` ==== Basic values diff --git a/documentation/src/main/asciidoc/core/manual/en_US/18_jpql_functions.adoc b/documentation/src/main/asciidoc/core/manual/en_US/18_jpql_functions.adoc index 52a143f2ae..3e4dceb2da 100644 --- a/documentation/src/main/asciidoc/core/manual/en_US/18_jpql_functions.adoc +++ b/documentation/src/main/asciidoc/core/manual/en_US/18_jpql_functions.adoc @@ -339,6 +339,14 @@ Simply renders the subquery argument. WARNING: This is an internal function that is used to bypass the Hibernate parser for rendering subqueries as aggregate function arguments. +==== ENTITY_FUNCTION function + +Syntax: `FUNCTION ( 'ENTITY_FUNCTION', subquery, entityName, valuesClause, valuesAliases, syntheticPredicate)` + +Rewrites the passed in query by replacing placeholder SQL parts with the proper SQL. + +WARNING: This is an internal function that is used to implement entity functions like the `VALUES` clause for subqueries. It is not intended for direct use and might change without notice. + === Custom JPQL functions Apart from providing many useful functions out of the box, {projectname} also allows to implement custom JPQL functions that can be called just like any other non-standard function, diff --git a/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51ExtendedQuerySupport.java b/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51ExtendedQuerySupport.java index 9eb0db9567..049f48a71a 100644 --- a/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51ExtendedQuerySupport.java +++ b/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51ExtendedQuerySupport.java @@ -94,13 +94,13 @@ public Object getSingleResult(com.blazebit.persistence.spi.ServiceProvider servi } @Override - public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride) { + public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query baseQuery, Query query, String sqlOverride) { applySql(query, sqlOverride); return query.executeUpdate(); } @Override - public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query exampleQuery, String sqlOverride) { + public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query modificationBaseQuery, Query exampleQuery, String sqlOverride) { // TODO: implement throw new UnsupportedOperationException("Not yet implemeneted!"); } diff --git a/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusExtendedQuerySupport.java b/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusExtendedQuerySupport.java index 9bbdbe32c7..c563a44a64 100644 --- a/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusExtendedQuerySupport.java +++ b/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusExtendedQuerySupport.java @@ -94,13 +94,13 @@ public Object getSingleResult(com.blazebit.persistence.spi.ServiceProvider servi } @Override - public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String sqlOverride) { + public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query baseQuery, Query query, String sqlOverride) { applySql(query, sqlOverride); return query.executeUpdate(); } @Override - public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query exampleQuery, String sqlOverride) { + public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query modificationBaseQuery, Query exampleQuery, String sqlOverride) { // TODO: implement throw new UnsupportedOperationException("Not yet implemeneted!"); } diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateExtendedQuerySupport.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateExtendedQuerySupport.java index 2010e4bf6e..6c2dfca91f 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateExtendedQuerySupport.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateExtendedQuerySupport.java @@ -362,7 +362,7 @@ private List list(com.blazebit.persistence.spi.ServiceProvider serviceProvider, HQLQueryPlan queryPlan = queryPlanEntry.getValue(); if (!queryPlanEntry.isFromCache()) { - prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, participatingQueries.get(participatingQueries.size() - 1), false, serviceProvider.getService(DbmsDialect.class)); + prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, null, false, serviceProvider.getService(DbmsDialect.class)); queryPlan = putQueryPlanIfAbsent(sfi, cacheKey, queryPlan); } @@ -370,7 +370,7 @@ private List list(com.blazebit.persistence.spi.ServiceProvider serviceProvider, } @Override - public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query query, String finalSql) { + public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query baseQuery, Query query, String finalSql) { DbmsDialect dbmsDialect = serviceProvider.getService(DbmsDialect.class); EntityManager em = serviceProvider.getService(EntityManager.class); SessionImplementor session = em.unwrap(SessionImplementor.class); @@ -400,7 +400,7 @@ public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider servicePro HQLQueryPlan queryPlan = queryPlanEntry.getValue(); if (!queryPlanEntry.isFromCache()) { - prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, participatingQueries.get(participatingQueries.size() - 1), true, dbmsDialect); + prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, baseQuery, true, dbmsDialect); queryPlan = putQueryPlanIfAbsent(sfi, cacheKey, queryPlan); } @@ -438,7 +438,7 @@ public int executeUpdate(com.blazebit.persistence.spi.ServiceProvider servicePro @Override @SuppressWarnings("unchecked") - public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query exampleQuery, String sqlOverride) { + public ReturningResult executeReturning(com.blazebit.persistence.spi.ServiceProvider serviceProvider, List participatingQueries, Query modificationBaseQuery, Query exampleQuery, String sqlOverride) { DbmsDialect dbmsDialect = serviceProvider.getService(DbmsDialect.class); EntityManager em = serviceProvider.getService(EntityManager.class); SessionImplementor session = em.unwrap(SessionImplementor.class); @@ -470,7 +470,7 @@ public ReturningResult executeReturning(com.blazebit.persistence.spi.S try { HibernateReturningResult returningResult = new HibernateReturningResult(); if (!queryPlanEntry.isFromCache()) { - prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, participatingQueries.get(participatingQueries.size() - 1), true, dbmsDialect); + prepareQueryPlan(queryPlan, queryParametersEntry.specifications, finalSql, session, modificationBaseQuery, true, dbmsDialect); queryPlan = putQueryPlanIfAbsent(sfi, cacheKey, queryPlan); } @@ -481,7 +481,7 @@ public ReturningResult executeReturning(com.blazebit.persistence.spi.S QueryTranslator queryTranslator = queryPlan.getTranslators()[0]; // If the DBMS doesn't support inclusion of cascading deletes in a with clause, we have to execute them manually - StatementExecutor executor = getExecutor(queryTranslator, session, participatingQueries.get(participatingQueries.size() - 1)); + StatementExecutor executor = getExecutor(queryTranslator, session, modificationBaseQuery); List originalDeletes = Collections.emptyList(); if (executor != null && executor instanceof DeleteExecutor) { @@ -799,7 +799,7 @@ private StatementExecutor getStatementExecutor(QueryTranslator queryTranslator) return getField(queryTranslator, "statementExecutor"); } - private void prepareQueryPlan(HQLQueryPlan queryPlan, List queryParameterSpecifications, String finalSql, SessionImplementor session, Query lastQuery, boolean isModification, DbmsDialect dbmsDialect) { + private void prepareQueryPlan(HQLQueryPlan queryPlan, List queryParameterSpecifications, String finalSql, SessionImplementor session, Query modificationBaseQuery, boolean isModification, DbmsDialect dbmsDialect) { try { if (queryPlan.getTranslators().length > 1) { throw new IllegalArgumentException("No support for multiple translators yet!"); @@ -833,7 +833,7 @@ private void prepareQueryPlan(HQLQueryPlan queryPlan, List namedParams = new HashMap(hibernateAccess.getNamedParams(lastHibernateQuery)); String queryString = hibernateAccess.expandParameterLists(session, lastHibernateQuery, namedParams); diff --git a/website/src/main/jbake/content/news/2018/blaze-persistence-1.3.0-Alpha2-release.adoc b/website/src/main/jbake/content/news/2018/blaze-persistence-1.3.0-Alpha2-release.adoc index 08d65eda35..f5d1897ba6 100644 --- a/website/src/main/jbake/content/news/2018/blaze-persistence-1.3.0-Alpha2-release.adoc +++ b/website/src/main/jbake/content/news/2018/blaze-persistence-1.3.0-Alpha2-release.adoc @@ -9,11 +9,11 @@ Christian Beikov :jbake-status: published :linkattrs: -Blaze-Persistence version 1.3.0-Alpha2 adds a few new features but mainly focused on bug fixes! +Blaze-Persistence version 1.3.0-Alpha2 adds a few new features but mainly has focused on bug fixes! The main features are the support for the new https://github.com/Blazebit/blaze-persistence/issues/367[`EMBEDDING_VIEW` function, window="_blank"] in entity views, that finally allows to refer to the entity relation of a view that embeds a `SubqueryProvider` or `CorrelationProvider`, but also in case of simple subviews. -Giovanni Lovato did his first PR and added support for passing entity view optional parameters to spring data repository methods via https://github.com/Blazebit/blaze-persistence/issues/325[`@OptionalParam`, window="_blank"], +https://github.com/heruan[Giovanni Lovato] did his first PR and added support for passing entity view optional parameters to spring data repository methods via https://github.com/Blazebit/blaze-persistence/issues/325[`@OptionalParam`, window="_blank"], thank you for that! A few other notable changes and fixes @@ -24,7 +24,7 @@ A few other notable changes and fixes * https://github.com/Blazebit/blaze-persistence/issues/602[*#602*, window="_blank"] Fix problems in entity views with `SUBSELECT` fetching when subview uses named parameters multiple times * https://github.com/Blazebit/blaze-persistence/issues/608[*#608*, window="_blank"] Omit null precedence ++++++ emulation expression on MySQL when it matches the native behavior -We are making good progress on the 1.3 timeline, but a final release will probably take a few more weeks as we preferred fixing bugs that new users encountered over strictly following our plan. +We are making good progress on the 1.3 timeline, but a final release will probably take a few more weeks as we prioritized fixing bugs that new users encountered over strictly following our plan. Currently, we assume we can do the 1.3.0 final release by mid August, and we will most likely publish another Alpha release by end of July. Grab the release while it's hot! We appreciate any feedback, so let us know what you think of this release :) \ No newline at end of file