DebugVisitor.java
package com.github.mygreen.sqlmapper.metamodel.support;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.mygreen.sqlmapper.metamodel.OrderSpecifier;
import com.github.mygreen.sqlmapper.metamodel.Path;
import com.github.mygreen.sqlmapper.metamodel.PathMeta;
import com.github.mygreen.sqlmapper.metamodel.PathType;
import com.github.mygreen.sqlmapper.metamodel.PropertyPath;
import com.github.mygreen.sqlmapper.metamodel.Visitor;
import com.github.mygreen.sqlmapper.metamodel.expression.Constant;
import com.github.mygreen.sqlmapper.metamodel.expression.Expression;
import com.github.mygreen.sqlmapper.metamodel.expression.SubQueryExpression;
import com.github.mygreen.sqlmapper.metamodel.operation.CustomFunctionOperation;
import com.github.mygreen.sqlmapper.metamodel.operation.Operation;
import com.github.mygreen.sqlmapper.metamodel.operation.SubQueryMeta;
import com.github.mygreen.sqlmapper.metamodel.operator.ArithmeticOp;
import com.github.mygreen.sqlmapper.metamodel.operator.BooleanOp;
import com.github.mygreen.sqlmapper.metamodel.operator.ComparisionOp;
import com.github.mygreen.sqlmapper.metamodel.operator.FunctionOp;
import com.github.mygreen.sqlmapper.metamodel.operator.LikeOp;
import com.github.mygreen.sqlmapper.metamodel.operator.Operator;
import com.github.mygreen.sqlmapper.metamodel.operator.UnaryOp;
import com.github.mygreen.sqlmapper.metamodel.support.SqlFunctionParser.Token;
import com.github.mygreen.sqlmapper.metamodel.support.SqlFunctionTokenizer.TokenType;
/**
* 式を文字列として評価するためのデバッグ用のVisitor。
*
*
* @author T.TSUCHIE
*
*/
public class DebugVisitor implements Visitor<DebugVisitorContext>{
private static final DateTimeFormatter FORMATTER_LOCAL_DATETIME = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
private static final DateTimeFormatter FORMATTER_LOCAL_DATE = DateTimeFormatter.ofPattern("uuuu-MM-dd");
private static final DateTimeFormatter FORMATTER_LOCAL_TIME = DateTimeFormatter.ofPattern("HH:mm:ss");
/**
* 演算子に対する式テンプレートのマップ
*/
private Map<Operator, String> operationTemplateMap = new HashMap<>();
public DebugVisitor() {
initOperationTemplate();
}
protected void initOperationTemplate() {
// BooleanOp
operationTemplateMap.put(BooleanOp.AND, "{0} and {1}");
operationTemplateMap.put(BooleanOp.OR, "{0} or {1}");
// UnaryOp
operationTemplateMap.put(UnaryOp.NOT, "not {0}");
operationTemplateMap.put(UnaryOp.IS_NULL, "{0} is null");
operationTemplateMap.put(UnaryOp.IS_NOT_NULL, "{0} is not null");
operationTemplateMap.put(UnaryOp.EXISTS, "exists {0}");
operationTemplateMap.put(UnaryOp.NOT_EXISTS, "not exists {0}");
// ComparisionOp
operationTemplateMap.put(ComparisionOp.EQ, "{0} = {1}");
operationTemplateMap.put(ComparisionOp.NE, "{0} <> {1}");
operationTemplateMap.put(ComparisionOp.IN, "{0} in {1}");
operationTemplateMap.put(ComparisionOp.NOT_IN, "{0} not in {1}");
operationTemplateMap.put(ComparisionOp.BETWEEN, "{0} between {1} and {2}");
operationTemplateMap.put(ComparisionOp.GOE, "{0} >= {1}");
operationTemplateMap.put(ComparisionOp.GT, "{0} > {1}");
operationTemplateMap.put(ComparisionOp.LOE, "{0} <= {1}");
operationTemplateMap.put(ComparisionOp.LT, "{0} < {1}");
// ArithmeticOp
operationTemplateMap.put(ArithmeticOp.MULT, "{0} * {1}");
operationTemplateMap.put(ArithmeticOp.DIV, "{0} / {1}");
operationTemplateMap.put(ArithmeticOp.MOD, "mod({0}, {1})");
operationTemplateMap.put(ArithmeticOp.ADD, "{0} + {1}");
operationTemplateMap.put(ArithmeticOp.SUB, "{0} - {1}");
// FunctionOp
operationTemplateMap.put(FunctionOp.LOWER, "lower({0})");
operationTemplateMap.put(FunctionOp.UPPER, "upper({0})");
operationTemplateMap.put(FunctionOp.CONCAT, "concat({0}, {1})");
// operationTemplateMap.put(FunctionOp.CURRENT_DATE, "current_date");
// operationTemplateMap.put(FunctionOp.CURRENT_TIME, "current_time");
// operationTemplateMap.put(FunctionOp.CURRENT_TIMESTAMP, "current_timestamp");
// operationTemplateMap.put(FunctionOp.CUSTOM, "custom()");
// LikeOp
operationTemplateMap.put(LikeOp.LIKE, "{0} like {1}");
// 関数として扱う
operationTemplateMap.put(LikeOp.CONTAINS, "contains({0}, {1})");
operationTemplateMap.put(LikeOp.STARTS, "starts({0}, {1})");
operationTemplateMap.put(LikeOp.ENDS, "ends({0}, {1})");
}
@Override
public void visit(final Operation<?> expr, final DebugVisitorContext context) {
final Operator operator = expr.getOperator();
final String template = operationTemplateMap.get(operator);
if(template != null) {
// 引数を評価します。
List<Object> evals = new ArrayList<>();
for(Expression<?> arg : expr.getArgs()) {
DebugVisitorContext argContext = new DebugVisitorContext();
invoke(operator, arg, argContext);
evals.add(argContext.getCriteria());
}
String formatExpr = MessageFormat.format(template, evals.toArray(new Object[evals.size()]));
context.append(formatExpr);
} else if(operator == FunctionOp.CURRENT_DATE
|| operator == FunctionOp.CURRENT_TIME
|| operator == FunctionOp.CURRENT_TIMESTAMP) {
context.append(operator.name().toLowerCase());
// 精度が存在するとき評価する
if(!expr.getArgs().isEmpty()) {
context.append("(");
invoke(operator, expr.getArg(0), context);
context.append(")");
}
} else if(operator == FunctionOp.CUSTOM) {
// 左辺の評価
Expression<?> left = expr.getArg(0);
DebugVisitorContext leftContext = new DebugVisitorContext();
invoke(operator, left, leftContext);
// トークンを置換していく
CustomFunctionOperation op = (CustomFunctionOperation) expr.getArg(1);
@SuppressWarnings("unchecked")
List<Token> tokens = op.getTokens();
for(Token token : tokens) {
if(token.type == TokenType.SQL) {
context.append(token.value);
} else if(token.type == TokenType.LEFT_VARIABLE) {
context.append(leftContext.getCriteria());
} else if(token.type == TokenType.BIND_VARIABLE) {
int varIndex = token.bindBariableIndex;
Expression<?> arg = op.getArg(varIndex);
DebugVisitorContext argContext = new DebugVisitorContext();
invoke(operator, arg, argContext);
context.append(argContext.getCriteria());
} else {
// unknown token
}
}
}
}
@Override
public void visit(final Constant<?> expr, final DebugVisitorContext context) {
final Object constant = expr.getValue();
if(constant == null) {
context.append("null");
} else if(expr.isExpandable()) {
context.append("(");
StringBuilder joined = new StringBuilder();
for(Object value : (Collection<?>)constant) {
if(joined.length() > 0) {
joined.append(", ");
}
joined.append(formatConstantValue(value));
}
context.append(joined.toString())
.append(")");
} else {
context.append(formatConstantValue(constant));
}
}
/**
* 定数の値をフォーマットします。
* @param value フォーマット対象の値
* @return フォーマットした値
*/
private String formatConstantValue(final Object value) {
if(value == null) {
return "null";
}
if(value instanceof CharSequence) {
return "'" + value.toString() + "'";
} else if(value instanceof Date) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value);
} else if(value instanceof java.sql.Date) {
return new SimpleDateFormat("yyyy-MM-dd").format(value);
} else if(value instanceof Time) {
return new SimpleDateFormat("HH:mm:ss").format(value);
} else if(value instanceof Timestamp) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(value);
} else if(value instanceof LocalDateTime) {
return FORMATTER_LOCAL_DATETIME.format((LocalDateTime)value);
} else if(value instanceof LocalDate) {
return FORMATTER_LOCAL_DATE.format((LocalDate)value);
} else if(value instanceof LocalTime) {
return FORMATTER_LOCAL_TIME.format((LocalTime)value);
}
return value.toString();
}
@Override
public void visit(final Path<?> expr, final DebugVisitorContext context) {
PathMeta pathMeta = expr.getPathMeta();
String pathName = pathMeta.getElement();
Path<?> parent = null;
// 親をたどりパス名を追加していく。
// <親のパス>.<子のパス>
do {
parent = pathMeta.getParent();
if(parent == null) {
break;
}
pathMeta = parent.getPathMeta();
// 親の名前を先頭に追加していく。
pathName = pathMeta.getElement() + "." + pathName;
} while(parent.getPathMeta().getType() != PathType.ROOT);
context.append(pathName);
}
@Override
public void visit(final SubQueryExpression<?> expr, final DebugVisitorContext context) {
final SubQueryMeta queryMeta = expr.getQueryMeta();
context.append("select");
// 抽出カラムの組み立て
if(!queryMeta.getIncludesProperties().isEmpty()) {
StringBuilder columnQuery = new StringBuilder();
for(PropertyPath<?> propertyPath : queryMeta.getIncludesProperties()) {
if(columnQuery.length() > 0) {
columnQuery.append(", ");
}
// プロパティのパスの評価
DebugVisitorContext propertyPathContext = new DebugVisitorContext();
propertyPath.accept(this, propertyPathContext);
columnQuery.append(propertyPathContext.getCriteria());
}
context.append(columnQuery.toString());
} else {
context.append(" *");
}
// from句の組み立て
context.append(" from ").append(queryMeta.getEntityPath().getPathMeta().getElement());
// where句の組み立て
if(queryMeta.getWhere() != null) {
// 条件式の評価
DebugVisitorContext whereContext = new DebugVisitorContext();
queryMeta.getWhere().accept(this, whereContext);
context.append(" whrere ")
.append(whereContext.getCriteria());
}
// 並び順の組み立て
if(!queryMeta.getOrders().isEmpty()) {
StringBuilder orderQuery = new StringBuilder();
for(OrderSpecifier order : queryMeta.getOrders()) {
if(orderQuery.length() > 0) {
orderQuery.append(", ");
}
// 並び順のパスを評価する
DebugVisitorContext orderContext = new DebugVisitorContext();
order.getPath().accept(this, orderContext);
orderQuery.append(orderContext.getCriteria())
.append(" ")
.append(order.getOrder().name());
}
context.append(" order by ")
.append(orderQuery.toString());
}
// limit句の組み立て
if(queryMeta.getLimit() > 0 || queryMeta.getLimit() == 0 && queryMeta.getOffset() > 0) {
if (queryMeta.getOffset() > 0) {
context.append(" limit ")
.append(queryMeta.getLimit())
.append(" offset ")
.append(queryMeta.getOffset());
} else {
context.append(" limit ")
.append(queryMeta.getLimit());
}
}
}
protected void invoke(final Operator parentOperator, final Expression<?> expr, final DebugVisitorContext context) {
if(expr instanceof Operation) {
Operation<?> operation = (Operation<?>)expr;
// /子ノードが演算子の場合、括弧で囲むか判定する。
if(OperationUtils.isEnclosedParenthesis(parentOperator, operation.getOperator())) {
context.append("(");
visit(operation, context);
context.append(")");
} else {
visit(operation, context);
}
} else if(expr instanceof Constant) {
visit((Constant<?>)expr, context);
} else if (expr instanceof Path) {
visit((Path<?>)expr, context);
} else if(expr instanceof SubQueryExpression) {
context.append("(");
visit((SubQueryExpression<?>)expr, context);
context.append(")");
} else {
throw new IllegalArgumentException("not support Expression instance of " + expr.getClass());
}
}
}