All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gorm.tools.mango.jpql.JpqlQueryBuilder.groovy Maven / Gradle / Ivy

There is a newer version: 7.3.74
Show newest version
/*
* Copyright 2011 Yak.Works - Licensed under the Apache License, Version 2.0 (the "License")
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
package gorm.tools.mango.jpql

import groovy.transform.CompileStatic

import org.grails.datastore.mapping.model.AbstractPersistentEntity
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.model.PersistentProperty
import org.grails.datastore.mapping.model.types.Association
import org.grails.datastore.mapping.model.types.ToOne
import org.grails.datastore.mapping.query.AssociationQuery
import org.grails.datastore.mapping.query.Query
import org.grails.datastore.mapping.query.api.AssociationCriteria
import org.grails.datastore.mapping.query.api.QueryableCriteria
import org.springframework.core.convert.ConversionService
import org.springframework.core.convert.support.GenericConversionService
import org.springframework.dao.InvalidDataAccessResourceUsageException

import gorm.tools.mango.MangoDetachedCriteria
import gorm.tools.utils.GormMetaUtils
import grails.gorm.DetachedCriteria

/**
 * Builds JPQL String-based queries from the DetachedCriteria.
 * Used for projections and aggregate based queries as the criteria interface is limited there
 * Based on org.grails.datastore.mapping.query.jpa.JpaQueryBuilder
 *
 * @author Joshua Burnett (@basejump)
 * @since 7.0.8
 */
//FIXME refactor later
@SuppressWarnings(['BuilderMethodWithSideEffects', 'AbcMetric', 'ParameterCount', 'ExplicitCallToEqualsMethod', 'ClassSize', 'MethodSize'])
@CompileStatic
class JpqlQueryBuilder {
    private static final String DISTINCT_CLAUSE = "DISTINCT "
    private static final String ORDER_BY_CLAUSE = " ORDER BY "
    private static final char COMMA = ','
    private static final char DOT = '.'
    private static final String PARAMETER_PREFIX = ":p"

    private Map queryHandlers = [:]
    private Query.Junction criteria
    private Query.ProjectionList projectionList = new Query.ProjectionList()
    private List orders = Collections.emptyList()
    //the main root entity alias, will normall be just the decapitalized name
    private String entityAlias

    PersistentEntity entity
    boolean hibernateCompatible
    //wraps the SELECT in a new map( ...) when true
    boolean aliasToMap
    Map projectionAliases = [:]
    Map propertyAliases = [:]
    List groupByList = []
    boolean allowJoins = true
    ConversionService conversionService = new GenericConversionService()

    // JpqlQueryBuilder(QueryableCriteria criteria) {
    //     this(criteria.getPersistentEntity(), criteria.getCriteria())
    // }

    // JpqlQueryBuilder(PersistentEntity entity, List criteria) {
    //     this(entity, new Query.Conjunction(criteria))
    // }

    // JpqlQueryBuilder(PersistentEntity entity, List criteria, Query.ProjectionList projectionList) {
    //     this(entity, new Query.Conjunction(criteria), projectionList)
    // }

    // JpqlQueryBuilder(PersistentEntity entity, List criteria, Query.ProjectionList projectionList, List orders) {
    //     this(entity, new Query.Conjunction(criteria), projectionList, orders)
    // }

    JpqlQueryBuilder(PersistentEntity entity, Query.Junction criteria) {
        this.entity = entity
        this.criteria = criteria
        this.entityAlias = entity.getDecapitalizedName()
    }

    JpqlQueryBuilder(PersistentEntity entity) {
        this.entity = entity
        this.entityAlias = entity.getDecapitalizedName()
    }

    // JpqlQueryBuilder(PersistentEntity entity, Query.Junction criteria, Query.ProjectionList projectionList) {
    //     this(entity, criteria)
    //     this.projectionList = projectionList
    // }

    // JpqlQueryBuilder(PersistentEntity entity, Query.Junction criteria, Query.ProjectionList projectionList, List orders) {
    //     this(entity, criteria, projectionList)
    //     this.orders = orders
    // }

    static JpqlQueryBuilder of(MangoDetachedCriteria crit){
        var jqb = new JpqlQueryBuilder(crit.persistentEntity, new Query.Conjunction(crit.criteria) )
        jqb.initHandlers()
        // List criteria = crit.getCriteria()
        // jqb.criteria = new Query.Conjunction(criteria)
        jqb.propertyAliases = crit.propertyAliases

        List projections = crit.getProjections()
        for (Query.Projection projection : projections) {
            jqb.projectionList.add(projection)
        }

        jqb.orders = crit.getOrders()

        return jqb
    }

    /**
     * wraps the SELECT in a new map( ...)
     */
    JpqlQueryBuilder aliasToMap(boolean val){
        this.aliasToMap = val
        return this
    }

    public void setHibernateCompatible(boolean hibernateCompatible) {
        this.hibernateCompatible = hibernateCompatible
    }

    /**
     * Builds an UPDATE statement.
     *
     * @param propertiesToUpdate THe properties to update
     * @return The JpaQueryInfo object
     */
    public JpqlQueryInfo buildUpdate(Map propertiesToUpdate) {
        if (propertiesToUpdate.isEmpty()) {
            throw new InvalidDataAccessResourceUsageException("No properties specified to update")
        }
        // allowJoins = false
        StringBuilder queryString = new StringBuilder("UPDATE ${entity.getName()} ${entityAlias}")

        List parameters = []
        buildUpdateStatement(queryString, propertiesToUpdate, parameters)
        StringBuilder whereClause = new StringBuilder()
        buildWhereClause(queryString, whereClause, entityAlias, parameters)
        return new JpqlQueryInfo(queryString.toString(), parameters)
    }

    /**
     * Builds a DELETE statement
     *
     * @return The JpaQueryInfo
     */
    public JpqlQueryInfo buildDelete() {
        StringBuilder queryString = new StringBuilder("DELETE ${entity.getName()} ${entityAlias}")
        StringBuilder whereClause = new StringBuilder()
        allowJoins = false
        List parameters = buildWhereClause(queryString, whereClause, entityAlias)
        return new JpqlQueryInfo(queryString.toString(), parameters)
    }

    /**
     * Builds  SELECT statement
     *
     * @return The JpaQueryInfo
     */
    JpqlQueryInfo buildSelect() {
        StringBuilder queryString = new StringBuilder("SELECT ")

        buildSelectClause(queryString)

        StringBuilder whereClause= new StringBuilder()
        List parameters = []
        if (!criteria.isEmpty()) {
            parameters = buildWhereClause(queryString, whereClause, entityAlias, parameters)
        }

        buildGroup(queryString)

        if (!criteria.isEmpty() && projectionAliases) {
            buildHavingClause(queryString, entityAlias, parameters)
        }

        appendOrder(queryString, entityAlias)
        return new JpqlQueryInfo(queryString.toString(), parameters)
    }

    void buildGroup(StringBuilder queryString){
        if(groupByList){
            queryString.append(" GROUP BY ")
            for (Iterator i = groupByList.iterator(); i.hasNext();) {
                String val = (String) i.next()
                queryString.append("$val")
                if (i.hasNext()) {
                    queryString.append(COMMA)
                }
            }
        }
    }

    private void buildSelectClause(StringBuilder queryString) {
        Query.ProjectionList projectionList = this.projectionList
        String logicalName = this.entityAlias
        PersistentEntity entity = this.entity
        buildSelect(queryString, projectionList.getProjectionList(), logicalName, entity)

        queryString.append(" FROM ${entity.getName()} AS ${logicalName}")
    }

    void appendAlias( StringBuilder queryString, String projField, String name, String aliasPrefix){
        String aliasKey = "${aliasPrefix}_${name}"
        String propalias = propertyAliases.containsKey(aliasKey) ? propertyAliases[aliasKey] : name.replace('.', '_')
        propalias = "${propalias}"
        queryString
            .append(projField)
            .append(' as ')
            .append(propalias)
        projectionAliases[propalias] = projField
    }

    void buildSelect(StringBuilder queryString, List projectionList, String logicalName, PersistentEntity entity) {
        if (projectionList.isEmpty()) {
            queryString.append(DISTINCT_CLAUSE)
                    .append(logicalName)
        }
        else {
            if(aliasToMap){
                queryString.append("new map( ")
            }
            for (Iterator i = projectionList.iterator(); i.hasNext();) {
                Query.Projection projection = (Query.Projection) i.next()
                if (projection instanceof Query.CountProjection) {
                    String projField = "COUNT(${logicalName})"
                    appendAlias(queryString, projField, logicalName, 'COUNT')
                }
                else if (projection instanceof Query.IdProjection) {
                    queryString.append(logicalName)
                            .append(DOT)
                            .append(entity.getIdentity().getName())
                }
                else if (projection instanceof Query.PropertyProjection) {
                    Query.PropertyProjection pp = (Query.PropertyProjection) projection
                    if (projection instanceof Query.AvgProjection) {
                        String projField = "AVG(${logicalName}.${pp.getPropertyName()})"
                        appendAlias(queryString, projField, pp.getPropertyName(), 'AVG')
                    }
                    else if (projection instanceof Query.SumProjection) {
                        String projField = "SUM(${logicalName}.${pp.getPropertyName()})"
                        appendAlias(queryString, projField, pp.getPropertyName(), 'SUM')
                    }
                    else if (projection instanceof Query.MinProjection) {
                        String projField = "MIN(${logicalName}.${pp.getPropertyName()})"
                        appendAlias(queryString, projField, pp.getPropertyName(), 'MIN')
                    }
                    else if (projection instanceof Query.MaxProjection) {
                        String projField = "MAX(${logicalName}.${pp.getPropertyName()})"
                        appendAlias(queryString, projField, pp.getPropertyName(), 'MAX')
                    }
                    else if (projection instanceof Query.CountDistinctProjection) {
                        String projField = "COUNT(DISTINCT ${logicalName}.${pp.getPropertyName()})"
                        appendAlias(queryString, projField, pp.getPropertyName(), 'COUNT')
                    }
                    else {
                        String projField = "${logicalName}.${pp.getPropertyName()}"
                        appendAlias(queryString, projField, pp.getPropertyName(), '')
                        groupByList << "${logicalName}.${pp.getPropertyName()}".toString()
                    }
                }

                if (i.hasNext()) {
                    queryString.append(COMMA)
                    queryString.append(' ')
                }
            }

            if(aliasToMap){
                queryString.append(" )")
            }
        }
    }

    int appendCriteriaForOperator(StringBuilder q, String logicalName, final String name, int position, String operator) {
        //if its a projectionAlias then use it
        String propName = buildPropName(name, logicalName)

        q.append(propName)
         .append(operator)
         .append(PARAMETER_PREFIX)
         .append(++position)
        return position
    }

    /**
     * Build a property name like kitchenSink.amount for 'amount'
     * Will use the projection alias if exists
     */
    String buildPropName(String name, String logicalName) {
        String propName
        if(projectionAliases.containsKey(name)) {
            propName = projectionAliases[name]
        } else if(logicalName && !name.startsWith(logicalName + DOT)) {
            propName = logicalName + DOT + name
        }
        else {
            propName = name
        }
        return propName
    }

    QueryHandler getCompareQueryHandler(String compareOp){
        return new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.PropertyCriterion opCriterion = (Query.PropertyCriterion) criterion
                String name = opCriterion.getProperty()
                PersistentProperty prop = validateProperty(entity, name, opCriterion.class.simpleName)
                int newPosition = appendCriteriaForOperator(whereClause, logicalName, name, position, compareOp)
                if(prop){
                    Class propType = prop.getType()
                    parameters.add(conversionService.convert( opCriterion.getValue(), propType ))
                } else {
                    parameters.add(opCriterion.getValue())
                }
                return newPosition
            }
        }
    }

    //Property compares
    QueryHandler getPropertyCompareQueryHandler(String compareOp){
        new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.PropertyComparisonCriterion opCriterion = (Query.PropertyComparisonCriterion) criterion
                //handlePropCompare(entity, opCriterion, logicalName, compareOp,  whereClause)
                String propertyName = opCriterion.getProperty()
                String otherProperty = opCriterion.getOtherProperty()

                validateProperty(entity, propertyName, criterion.class.simpleName)
                validateProperty(entity, otherProperty, criterion.class.simpleName)
                appendPropertyComparison(whereClause, logicalName, propertyName, otherProperty, compareOp)

                return position
            }
        }
    }

    //Is Null, Is Empty, IS Not Null, etc...
    QueryHandler getISCheckQueryHandler(String compareOp){
        new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.PropertyNameCriterion ischeck = (Query.PropertyNameCriterion) criterion
                final String name = ischeck.getProperty()
                validateProperty(entity, name, compareOp)

                String propName = buildPropName(name, logicalName)
                whereClause.append(propName).append(compareOp)

                return position
            }
        }
    }

    //Is Null, Is Empty, IS Not Null, etc...
    QueryHandler getSubQueryHandler(String compareOp){
        new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.SubqueryCriterion subqueryCriterion = (Query.SubqueryCriterion) criterion
                return handleSubQuery(entity, q, whereClause, logicalName, position, parameters, subqueryCriterion, compareOp)
            }
        }
    }

    void initHandlers(){

        //Normal compares
        queryHandlers.put(Query.Equals, getCompareQueryHandler("="))
        queryHandlers.put(Query.NotEquals, getCompareQueryHandler(" != "))
        queryHandlers.put(Query.GreaterThan, getCompareQueryHandler(" > "))
        queryHandlers.put(Query.LessThan, getCompareQueryHandler(" < "))
        queryHandlers.put(Query.LessThanEquals, getCompareQueryHandler(" <= "))
        queryHandlers.put(Query.GreaterThanEquals, getCompareQueryHandler(" >= "))
        queryHandlers.put(Query.Like, getCompareQueryHandler(" like "))

        //Property compares
        queryHandlers.put(Query.EqualsProperty, getPropertyCompareQueryHandler(" = "))
        queryHandlers.put(Query.NotEqualsProperty, getPropertyCompareQueryHandler(" != "))
        queryHandlers.put(Query.GreaterThanProperty, getPropertyCompareQueryHandler(" > "))
        queryHandlers.put(Query.LessThanProperty, getPropertyCompareQueryHandler(" < "))
        queryHandlers.put(Query.LessThanEqualsProperty, getPropertyCompareQueryHandler(" <= "))
        queryHandlers.put(Query.GreaterThanEqualsProperty, getPropertyCompareQueryHandler(" >= "))

        //Is Null, Is Empty, IS Not Null, etc...
        queryHandlers.put(Query.IsNull, getISCheckQueryHandler(" IS NULL "))
        queryHandlers.put(Query.IsNotNull, getISCheckQueryHandler(" IS NOT NULL "))
        queryHandlers.put(Query.IsEmpty, getISCheckQueryHandler(" IS EMPTY "))
        queryHandlers.put(Query.IsNotEmpty, getISCheckQueryHandler(" IS NOT EMPTY "))

        //sub queries, not all of these supported by Mango but here for future use
        queryHandlers.put(Query.NotIn, getSubQueryHandler(" NOT IN ("))
        queryHandlers.put(Query.EqualsAll, getSubQueryHandler(" = ALL ("))
        queryHandlers.put(Query.NotEqualsAll, getSubQueryHandler(" != ALL ("))
        queryHandlers.put(Query.GreaterThanAll, getSubQueryHandler(" > ALL ("))
        queryHandlers.put(Query.GreaterThanSome, getSubQueryHandler(" > SOME ("))
        queryHandlers.put(Query.GreaterThanEqualsAll, getSubQueryHandler(" >= ALL ("))
        queryHandlers.put(Query.GreaterThanEqualsSome, getSubQueryHandler(" >= SOME ("))
        queryHandlers.put(Query.LessThanAll, getSubQueryHandler(" < ALL ("))
        queryHandlers.put(Query.LessThanSome, getSubQueryHandler(" < SOME ("))
        queryHandlers.put(Query.LessThanEqualsAll, getSubQueryHandler(" <= ALL ("))
        queryHandlers.put(Query.LessThanEqualsSome, getSubQueryHandler(" <= SOME ("))


        queryHandlers.put(AssociationQuery, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {

                if (!allowJoins) {
                    throw new InvalidDataAccessResourceUsageException("Joins cannot be used in a DELETE or UPDATE operation")
                }
                AssociationQuery aq = (AssociationQuery) criterion
                final Association association = aq.getAssociation()
                Query.Junction associationCriteria = aq.getCriteria()
                List associationCriteriaList = associationCriteria.getCriteria()

                return handleAssociationCriteria(q, whereClause, logicalName, position, parameters,
                    association, associationCriteria, associationCriteriaList)
            }
        })

        queryHandlers.put(Query.Negation, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {

                whereClause.append(" NOT(")

                final Query.Negation negation = (Query.Negation)criterion
                position = buildWhereClauseForCriterion(entity, negation, q, whereClause, logicalName, negation.getCriteria(), position,
                    parameters)
                whereClause.append(")")

                return position
            }
        })

        queryHandlers.put(Query.Conjunction, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion,
                              StringBuilder q, StringBuilder whereClause, String logicalName, int position, List parameters) {
                whereClause.append("(")

                final Query.Conjunction conjunction = (Query.Conjunction)criterion
                position = buildWhereClauseForCriterion(entity, conjunction, q, whereClause, logicalName, conjunction.getCriteria(),
                    position, parameters)
                whereClause.append(")")

                return position
            }
        })

        queryHandlers.put(Query.Disjunction, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion,
                              StringBuilder q, StringBuilder whereClause, String logicalName, int position, List parameters) {
                whereClause.append("(")

                final Query.Disjunction disjunction = (Query.Disjunction)criterion
                position = buildWhereClauseForCriterion(entity, disjunction, q, whereClause,  logicalName, disjunction.getCriteria(),
                    position, parameters)
                whereClause.append(")")

                return position
            }
        })

        queryHandlers.put(Query.IdEquals, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.IdEquals eq = (Query.IdEquals) criterion
                PersistentProperty prop = entity.getIdentity()
                Class propType = prop.getType()
                position = appendCriteriaForOperator(whereClause, logicalName, prop.getName(), position, "=")
                parameters.add(conversionService.convert( eq.getValue(), propType ))
                return position
            }
        })

        queryHandlers.put(Query.Between, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.Between between = (Query.Between) criterion
                final Object from = between.getFrom()
                final Object to = between.getTo()

                final String name = between.getProperty()
                PersistentProperty prop = validateProperty(entity, name, Query.Between.simpleName)
                Class propType = prop.getType()

                String propName = buildPropName(name, logicalName)

                whereClause.append("(")
                           .append(propName)
                           .append(" >= ")
                           .append(PARAMETER_PREFIX)
                           .append(++position)
                whereClause.append(" AND ")
                           .append(propName)
                           .append(" <= ")
                           .append(PARAMETER_PREFIX)
                           .append(++position)
                           .append(")")

                parameters.add(conversionService.convert( from, propType ))
                parameters.add(conversionService.convert( to, propType ))
                return position
            }
        })

        queryHandlers.put(Query.ILike, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.ILike eq = (Query.ILike) criterion
                final String name = eq.getProperty()
                PersistentProperty prop = validateProperty(entity, name, Query.ILike.simpleName)
                Class propType = prop.getType()

                whereClause.append("lower(")

                String propName = buildPropName(name, logicalName)

                whereClause
                 .append(propName)
                 .append(")")
                 .append(" like lower(")
                 .append(PARAMETER_PREFIX)
                 .append(++position)
                 .append(")")
                parameters.add(conversionService.convert( eq.getValue(), propType ))
                return position
            }
        })

        queryHandlers.put(Query.In, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.In inQuery = (Query.In) criterion
                final String name = inQuery.getProperty()
                PersistentProperty prop = validateProperty(entity, name, Query.In.simpleName)
                Class propType = prop.getType()

                String propName = buildPropName(name, logicalName)

                whereClause.append(propName).append(" IN (")
                QueryableCriteria subquery = inQuery.getSubquery()
                if(subquery != null) {
                    buildSubQuery(q, whereClause, position, parameters, subquery)
                }
                else {
                    for (Iterator i = inQuery.getValues().iterator(); i.hasNext();) {
                        Object val = i.next()
                        whereClause.append(PARAMETER_PREFIX)
                        whereClause.append(++position)
                        if (i.hasNext()) {
                            whereClause.append(COMMA)
                        }
                        parameters.add(conversionService.convert(val, propType))
                    }
                }
                whereClause.append(")")

                return position
            }
        })

        queryHandlers.put(Query.Exists, new QueryHandler() {
            public int handle(PersistentEntity entity, Query.Criterion criterion, StringBuilder q, StringBuilder whereClause,
                              String logicalName, int position, List parameters) {
                Query.Exists existsQuery = (Query.Exists) criterion

                whereClause.append("EXISTS ( ")
                QueryableCriteria subquery = existsQuery.getSubquery()
                if (subquery != null) {
                    buildSubQuery(q, whereClause, position, parameters, subquery)
                }
                whereClause.append(" ) ")
                return position
            }
        })

    }

    int handleSubQuery(PersistentEntity entity, StringBuilder q, StringBuilder whereClause, String logicalName, int position, List parameters,
                       Query.SubqueryCriterion equalsAll, String comparisonExpression) {
        final String name = equalsAll.getProperty()
        validateProperty(entity, name, Query.In.simpleName)
        QueryableCriteria subquery = equalsAll.getValue()
        whereClause.append(logicalName)
                .append(DOT)
                .append(name)
                .append(comparisonExpression)
        buildSubQuery(q, whereClause, position, parameters, subquery)
        whereClause.append(")")
        return position
    }

    void buildSubQuery(StringBuilder q, StringBuilder whereClause, int position, List parameters,
                       QueryableCriteria subquery) {
        PersistentEntity associatedEntity = subquery.getPersistentEntity()
        String associatedEntityName = associatedEntity.getName()
        String associatedEntityLogicalName = associatedEntity.getDecapitalizedName() + position
        whereClause.append("SELECT ")
        buildSelect(whereClause, subquery.getProjections(), associatedEntityLogicalName, associatedEntity)
        whereClause.append(" FROM ${associatedEntityName} ${associatedEntityLogicalName} WHERE ")

        List criteria = subquery.getCriteria()
        var conj = new Query.Conjunction(criteria)
        position = buildWhereClauseForCriterion(associatedEntity, conj, q, whereClause, associatedEntityLogicalName,
            criteria, position, parameters)

        // JpqlQueryBuilder subQueryBuilder = new  JpqlQueryBuilder(associatedEntity)
    }

    int handleAssociationCriteria(StringBuilder query, StringBuilder whereClause, String logicalName, int position, List parameters,
                                  Association association, Query.Junction associationCriteria, List associationCriteriaList) {
        if (association instanceof ToOne) {
            final String associationName = association.getName()
            logicalName = "${logicalName}.${associationName}"
            // JpqlQueryBuilder assocQueryBuilder = new  JpqlQueryBuilder(association.getAssociatedEntity())
            return buildWhereClauseForCriterion(association.getAssociatedEntity(), associationCriteria, query, whereClause, logicalName,
                associationCriteriaList, position, parameters)
        }
        else if (association != null) {
            final String associationName = association.getName()
            // TODO: Allow customization of join strategy!
            query.append(" INNER JOIN ${logicalName}.${associationName} ${associationName}")

            return buildWhereClauseForCriterion(association.getAssociatedEntity(), associationCriteria, query, whereClause, associationName,
                associationCriteriaList, position, parameters)
        }

        return position
    }

    private void buildUpdateStatement(StringBuilder queryString, Map propertiesToUpdate, List parameters) {
        queryString.append(" SET")

        // keys need to be sorted before query is built
        Set keys = new TreeSet(propertiesToUpdate.keySet())

        Iterator iterator = keys.iterator()
        while (iterator.hasNext()) {
            String propertyName = iterator.next()
            PersistentProperty prop = entity.getPropertyByName(propertyName)
            if (prop == null) throw new InvalidDataAccessResourceUsageException("""\
                Property '${propertyName}' of class '${entity.getName()}' specified in update does not exist
            """.stripIndent())

            parameters.add(propertiesToUpdate.get(propertyName))
            queryString.append(" ${entityAlias}.${propertyName}=")
            queryString.append(PARAMETER_PREFIX).append(parameters.size())
            if (iterator.hasNext()) {
                queryString.append(COMMA)
            }
        }
    }

    void appendPropertyComparison(StringBuilder q, String logicalName, String propertyName, String otherProperty,
                                                 String operator) {
        q.append(logicalName)
         .append(DOT)
         .append(propertyName)
         .append(operator)

        //FIXME hack for now, if the other property ends with _ and removnig that matches the logicalName then its the alias
        // used in EXISTS query where there is a subquery and we are tying it together.
        int dotIdx = otherProperty.indexOf(".")
        String rootObj = dotIdx > -1 ? otherProperty.substring(0, dotIdx) : otherProperty
        if(rootObj.endsWith('_') && rootObj[0..-2] == this.entityAlias){
            String restOfPath = dotIdx > -1 ? otherProperty.substring(dotIdx) : ""
            //[0..-2] removes last char from string
            q.append(rootObj[0..-2])
                .append(restOfPath)
        } else {
            q.append(logicalName).append(DOT).append(otherProperty)
        }

    }

    PersistentProperty validateProperty(PersistentEntity entity, String name, String whatItChecks) {
        if(name.endsWith('.id') && name.count('.') >= 1){
            return GormMetaUtils.getPersistentProperty(entity, name)
        }
        // if(name.endsWith('.id') && name.count('.') == 1) {
        //     String assoc = name.tokenize('.')[0]
        //     return (entity.getPropertyByName(assoc) as Association).getAssociatedEntity().getIdentity()
        // }

        PersistentProperty identity = entity.getIdentity()
        if (identity != null && identity.getName().equals(name)) {
            return identity
        }
        PersistentProperty[] compositeIdentity = ((AbstractPersistentEntity) entity).getCompositeIdentity()
        if(compositeIdentity != null) {
            for (PersistentProperty property : compositeIdentity) {
                if(property.getName().equals(name)) {
                    return property
                }
            }
        }
        PersistentProperty prop = entity.getPropertyByName(name)
        boolean isAliasProp = projectionAliases.containsKey(name)
        if (prop == null && !isAliasProp) {
            throw new InvalidDataAccessResourceUsageException("Cannot use [${whatItChecks}] criterion on non-existent property: " + name)
        }
        return prop
    }

    private List buildWhereClause(StringBuilder q, StringBuilder whereClause, String logicalName, List parameters = []) {
        if (!criteria.isEmpty()) {
            int position = parameters.size()
            final List criterionList = criteria.getCriteria()
            StringBuilder tempWhereClause = new StringBuilder()

            position = buildWhereClauseForCriterion(entity, criteria, q, tempWhereClause, logicalName, criterionList, position, parameters)

            //if it didn't build anything then dont add it
            if(tempWhereClause.toString()) {
                whereClause.append(" WHERE ")
                if (criteria instanceof Query.Negation) {
                    whereClause.append(" NOT")
                }
                whereClause.append("(")
                whereClause.append(tempWhereClause.toString())

                q.append(whereClause.toString())
                q.append(")")
            }
        }
        return parameters
    }

    private List buildHavingClause(StringBuilder q, String logicalName, List parameters) {
        if (!criteria.isEmpty() && projectionAliases) {
            int position = parameters.size()
            final List criterionList = criteria.getCriteria()

            StringBuilder tempHavingClause = new StringBuilder()
            position = buildHavingClauseForCriterion(q, tempHavingClause, logicalName, criterionList, position, parameters)

            if(tempHavingClause.toString()) {
                q.append(" HAVING ")
                q.append("(")
                q.append(tempHavingClause.toString())
                q.append(")")
            }
        }
        return parameters
    }

    protected void appendOrder(StringBuilder queryString, String logicalName) {
        if (!orders.isEmpty()) {
            queryString.append( ORDER_BY_CLAUSE)
            for (Query.Order order : orders) {
                //if its not in aliases then add the entityName
                if(!projectionAliases.containsKey(order.getProperty())){
                    queryString.append(logicalName)
                        .append(DOT)
                }
                queryString.append("${order.getProperty()} ${order.getDirection().toString()} ")
            }
        }
    }

    // int buildWhereClauseForCriterion(JpqlQueryBuilder subQueryBuilder, Query.Junction criteria, StringBuilder q, StringBuilder whereClause,
    //                                  String logicalName, final List criterionList, int position, List parameters) {
    //
    //     return buildWhereClauseForCriterion(subQueryBuilder.entity, criteria, q, whereClause, logicalName, criterionList, position, parameters)
    // }

    int buildWhereClauseForCriterion(PersistentEntity persistentEntity, Query.Junction criteria, StringBuilder q, StringBuilder whereClause,
                                     String logicalName, final List criterionList, int position, List parameters) {

        Map clauseTokens = [:] as Map

        for (Iterator iterator = criterionList.iterator(); iterator.hasNext();) {
            StringBuilder tempWhereClause = new StringBuilder()
            Query.Criterion criterion = iterator.next()

            //TODO handle it for situations like below
            //select sum(amount) as amount from artran where amount> 100 group by trantypeid ; VS
            //select sum(amount) as amount from artran group by trantypeid having sum(amount) > 100;
            if(criterion instanceof Query.PropertyNameCriterion){
                String prop = criterion.getProperty()
                String groupProp = logicalName ? "${logicalName}.${prop}" : prop
                if(projectionAliases.containsKey(prop) && !groupByList.contains(groupProp)){
                    continue
                }
            }
            final String operator = criteria instanceof Query.Conjunction ? " AND " : " OR "

            QueryHandler qh = queryHandlers.get(criterion.getClass())
            if (qh != null) {

                position = qh.handle(persistentEntity, criterion, q, tempWhereClause, logicalName,
                        position, parameters)
            }
            else if (criterion instanceof AssociationCriteria) {

                if (!allowJoins) {
                    throw new InvalidDataAccessResourceUsageException("Joins cannot be used in a DELETE or UPDATE operation")
                }
                AssociationCriteria ac = (AssociationCriteria) criterion
                Association association = ac.getAssociation()
                List associationCriteriaList = ac.getCriteria()
                position = handleAssociationCriteria(q, tempWhereClause, logicalName, position, parameters, association,
                    new Query.Conjunction(), associationCriteriaList)
            }
            else {
                throw new InvalidDataAccessResourceUsageException("Queries of type "+
                    criterion.getClass().getSimpleName()+" are not supported by this implementation")
            }

            String toStringWhere = tempWhereClause
            if(toStringWhere) clauseTokens[toStringWhere] = operator
            // if (whereClause.toString() && iterator.hasNext()) {
            //     whereClause.append(operator)
            // }
        }

        for (Iterator iterator = clauseTokens.keySet().iterator(); iterator.hasNext();) {
            String key = iterator.next()
            String operator = clauseTokens[key]
            whereClause.append(key)
            if(iterator.hasNext()) whereClause.append(operator)
        }

        return position
    }

    int buildHavingClauseForCriterion(StringBuilder q, StringBuilder whereClause,
                                      String logicalName, final List criterionList, int position, List parameters) {

        Map clauseTokens = [:] as Map

        for (Iterator iterator = criterionList.iterator(); iterator.hasNext();) {
            StringBuilder tempWhereClause = new StringBuilder()
            Query.Criterion criterion = iterator.next()
            //skip if its anything but a projection alias
            boolean isPropCrit = criterion instanceof Query.PropertyNameCriterion
            if(isPropCrit){
                //skip if its a groupby or it has an alias
                String prop = (criterion as Query.PropertyNameCriterion).getProperty()
                String groupProp = logicalName ? "${logicalName}.${prop}" : prop
                boolean isGrouped = groupByList.contains(groupProp)
                boolean hasAlias = projectionAliases.containsKey(prop)

                if(isGrouped || !hasAlias){
                    continue
                }
            } else {
                continue
            }
            final String operator = criteria instanceof Query.Conjunction ? " AND " : " OR "

            QueryHandler qh = queryHandlers.get(criterion.getClass())
            if (qh != null) {
                position = qh.handle(this.entity, criterion, q, tempWhereClause, '', position, parameters)
            }
            else if (criterion instanceof AssociationCriteria) {

                if (!allowJoins) {
                    throw new InvalidDataAccessResourceUsageException("Joins cannot be used in a DELETE or UPDATE operation")
                }
                AssociationCriteria ac = (AssociationCriteria) criterion
                Association association = ac.getAssociation()
                List associationCriteriaList = ac.getCriteria()
                position = handleAssociationCriteria(q, tempWhereClause, logicalName, position, parameters, association,
                    new Query.Conjunction(), associationCriteriaList)
            }
            else {
                throw new InvalidDataAccessResourceUsageException("Queries of type "+
                    criterion.getClass().getSimpleName()+" are not supported by this implementation")
            }

            String toStringWhere = tempWhereClause
            if(toStringWhere) clauseTokens[toStringWhere] = operator
        }

        for (Iterator iterator = clauseTokens.keySet().iterator(); iterator.hasNext();) {
            String key = iterator.next()
            String operator = clauseTokens[key]
            whereClause.append(key)
            if(iterator.hasNext()) whereClause.append(operator)
        }

        return position
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy