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 param;
private final List<ConstantExpression> PARAMS = new ArrayList<>();
private final 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 -> PARAMS.add((ConstantExpression) x));
}
if (target.getExpressionType() == ExpressionType.MethodAccess) {
e.getArguments().stream().findFirst().ifPresent(x -> { param = 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) {
param.accept(this);
return invoke(member, instance);
}
if (instance instanceof InvocationExpression) {
((InvocationExpression) instance).getTarget().accept(this);
return invoke(member, param);
}
return SQL;
}
@Override
public SqlWrapper visit(ParameterExpression e) {
PARAMS.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);
}
public void clear() {
SQL.clear();
}
// --------------------------------------------------------------------------------------------
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);
return SQL;
case "startsWith"/* like '{0}%' */:
SQL.getKey().append(" like ").append("concat(");
expression.accept(this);
SQL.getKey().append(",'%')");
return SQL;
case "endsWith"/* like '%{0}' */:
SQL.getKey().append(" like ").append("concat(").append("'%',");
expression.accept(this);
SQL.getKey().append(")");
return SQL;
case "contains"/* like '%{0}%' */:
SQL.getKey().append(" like ").append("concat(").append("'%',");
expression.accept(this);
SQL.getKey().append(",'%')");
return SQL;
default:
return doMethodOp(member, expression);
}
}
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);
return SQL;
case "after"/* > '{0}' */:
case "isAfter":
SQL.getKey().append(" > ");
expression.accept(this);
return SQL;
case "before"/* < '{0}' */:
case "isBefore":
SQL.getKey().append(" < ");
expression.accept(this);
return SQL;
default:
return doMethodOp(member, expression);
}
}
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() {
this.sql = new StringBuilder();
this.params = new ArrayList<>();
}
@Override
public synchronized StringBuilder getKey() {
return this.sql;
}
public synchronized StringBuilder addSql(CharSequence input) {
this.sql.append(input);
return this.sql;
}
@Override
public synchronized List<Object> getValue() {
return this.params;
}
public synchronized List<Object> addParams(Object o) {
this.params.add(o);
return this.params;
}
@Override
public List<Object> setValue(List<Object> value) {
throw new UnsupportedOperationException();
}
public synchronized void clear() {
this.sql.setLength(0);
this.params.clear();
}
@Override
public String toString() {
return this.sql.toString() + System.lineSeparator() + this.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;
}
}
调用
import java.time.LocalDate;
import java.util.Date;
import lombok.Data;
import xyz.xkind.core.sql.lambda.SqlPredicate;
import xyz.xkind.core.sql.lambda.SqlVisitor;
public class TestLambdaToSql {
@Data
class Person {
private String name;
private int age;
private LocalDate create;
private Date login;
}
public static void main(String[] args) {
final LocalDate ld = LocalDate.of(1970, 1, 1);
final Date date = new Date();
SqlPredicate<Person> sp_0 = p -> p.getName().equals("root");
SqlPredicate<Person> sp_1 = p -> p.getName().startsWith("ro") || p.getName().endsWith("ot");
SqlPredicate<Person> sp_2 = p -> p.getName().contains("root");
SqlVisitor visitor = new SqlVisitor("`", "`");
System.out.println(sp_0.sql(visitor) + System.lineSeparator());
visitor.clear();
System.out.println(sp_1.sql(visitor) + System.lineSeparator());
visitor.clear();
System.out.println(sp_2.sql(visitor) + System.lineSeparator());
SqlPredicate<Person> sp_3 = p -> p.getAge() > 18 && p.getLogin().before(date)
&& p.getCreate().isAfter(ld);
System.out.println(sp_3.sql() + System.lineSeparator());
}
}