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

com.github.alittlehuang.data.jdbc.sql.AbstractSqlBuilder Maven / Gradle / Ivy

The newest version!
package com.github.alittlehuang.data.jdbc.sql;

import com.github.alittlehuang.data.jdbc.JdbcQueryStoredConfig;
import com.github.alittlehuang.data.metamodel.Attribute;
import com.github.alittlehuang.data.metamodel.EntityInformation;
import com.github.alittlehuang.data.query.page.Pageable;
import com.github.alittlehuang.data.query.specification.*;
import com.github.alittlehuang.data.util.Assert;
import com.github.alittlehuang.data.util.JointKey;

import javax.persistence.LockModeType;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.regex.Pattern;

import static javax.persistence.criteria.CriteriaBuilder.Trimspec;

/**
 * @author ALittleHuang
 */
public abstract class AbstractSqlBuilder implements QuerySqlBuilderFactory.SqlBuilder {
    private static final JoinType DEFAULT_JOIN_TYPE = JoinType.LEFT;
    private JdbcQueryStoredConfig config;
    private Criteria criteria;
    private EntityInformation rootEntityInfo;
    private List args = new ArrayList<>();
    private List selectedAttributes;
    private Map joinAttrs;
    private Pageable pageable;

    StringBuilder sql;

    public AbstractSqlBuilder(JdbcQueryStoredConfig config, Criteria criteria) {
        this.config = config;
        this.criteria = criteria;
        rootEntityInfo = getEntityInformation(criteria.getJavaType());
    }

    public AbstractSqlBuilder(JdbcQueryStoredConfig config, Criteria criteria, Pageable pageable) {
        this.config = config;
        this.criteria = criteria;
        rootEntityInfo = getEntityInformation(criteria.getJavaType());
        this.pageable = pageable;
    }

    @Override
    public PrecompiledSqlForEntity listEntityResult() {
        sql = new StringBuilder();
        appendSelectFromEntity();
        int index = sql.length();
        appendWhereClause();
        appendOrders();
        insertJoin(index);
        appendLimit();
        appendLockMode();
        return new PrecompiledSqlForEntity<>(sql.toString(), args, selectedAttributes);
    }

    @Override
    public PrecompiledSql listObjectResult() {
        sql = new StringBuilder();
        appendSelections();
        int index = sql.length();
        appendWhereClause();
        appendGroupings();
        appendOrders();
        insertJoin(index);
        appendLimit();
        appendLockMode();
        return new PrecompiledSql(sql.toString(), args);
    }

    @Override
    public PrecompiledSql count() {
        sql = new StringBuilder();
        sql.append("SELECT COUNT(1) AS count_").append(rootEntityInfo.getTableName()).append("_");
        appendFrom(rootEntityInfo);
        int index = sql.length();
        appendWhereClause();
        insertJoin(index);
        return new PrecompiledSql(sql.toString(), args);
    }

    @Override
    public PrecompiledSql exists() {
        sql = new StringBuilder();
        sql.append("SELECT ");
        appendRootTableAlias();
        sql.append(".`").append(rootEntityInfo.getIdAttribute().getColumnName())
                .append("` ");
        appendFrom(rootEntityInfo);
        int index = sql.length();
        appendWhereClause();
        insertJoin(index);
        sql.append(" LIMIT 1");
        return new PrecompiledSql(sql.toString(), args);
    }

    protected void appendLimit() {
        Long offset = criteria.getOffset();
        Long maxResults = criteria.getMaxResults();
        if (pageable != null) {
            offset = pageable.getOffset();
            maxResults = pageable.getPageSize();
        }
        if (offset != null || maxResults != null) {
            sql.append(" LIMIT ")
                    .append(offset == null ? 0 : offset)
                    .append(',')
                    .append(maxResults == null ? Long.MAX_VALUE : maxResults);
        }
    }

    protected void appendLockMode() {
        LockModeType lockModeType = criteria.getLockModeType();
        if (lockModeType != null) {
            switch (lockModeType) {
                case PESSIMISTIC_READ:
                    sql.append(" LOCK IN SHARE MODE");
                    break;
                case PESSIMISTIC_WRITE:
                case PESSIMISTIC_FORCE_INCREMENT:
                    sql.append(" FOR UPDATE");
                    break;
                default:
                    break;
            }
        }
    }

    protected void appendGroupings() {
        List> groupings = criteria.getGroupings();
        if (groupings == null || groupings.isEmpty()) {
            return;
        }
        sql.append(" GROUP BY ");
        boolean first = true;
        for (Expression grouping : groupings) {
            if (first) {
                first = false;
            } else {
                sql.append(",");
            }
            appendExpression(grouping);
        }
    }

    protected void appendOrders() {
        List> orders = criteria.getOrders();
        if (orders != null && !orders.isEmpty()) {
            sql.append(" ORDER BY ");
            boolean first = true;
            for (Orders order : orders) {
                if (first) {
                    first = false;
                } else {
                    sql.append(",");
                }
                appendExpression(order);
                sql.append(" ").append(order.getDirection());
            }

        }
    }

    protected void appendWhereClause() {
        WhereClause whereClause = criteria.getWhereClause();
        if (whereClause != null) {
            boolean notEmpty = !whereClause.isCompound() || !whereClause.getCompoundItems().isEmpty();
            if (notEmpty) {
                sql.append(" WHERE ");
                appendWhereClause(whereClause);
            }
        }
    }

    protected void insertJoin(int index) {
        if (joinAttrs != null && !joinAttrs.isEmpty()) {
            StringBuilder join = new StringBuilder();
            for (JoinAttr value : joinAttrs.values()) {
                buildJoin(join, value);
            }
            sql.insert(index, join);
        }
    }

    protected void buildJoin(StringBuilder sql, JoinAttr joinAttr) {
        JoinAttr parent = joinAttr.parent;
        if (parent != null) {
            buildJoin(sql, parent);
        }
        if (!joinAttr.appended) {
            joinAttr.appended = true;
            sql.append(" ").append(joinAttr.joinType).append(" JOIN `")
                    .append(joinAttr.attrInfo.getTableName())
                    .append("` ");
            joinAttr.appendAlias(sql);
            sql.append(" ON ");

            if (parent != null) {
                parent.appendAlias(sql);
            } else {
                appendRootTableAlias(sql);
            }

            sql.append(".`").append(joinAttr.attribute.getJoinColumn().name()).append("`=");
            joinAttr.appendAlias(sql);
            String referenced = joinAttr.attribute.getJoinColumn().referencedColumnName();
            if (referenced.length() == 0) {
                referenced = joinAttr.attrInfo.getIdAttribute().getColumnName();
            }
            sql.append(".`").append(referenced).append('`');

        }
    }

    protected void appendFrom(EntityInformation entityInfo) {
        sql.append(" FROM `")
                .append(entityInfo.getTableName())
                .append("` ");
        appendRootTableAlias();
    }

    protected void appendSelectFromEntity() {
        selectedAttributes = new ArrayList<>();
        sql.append("SELECT ");
        boolean first = true;
        for (Attribute attribute : rootEntityInfo.getBasicAttributes()) {
            if (first) {
                first = false;
            } else {
                sql.append(",");
            }
            appendRootTableAlias();
            sql.append('.');
            appendColumnName(attribute);
            selectedAttributes.add(new SelectedAttribute(attribute));
        }
        List> fetchList = criteria.getFetchAttributes();
        if (fetchList != null && !fetchList.isEmpty()) {
            for (FetchAttribute fetch : fetchList) {
                String[] names = fetch.getNames(criteria.getJavaType());
                boolean hasCollections = false;
                Class upEntityType = rootEntityInfo.getJavaType();
                for (String name : names) {
                    EntityInformation information = getEntityInformation(upEntityType);
                    Attribute attribute = information.getAttribute(name);
                    if (attribute.isCollection()) {
                        hasCollections = true;
                        break;
                    }
                    upEntityType = attribute.getJavaType();
                }

                if (!hasCollections) {
                    String[] tmp = new String[names.length + 1];
                    System.arraycopy(names, 0, tmp, 0, names.length);

                    Attribute attr = rootEntityInfo.getAttribute(names[0]);
                    EntityInformation attrInfo = getEntityInformation(attr.getJavaType());
                    SelectedAttribute select = new SelectedAttribute(attr, null);
                    if (names.length > 1) {
                        for (int i = 1; i < names.length; i++) {
                            attr = attrInfo.getAttribute(names[i]);
                            attrInfo = getEntityInformation(attr.getJavaType());
                            select = new SelectedAttribute(attr, select);
                        }
                    }
                    for (Attribute attribute : attrInfo.getBasicAttributes()) {
                        sql.append(",");
                        tmp[names.length] = attribute.getFieldName();
                        appendAttribute(tmp, fetch.getJoinType());
                        selectedAttributes.add(new SelectedAttribute(attribute, select));
                    }
                }
            }
        }

        sql.append(" ");
        appendFrom(rootEntityInfo);
    }

    protected void appendSelections() {
        List> selections = criteria.getSelections();
        Assert.state(selections != null && !selections.isEmpty(), "selections must not be empty");

        sql.append("SELECT ");
        boolean first = true;
        for (Selection selection : selections) {
            if (first) {
                first = false;
            } else {
                sql.append(",");
            }
            AggregateFunctions aggregate = selection.getAggregateFunctions();
            if (aggregate == null || aggregate == AggregateFunctions.NONE) {
                appendExpression(selection);
            } else {
                sql.append(aggregate).append("(");
                appendExpression(selection);
                sql.append(")");
            }
        }

        sql.append(" ");
        appendFrom(rootEntityInfo);
    }

    protected void appendColumnName(Attribute attribute) {
        sql.append("`").append(attribute.getColumnName()).append("`");
    }

    protected void appendRootTableAlias() {
        appendRootTableAlias(sql);
    }

    protected void appendRootTableAlias(StringBuilder sql) {
        sql.append(rootEntityInfo.getTableName()).append("0_");
    }

    protected void appendWhereClause(WhereClause whereClause) {
        if (whereClause.isCompound()) {
            appendCompoundWhereClause(whereClause);
        } else {
            appendNonCompoundWhereClause(whereClause);
        }
    }

    protected void appendNonCompoundWhereClause(WhereClause item) {
        Expression expression = item.getExpression();
        boolean negate = item.isNegate();
        appendExpression(expression);
        switch (item.getConditionalOperator()) {
            case EQUAL:
                appendComparisonOperatorExpression(item, negate ? "<>" : "=");
                break;
            case GREATER_THAN:
                appendComparisonOperatorExpression(item, negate ? "<=" : ">");
                break;
            case LESS_THAN:
                appendComparisonOperatorExpression(item, negate ? ">=" : "<");
                break;
            case GREATER_THAN_OR_EQUAL_TO:
                appendComparisonOperatorExpression(item, negate ? "<" : ">=");
                break;
            case LESS_THAN_OR_EQUAL_TO:
                appendComparisonOperatorExpression(item, negate ? ">" : "<=");
                break;
            case BETWEEN: {
                appendBetweenExpression(item, negate);
                break;
            }
            case IN: {
                appendInExpression(item, negate);
                break;
            }
            case LIKE:
                appendComparisonOperatorExpression(item, negate ? " NOT LIKE " : " LIKE ");
                break;
            case IS_NULL:
                sql.append(negate ? " IS NOT NULL" : " IS NULL");
                break;
            default:
                break;
        }

    }

    private void appendInExpression(WhereClause item, boolean negate) {
        sql.append(negate ? " NOT IN(" : " IN(");
        appendSqlParameter(item.getParameter());
        sql.append(")");
    }

    private void appendBetweenExpression(WhereClause item, boolean negate) {
        Iterator iterator = ((Iterable) item.getParameter()).iterator();
        sql.append(negate ? " NOT BETWEEN " : " BETWEEN ");
        appendSimpleParam(iterator.next());
        sql.append(" AND ");
        appendSimpleParam(iterator.next());
    }

    protected void appendComparisonOperatorExpression(WhereClause item, String operator) {
        Object parameter = item.getParameter();
        sql.append(operator);
        appendSqlParameter(parameter);
    }

    protected void appendSqlParameter(Object parameter) {
        if (parameter instanceof Expression) {
            //noinspection unchecked
            appendExpression((Expression) parameter);
        } else if (parameter instanceof Iterable) {
            boolean first = true;
            for (Object arg : ((Iterable) parameter)) {
                if (first) {
                    first = false;
                } else {
                    sql.append(',');
                }
                appendSimpleParam(arg);
            }
        } else {
            appendSimpleParam(parameter);
        }
    }

    private static Set> BASIC_NUMBER_CLASS = new HashSet<>(Arrays.asList(
            BigInteger.class,
            BigDecimal.class,
            Byte.class,
            Short.class,
            Integer.class,
            Long.class,
            Float.class,
            Double.class
    ));

    protected void appendSimpleParam(Object arg) {
        if (arg != null && BASIC_NUMBER_CLASS.contains(arg.getClass())) {
            sql.append(arg);
        } else {
            sql.append("?");
            args.add(arg);
        }
    }

    protected void appendFunArg(Object[] parameter) {
        boolean first = true;
        for (Object o : parameter) {
            if (first) {
                first = false;
            } else {
                sql.append(",");
            }
            appendSqlParameter(o);
        }
    }

    protected void appendExpression(Expression expression) {
        Expression.Function function = expression.getFunction();
        function = function == null ? Expression.Function.NONE : function;
        Object[] args = expression.getArgs();
        switch (function) {
            case NONE:
                appendAttribute(expression);
                break;
            case ABS:
                appendSingleParameterFunction(expression, "ABS");
                break;
            case SUM: {
                numberOperation(expression, "+", args[0]);
                break;
            }
            case PROD: {
                numberOperation(expression, "*", args[0]);
                break;
            }
            case DIFF: {
                numberOperation(expression, "-", args[0]);
                break;
            }
            case QUOT: {
                numberOperation(expression, "/", args[0]);
                break;
            }
            case MOD:
                String mod = "MOD";
                appendMultiParameterFunction(expression, mod);
                break;
            case SQRT:
                appendSingleParameterFunction(expression, "SQRT");
                break;
            case CONCAT:
                String concat = "CONCAT";
                appendMultiParameterFunction(expression, concat);
                break;
            case SUBSTRING:
                appendMultiParameterFunction(expression, "SUBSTRING");
                break;
            case TRIM:
                trim(expression, args);
                break;
            case LOWER:
                appendSingleParameterFunction(expression, "LOWER");
                break;
            case UPPER:
                appendSingleParameterFunction(expression, "UPPER");
                break;
            case LENGTH:
                appendSingleParameterFunction(expression, "LENGTH");
                break;
            case LOCATE:
                locate(expression, args);
                break;
            case COALESCE:
                appendMultiParameterFunction(expression, "COALESCE");
                break;
            case NULLIF:
                appendMultiParameterFunction(expression, "NULLIF");
                break;
            case CUSTOMIZE:
                customize(expression);
                break;
            default:
        }

    }

    private void numberOperation(Expression expression, String operator, Object arg) {
        appendAttribute(expression.getSubexpression());
        sql.append(operator);
        appendFunArgs(arg);
    }

    private void customize(Expression expression) {
        String functionName = expression.getFunctionName();
        if (expression.getArgs() == null || expression.getArgs().length == 0) {
            appendSingleParameterFunction(expression, functionName);
        } else {
            appendMultiParameterFunction(expression, functionName);
        }
    }

    private void locate(Expression expression, Object[] args) {
        sql.append("LOCATE").append("(");
        appendFunArg(args);
        sql.append(",");
        appendAttribute(expression.getSubexpression());
        sql.append(")");
    }

    private void trim(Expression expression, Object[] args) {
        if (args == null || args.length == 0) {
            appendSingleParameterFunction(expression, "TRIM");
        } else {
            Trimspec p0 = (Trimspec) args[0];
            char p1 = ' ';
            if (args.length > 1) {
                p1 = (char) args[1];
            }
            sql.append("TRIM(").append(p0).append(" '").append(p1).append("' FROM ");
            appendExpression(expression.getSubexpression());
            sql.append(")");
        }
    }

    protected void appendMultiParameterFunction(Expression expression, String funStr) {
        sql.append(funStr).append("(");
        appendAttribute(expression.getSubexpression());
        sql.append(",");
        appendFunArg(expression.getArgs());
        sql.append(")");
    }

    private static Pattern pattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]*$");

    protected void appendSingleParameterFunction(Expression expression, String funStr) {
        Assert.state(pattern.matcher(funStr).matches(), "function name error");
        sql.append(funStr).append("(");
        appendExpression(expression.getSubexpression());
        sql.append(")");
    }

    protected void appendFunArgs(Object arg) {
        if (arg instanceof Expression) {
            //noinspection unchecked
            Expression ex = (Expression) arg;
            boolean lowPriority = ex.getFunction() == Expression.Function.SUM
                    || ex.getFunction() == Expression.Function.DIFF;
            if (lowPriority) {
                sql.append("(");
            }
            appendExpression(ex);
            if (lowPriority) {
                sql.append(")");
            }
        } else {
            appendSqlParameter(arg);
        }
    }

    protected void appendAttribute(AttributePath attribute) {
        String[] names = attribute.getNames(criteria.getJavaType());
        appendAttribute(names, null);
    }

    protected void appendAttribute(String[] names, JoinType joinType) {
        Attribute attr = rootEntityInfo.getAttribute(names[0]);
        if (names.length > 1) {
            joinAttrs = joinAttrs == null ? new HashMap<>() : joinAttrs;
            JoinAttr joinAttr = null;
            for (int i = 1; i < names.length; i++) {
                JointKey key = new JointKey(joinAttr, attr);
                if (!joinAttrs.containsKey(key)) {
                    joinAttrs.put(key, new JoinAttr(joinAttr, attr));
                }
                joinAttr = joinAttrs.get(key);
                if (i == names.length - 1) {
                    joinAttr.joinType = joinType == null ? DEFAULT_JOIN_TYPE : joinType;
                } else if (joinAttr.joinType == null) {
                    joinAttr.joinType = DEFAULT_JOIN_TYPE;
                }

                EntityInformation attrInfo = getEntityInformation(attr.getJavaType());
                attr = attrInfo.getAttribute(names[i]);
                if (!attr.isEntityType()) {
                    joinAttr.appendAlias(sql);
                    sql.append('.');
                }
            }
        } else {
            appendRootTableAlias();
            sql.append('.');
        }
        appendColumnName(attr);
    }

    protected void appendCompoundWhereClause(WhereClause whereClause) {

        int appendIndex = sql.length();

        List> items = whereClause.getCompoundItems();
        if (items.size() == 1) {
            WhereClause sub = items.get(0);
            if (sub.isCompound()) {
                appendCompoundWhereClause(sub);
            } else {
                appendNonCompoundWhereClause(sub);
            }
        } else if (!items.isEmpty()) {
            Predicate.BooleanOperator preOperator = null;
            boolean firstWhereClause = true;
            for (WhereClause item : items) {
                if (isEmpty(item)) {
                    continue;
                } else if (!firstWhereClause) {
                    Predicate.BooleanOperator operator = item.getBooleanOperator();
                    if (preOperator == Predicate.BooleanOperator.OR && operator == Predicate.BooleanOperator.AND) {
                        sql.insert(appendIndex, "(").append(")");
                    }
                    sql.append(operator == Predicate.BooleanOperator.OR ? " OR " : " AND ");
                    preOperator = operator;
                } else {
                    firstWhereClause = false;
                }
                boolean compound = item.isCompound() && item.getCompoundItems().size() > 1;
                if (compound) {
                    sql.append("(");
                }
                appendWhereClause(item);
                if (compound) {
                    sql.append(")");
                }
            }
        }
    }

    private boolean isEmpty(WhereClause whereClause) {
        return WhereClause.isEmpty(whereClause);
    }

    public  EntityInformation getEntityInformation(Class clazz) {
        EntityInformation info = config.getEntityInformationFactory().getEntityInfo(clazz);
        Assert.notNull(info, "the type " + clazz + " is not an entity type");
        return info;
    }

    class JoinAttr {
        Attribute attribute;
        EntityInformation attrInfo;
        JoinAttr parent;
        JoinType joinType;
        boolean appended = false;
        int index = joinAttrs.size();

        public JoinAttr(JoinAttr parent, Attribute attribute) {
            this.parent = parent;
            this.attribute = attribute;
            this.attrInfo = getEntityInformation(attribute.getJavaType());
        }

        void appendAlias(StringBuilder sql) {
            sql.append(attrInfo.getTableName())
                    .append(index)
                    .append("_JN_");
        }
    }
}