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

org.rx.io.EntityQueryLambda Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package org.rx.io;

import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.rx.bean.BiTuple;
import org.rx.bean.Tuple;
import org.rx.core.Extends;
import org.rx.core.Linq;
import org.rx.core.Reflects;
import org.rx.core.StringBuilder;
import org.rx.third.guava.CaseFormat;
import org.rx.util.function.BiFunc;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@RequiredArgsConstructor
public class EntityQueryLambda implements Extends {
    private static final long serialVersionUID = 6543834322640433503L;

    @RequiredArgsConstructor
    enum Operator {
        AND("(%s)AND(%s)"), OR("(%s)OR(%s)"),
        EQ("%s=%s"), NE("%s!=%s"),
        GT("%s>%s"), LT("%s<%s"),
        GE("%s>=%s"), LE("%s<=%s"),
        IN("%s IN(%s)"), NOT_IN("%s NOT IN(%s)"),
        BETWEEN("%s BETWEEN %s AND %s"), NOT_BETWEEN("%s NOT BETWEEN %s AND %s"),
        LIKE("%s LIKE %s"), NOT_LIKE("%s NOT LIKE %s");

        final String format;
    }

    enum Order {
        ASC,
        DESC
    }

    static final String WHERE = " WHERE ", ORDER_BY = " ORDER BY ", GROUP_BY = " GROUP BY ", LIMIT = " LIMIT ",
            OP_AND = " AND ", DB_NULL = "NULL", PARAM_HOLD = "?";
    static final String FUNC_RAND = "RAND()";

    static void pkClaus(StringBuilder sql, String pk) {
        sql.append(WHERE).appendFormat(Operator.EQ.format, pk, PARAM_HOLD);
    }

    final Class entityType;
    final ArrayList> conditions = new ArrayList<>();
    final List, Order>> orders = new ArrayList<>();
    boolean orderByRand;
    Integer limit, offset;
    @Setter
    BiFunc columnMapping;

    public EntityQueryLambda limit(int limit) {
        this.limit = limit;
        return this;
    }

    public EntityQueryLambda limit(int offset, int limit) {
        this.offset = offset;
        this.limit = limit;
        return this;
    }

    public EntityQueryLambda newClause() {
        return new EntityQueryLambda<>(entityType);
    }

    public  EntityQueryLambda orderBy(BiFunc fn) {
        orders.add(Tuple.of(fn, Order.ASC));
        return this;
    }

    public  EntityQueryLambda orderByDescending(BiFunc fn) {
        orders.add(Tuple.of(fn, Order.DESC));
        return this;
    }

    public EntityQueryLambda orderByRand() {
        orderByRand = true;
        return this;
    }

    public EntityQueryLambda and(EntityQueryLambda lambda) {
        ArrayList> copy = new ArrayList<>(conditions);
        conditions.clear();
        conditions.add(BiTuple.of(copy, Operator.AND, lambda));
        orders.addAll(lambda.orders);
        lambda.orders.clear();
        return this;
    }

    public EntityQueryLambda or(EntityQueryLambda lambda) {
        ArrayList> copy = new ArrayList<>(conditions);
        conditions.clear();
        conditions.add(BiTuple.of(copy, Operator.OR, lambda));
        orders.addAll(lambda.orders);
        lambda.orders.clear();
        return this;
    }

    public  EntityQueryLambda eq(BiFunc fn, R val) {
        conditions.add(BiTuple.of(fn, Operator.EQ, val));
        return this;
    }

    public  EntityQueryLambda ne(BiFunc fn, R val) {
        conditions.add(BiTuple.of(fn, Operator.NE, val));
        return this;
    }

    public  EntityQueryLambda gt(BiFunc fn, R val) {
        conditions.add(BiTuple.of(fn, Operator.GT, val));
        return this;
    }

    public  EntityQueryLambda lt(BiFunc fn, R val) {
        conditions.add(BiTuple.of(fn, Operator.LT, val));
        return this;
    }

    public  EntityQueryLambda ge(BiFunc fn, R val) {
        conditions.add(BiTuple.of(fn, Operator.GE, val));
        return this;
    }

    public  EntityQueryLambda le(BiFunc fn, R val) {
        conditions.add(BiTuple.of(fn, Operator.LE, val));
        return this;
    }

    @SafeVarargs
    public final  EntityQueryLambda in(BiFunc fn, R... vals) {
        conditions.add(BiTuple.of(fn, Operator.IN, vals));
        return this;
    }

    @SafeVarargs
    public final  EntityQueryLambda notIn(BiFunc fn, R... vals) {
        conditions.add(BiTuple.of(fn, Operator.NOT_IN, vals));
        return this;
    }

    public  EntityQueryLambda between(BiFunc fn, R start, R end) {
        conditions.add(BiTuple.of(fn, Operator.BETWEEN, new Object[]{start, end}));
        return this;
    }

    public  EntityQueryLambda notBetween(BiFunc fn, R start, R end) {
        conditions.add(BiTuple.of(fn, Operator.NOT_BETWEEN, new Object[]{start, end}));
        return this;
    }

    public  EntityQueryLambda like(BiFunc fn, String expr) {
        conditions.add(BiTuple.of(fn, Operator.LIKE, expr));
        return this;
    }

    public  EntityQueryLambda notLike(BiFunc fn, String expr) {
        conditions.add(BiTuple.of(fn, Operator.NOT_LIKE, expr));
        return this;
    }

    @Override
    public String toString() {
        return toString(null);
    }

    public String toString(List params) {
        return resolve(conditions, params, orders, orderByRand, limit, offset, columnMapping);
    }

    static  List sharding(List result, EntityQueryLambda lambda) {
        Linq q = Linq.from(result);
        if (!lambda.orders.isEmpty()) {
            boolean isDescFirst = false;
            List> asc = new ArrayList<>(), desc = new ArrayList<>();
            for (int i = 0; i < lambda.orders.size(); i++) {
                Tuple, Order> order = lambda.orders.get(i);
                if (i == 0) {
                    isDescFirst = order.right == Order.DESC;
                }
                if (order.right == Order.DESC) {
                    desc.add(order.left);
                } else {
                    asc.add(order.left);
                }
            }
            if (isDescFirst) {
                q = q.orderByDescendingMany(p -> Linq.from(desc).select(x -> (Object) x.invoke(p)).toList())
                        .orderByMany(p -> Linq.from(asc).select(x -> (Object) x.invoke(p)).toList());
            } else {
                q = q.orderByMany(p -> Linq.from(asc).select(x -> (Object) x.invoke(p)).toList())
                        .orderByDescendingMany(p -> Linq.from(desc).select(x -> (Object) x.invoke(p)).toList());
            }
        }
        if (lambda.offset != null) {
            q = q.skip(lambda.offset);
        }
        if (lambda.limit != null) {
            q = q.take(lambda.limit);
        }
        return q.toList();
    }

    static  String resolve(ArrayList> conditions, List params,
                              List, Order>> orders, boolean orderByRand, Integer limit, Integer offset,
                              BiFunc columnMapping) {
        StringBuilder b = new StringBuilder(128);
        boolean isParam = params != null;
        for (BiTuple condition : conditions) {
            Operator op = condition.middle;
            switch (op) {
                case EQ:
                case NE:
                case GT:
                case LT:
                case GE:
                case LE:
                case LIKE:
                case NOT_LIKE: {
                    String colName = resolveColumnName(condition.left, columnMapping);
                    if (!b.isEmpty()) {
                        b.append(OP_AND);
                    }
                    String valHold;
                    if (isParam) {
                        params.add(condition.right);
                        valHold = PARAM_HOLD;
                    } else {
                        valHold = toValueString(condition.right);
                    }
                    b.appendFormat(op.format, colName, valHold);
                }
                break;
                case IN:
                case NOT_IN: {
                    String colName = resolveColumnName(condition.left, columnMapping);
                    if (!b.isEmpty()) {
                        b.append(OP_AND);
                    }
                    String valHold;
                    if (isParam) {
                        params.add(condition.right);
                        valHold = PARAM_HOLD;
                    } else {
                        valHold = Linq.from((Object[]) condition.right).toJoinString(",", EntityQueryLambda::toValueString);
                    }
                    b.appendFormat(op.format, colName, valHold);
                }
                break;
                case BETWEEN:
                case NOT_BETWEEN: {
                    String colName = resolveColumnName(condition.left, columnMapping);
                    Object[] p = (Object[]) condition.right;
                    if (!b.isEmpty()) {
                        b.append(OP_AND);
                    }
                    String valHold0, valHold1;
                    if (isParam) {
                        params.add(p[0]);
                        params.add(p[1]);
                        valHold0 = PARAM_HOLD;
                        valHold1 = PARAM_HOLD;
                    } else {
                        valHold0 = toValueString(p[0]);
                        valHold1 = toValueString(p[1]);
                    }
                    b.appendFormat(op.format, colName, valHold0, valHold1);
                }
                break;
                case AND:
                case OR:
                    ArrayList> l = (ArrayList>) condition.left;
                    EntityQueryLambda r = (EntityQueryLambda) condition.right;
                    if (!b.isEmpty()) {
                        b.append(OP_AND);
                    }
                    b.appendFormat(op.format, resolve(l, params, null, orderByRand, limit, offset, columnMapping), r.toString(params));
                    break;
            }
        }

        if (orderByRand) {
            b.append(ORDER_BY).append(FUNC_RAND);
        } else if (!CollectionUtils.isEmpty(orders)) {
            b.append(ORDER_BY);
            for (Tuple, Order> bi : orders) {
                String colName = resolveColumnName(bi.left, columnMapping);
                b.appendFormat("%s %s,", colName, bi.right);
            }
            b.setLength(b.length() - 1);
        }

        if (limit != null) {
            b.append(LIMIT);
            if (offset != null) {
                b.appendFormat("%s,", offset);
            }
            b.append(limit);
//            b.append(LIMIT).append(limit);
//            if (offset != null) {
//                b.append(" OFFSET %s", offset);
//            }
        }
        return b.toString();
    }

    static  String resolveColumnName(Object fn, BiFunc columnMapping) {
        String propName = Reflects.resolveProperty((BiFunc) fn);
        return columnMapping != null ? columnMapping.apply(propName) : propName;
//        return autoUnderscoreColumnName ? CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, propName)
//                : propName;
    }

    public static final BiFunc TO_UNDERSCORE_COLUMN_MAPPING = p -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, p);
    public static final BiFunc, String> TO_UNDERSCORE_TABLE_MAPPING = p -> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, p.getSimpleName());

    public static String toValueString(Object val) {
        if (val == null) {
            return DB_NULL;
        }
        if (val instanceof String) {
            return String.format("'%s'", ((String) val).replace("'", "‘"));
        }
        if (val instanceof Date || val instanceof Enum) {
            return String.format("'%s'", val);
        }
        return val.toString();
    }
}