【Java】LambdaToSql

java lambda to sql

目录

SqlVisitor

package xyz.xkind.core.sql.lambda;

import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.trigersoft.jaque.expression.BinaryExpression;
import com.trigersoft.jaque.expression.ConstantExpression;
import com.trigersoft.jaque.expression.DelegateExpression;
import com.trigersoft.jaque.expression.Expression;
import com.trigersoft.jaque.expression.ExpressionType;
import com.trigersoft.jaque.expression.ExpressionVisitor;
import com.trigersoft.jaque.expression.InvocationExpression;
import com.trigersoft.jaque.expression.LambdaExpression;
import com.trigersoft.jaque.expression.MemberExpression;
import com.trigersoft.jaque.expression.ParameterExpression;
import com.trigersoft.jaque.expression.UnaryExpression;

import lombok.NonNull;
import xyz.xkind.core.text.DateTimeUtils;

public class SqlVisitor implements ExpressionVisitor<SqlWrapper> {

    private Expression body;
    private Expression parameter;
    private List<ConstantExpression> parameters = new ArrayList<>();
    private SqlWrapper sql = new SqlWrapper();

    private final String head;
    private final String tail;

    public SqlVisitor() {
        this("`", "`");
    }
    
    public SqlVisitor(@NonNull String head, @NonNull String tail) {
        this.head = head;
        this.tail = tail;
    }

    private static String toSqlOp(int expressionType) {
        switch (expressionType) {
            case ExpressionType.BitwiseAnd: return "&";
            case ExpressionType.LogicalAnd: return "and";
            case ExpressionType.BitwiseOr: return "|";
            case ExpressionType.LogicalOr: return "or";
            case ExpressionType.LogicalNot: return "not";
            case ExpressionType.Equal: return "=";
            case ExpressionType.NotEqual: return "<>";
            case ExpressionType.GreaterThan: return ">";
            case ExpressionType.GreaterThanOrEqual: return ">=";
            case ExpressionType.LessThan: return "<";
            case ExpressionType.LessThanOrEqual: return "<=";
            case ExpressionType.Add: return "+";
            case ExpressionType.Subtract: return "-";
            case ExpressionType.Multiply: return "*";
            case ExpressionType.Divide: return "/";
            case ExpressionType.Modulo: return "%";
            default: throw new RuntimeException("Unknown ExpressionType '" + expressionType + "'");
        }
    }

    @Override
    public SqlWrapper visit(BinaryExpression e) {
        boolean quote = e != body && e.getExpressionType() == ExpressionType.LogicalOr;
        if (quote) { sql.getKey().append('('); };
        e.getFirst().accept(this);
        sql.getKey().append(' ').append(toSqlOp(e.getExpressionType())).append(' ');
        e.getSecond().accept(this);
        if (quote) { sql.getKey().append(')'); };
        return sql;
    }

    @Override
    public SqlWrapper visit(ConstantExpression e) {
        final Object value = e.getValue();
        if (value instanceof LambdaExpression) {
            return ((LambdaExpression<?>) value).getBody().accept(this);
        }
        sql.getKey().append('?');
        if (value instanceof Date) {
            Object ret = DateTimeUtils.DATE_TIME.formatDate((Date) value);
            sql.getValue().add(ret);
            return sql;
        }
        if (value instanceof LocalTime) {
            Object ret = DateTimeUtils.TIME.format((TemporalAccessor) value);
            sql.getValue().add(ret);
            return sql;
        }
        if (value instanceof LocalDate) {
            Object ret = DateTimeUtils.DATE.format((TemporalAccessor) value);
            sql.getValue().add(ret);
            return sql;
        }
        if (value instanceof LocalDateTime) {
            Object ret = DateTimeUtils.DATE_TIME.format((TemporalAccessor) value);
            sql.getValue().add(ret);
            return sql;
        }
        Object ret = value.toString();
        sql.getValue().add(ret);
        return sql;
    }

    @Override
    public SqlWrapper visit(InvocationExpression e) {
        final Expression target = e.getTarget();
        if (target instanceof LambdaExpression<?>) {
            e.getArguments().stream()
                            .filter(x -> x instanceof ConstantExpression)
                            .forEach(x -> parameters.add((ConstantExpression) x));
        }
        if (target.getExpressionType() == ExpressionType.MethodAccess) {
            e.getArguments().stream().findFirst().ifPresent(x -> { parameter = x; });
        }
        return e.getTarget().accept(this);
    }

    @Override
    public SqlWrapper visit(LambdaExpression<?> e) {
        if (null == body && e.getBody() instanceof BinaryExpression) {
            this.body = e.getBody();
        }
        return e.getBody().accept(this);
    }

    @Override
    public SqlWrapper visit(DelegateExpression e) {
        return e.getDelegate().accept(this);
    }

    @Override
    public SqlWrapper visit(MemberExpression e) {
        final Member member = e.getMember();
        final Expression instance = e.getInstance();
        if (instance == null) {
            return invoke(member, e);
        }
        if (instance instanceof ParameterExpression) {
            String name = member.getName();
            name = name.replaceAll("^(get)", "");
            name = name.substring(0, 1).toLowerCase() + name.substring(1);
            sql.getKey().append(head).append(name).append(tail);
        }
        if (instance instanceof ConstantExpression) {
            parameter.accept(this);
            return invoke(member, instance);
        }
        if (instance instanceof InvocationExpression) {
            ((InvocationExpression) instance).getTarget().accept(this);
            return invoke(member, parameter);
        }
        return sql;
    }

    @Override
    public SqlWrapper visit(ParameterExpression e) {
        parameters.get(e.getIndex()).accept(this);
        return sql;
    }

    @Override
    public SqlWrapper visit(UnaryExpression e) {
        sql.getKey().append(toSqlOp(e.getExpressionType())).append(' ');
        return e.getFirst().accept(this);
    }

    // ----------------------------------------------------------------------------------

    private SqlWrapper invoke(final Member member, final Expression expression) {
        if (member instanceof Constructor<?>) {
            return doCtorOp(member, expression);
        } else if (member instanceof Method) {
            final Class<?> clz = member.getDeclaringClass();
            if (Date.class.isAssignableFrom(clz)) {
                return doDateOp(member, expression);
            } else if (TemporalAccessor.class.isAssignableFrom(clz)) {
                return doDateOp(member, expression);
            } else if (String.class.isAssignableFrom(clz)) {
                return doStrOp(member, expression);
            } else {
                return doMethodOp(member, expression);
            }
        } else {
            throw new RuntimeException("The parameter '" + expression + "' not supported.");
        }
    }

    private SqlWrapper doCtorOp(final Member member, final Expression expression) {
        final Class<?> clz = member.getDeclaringClass();
        try {
            Object value = clz.newInstance();
            return Expression.constant(value, clz).accept(this);
        } catch (ReflectiveOperationException ex) {
            throw new RuntimeException(ex.getMessage(), ex.getCause());
        }
    }

    private SqlWrapper doStrOp(final Member member, final Expression expression) {
        final String method = member.getName();
        switch (method) {
            case "equals"/* = '{0}' */:
                sql.getKey().append(" = ");
                expression.accept(this);
                break;
            case "startsWith"/* like '{0}%' */:
                sql.getKey().append(" like ").append("concat(");
                expression.accept(this);
                sql.getKey().append(",'%')");
                break;
            case "endsWith"/* like '%{0}' */:
                sql.getKey().append(" like ").append("concat(").append("'%',");
                expression.accept(this);
                sql.getKey().append(")");
                break;
            case "contains"/* like '%{0}%' */:
                sql.getKey().append(" like ").append("concat(").append("'%',");
                expression.accept(this);
                sql.getKey().append(",'%')");
                break;
            default:
                return doMethodOp(member, expression);
        }
        return sql;
    }

    private SqlWrapper doDateOp(final Member member, final Expression expression) {
        final String method = member.getName();
        switch (method) {
            case "equals"/* = '{0}' */:
                sql.getKey().append(" = ");
                expression.accept(this);
                break;
            case "after"/* > '{0}' */:
            case "isAfter":
                sql.getKey().append(" > ");
                expression.accept(this);
                break;
            case "before"/* < '{0}' */:
            case "isBefore":
                sql.getKey().append(" < ");
                expression.accept(this);
                break;
            default:
                return doMethodOp(member, expression);
        }
        return sql;
    }

    private SqlWrapper doMethodOp(final Member member, final Expression expression) {
        int mod = member.getModifiers();
        if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) {
            try {
                final Class<?> clz = member.getDeclaringClass();
                Object value = clz.getMethod(member.getName()).invoke(null);
                return Expression.constant(value, clz).accept(this);
            } catch (ReflectiveOperationException ex) {
                throw new RuntimeException(ex.getMessage(), ex.getCause());
            }
        }
        throw new RuntimeException("The parameter '" + expression + "' not supported.");
    }

}

SqlWrapper

package xyz.xkind.core.sql.lambda;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SqlWrapper implements Map.Entry<StringBuilder, List<Object>> {
    private final StringBuilder sql;
    private final List<Object> params; 
       
    public SqlWrapper() {
        sql = new StringBuilder();
        params = new ArrayList<>();
    }
    
    @Override
    public StringBuilder getKey() {
        return sql;
    }
    public StringBuilder getSql() {
        return getKey();
    }
    
    @Override
    public List<Object> getValue() {
        return params;
    }
    public List<Object> getParams() {
        return getValue();
    }
    
    @Override
    public List<Object> setValue(List<Object> value) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public String toString() {
        return sql.toString() + System.lineSeparator() + params.toString();
    }
}

SerializedExpression

package xyz.xkind.core.sql.lambda;

import java.io.Serializable;

import com.trigersoft.jaque.expression.ExpressionVisitor;
import com.trigersoft.jaque.expression.LambdaExpression;

interface SerializedExpression<R> extends Serializable {
    default SqlWrapper sql() {
        return LambdaExpression.parse(this).accept(new SqlVisitor());
    }
    default R sql(ExpressionVisitor<R> visitor) {
        return (R) LambdaExpression.parse(this).accept(visitor);
    }
}

SqlPredicate

package xyz.xkind.core.sql.lambda;

import java.util.Objects;
import java.util.function.Predicate;

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@FunctionalInterface
public interface SqlPredicate<T> extends Predicate<T>, SerializedExpression<SqlWrapper> {

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * AND of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code false}, then the {@code other}
     * predicate is not evaluated.
     *
     * <p>Any exceptions thrown during evaluation of either predicate are relayed
     * to the caller; if evaluation of this predicate throws an exception, the
     * {@code other} predicate will not be evaluated.
     *
     * @param other a predicate that will be logically-ANDed with this
     *              predicate
     * @return a composed predicate that represents the short-circuiting logical
     * AND of this predicate and the {@code other} predicate
     * @throws NullPointerException if other is null
     */
    default SqlPredicate<T> and(SqlPredicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * Returns a predicate that represents the logical negation of this
     * predicate.
     *
     * @return a predicate that represents the logical negation of this
     * predicate
     */
    default SqlPredicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * OR of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code true}, then the {@code other}
     * predicate is not evaluated.
     *
     * <p>Any exceptions thrown during evaluation of either predicate are relayed
     * to the caller; if evaluation of this predicate throws an exception, the
     * {@code other} predicate will not be evaluated.
     *
     * @param other a predicate that will be logically-ORed with this
     *              predicate
     * @return a composed predicate that represents the short-circuiting logical
     * OR of this predicate and the {@code other} predicate
     * @throws NullPointerException if other is null
     */
    default SqlPredicate<T> or(SqlPredicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * Returns a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}.
     *
     * @param <T> the type of arguments to the predicate
     * @param targetRef the object reference with which to compare for equality,
     *               which may be {@code null}
     * @return a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}
     */
    static <T> SqlPredicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

}

SqlFunction

package xyz.xkind.core.sql.lambda;

import java.util.Objects;
import java.util.function.Function;

/**
 * Represents a function that accepts one argument and produces a result.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object)}.
 *
 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 *
 * @since 1.8
 */
@FunctionalInterface
public interface SqlFunction<T, R> extends Function<T, R>, SerializedExpression<SqlWrapper> {

    /**
     * Returns a composed function that first applies the {@code before}
     * function to its input, and then applies this function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of input to the {@code before} function, and to the
     *           composed function
     * @param before the function to apply before this function is applied
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     * @throws NullPointerException if before is null
     *
     * @see #andThen(Function)
     */
    default <V> SqlFunction<V, R> compose(SqlFunction<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     *
     * @see #compose(Function)
     */
    default <V> SqlFunction<T, V> andThen(SqlFunction<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     * Returns a function that always returns its input argument.
     *
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> SqlFunction<T, T> identity() {
        return t -> t;
    }

}
上一篇:Java 8 function包 部分函数介绍


下一篇:lambda表达式