/* * Copyright 2004-2010 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.doma.internal.jdbc.sql; import static org.seasar.doma.internal.util.AssertionUtil.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.seasar.doma.internal.expr.EvaluationResult; import org.seasar.doma.internal.expr.ExpressionEvaluator; import org.seasar.doma.internal.expr.ExpressionException; import org.seasar.doma.internal.expr.ExpressionParser; import org.seasar.doma.internal.expr.Value; import org.seasar.doma.internal.expr.node.ExpressionNode; import org.seasar.doma.internal.jdbc.sql.node.AnonymousNode; import org.seasar.doma.internal.jdbc.sql.node.AnonymousNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.BindVariableNode; import org.seasar.doma.internal.jdbc.sql.node.BindVariableNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ClauseNode; import org.seasar.doma.internal.jdbc.sql.node.CommentNode; import org.seasar.doma.internal.jdbc.sql.node.CommentNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ElseNode; import org.seasar.doma.internal.jdbc.sql.node.ElseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ElseifNode; import org.seasar.doma.internal.jdbc.sql.node.ElseifNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.EmbeddedVariableNode; import org.seasar.doma.internal.jdbc.sql.node.EmbeddedVariableNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.EndNode; import org.seasar.doma.internal.jdbc.sql.node.EndNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.EolNode; import org.seasar.doma.internal.jdbc.sql.node.EolNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ForBlockNode; import org.seasar.doma.internal.jdbc.sql.node.ForBlockNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ForNode; import org.seasar.doma.internal.jdbc.sql.node.ForNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ForUpdateClauseNode; import org.seasar.doma.internal.jdbc.sql.node.ForUpdateClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.FragmentNode; import org.seasar.doma.internal.jdbc.sql.node.FragmentNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.FromClauseNode; import org.seasar.doma.internal.jdbc.sql.node.FromClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.GroupByClauseNode; import org.seasar.doma.internal.jdbc.sql.node.GroupByClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.HavingClauseNode; import org.seasar.doma.internal.jdbc.sql.node.HavingClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.IfBlockNode; import org.seasar.doma.internal.jdbc.sql.node.IfBlockNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.IfNode; import org.seasar.doma.internal.jdbc.sql.node.IfNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.LogicalOperatorNode; import org.seasar.doma.internal.jdbc.sql.node.LogicalOperatorNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.OrderByClauseNode; import org.seasar.doma.internal.jdbc.sql.node.OrderByClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.OtherNode; import org.seasar.doma.internal.jdbc.sql.node.OtherNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.ParensNode; import org.seasar.doma.internal.jdbc.sql.node.ParensNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.SelectClauseNode; import org.seasar.doma.internal.jdbc.sql.node.SelectClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.SelectStatementNode; import org.seasar.doma.internal.jdbc.sql.node.SelectStatementNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.SqlLocation; import org.seasar.doma.internal.jdbc.sql.node.WhereClauseNode; import org.seasar.doma.internal.jdbc.sql.node.WhereClauseNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.WhitespaceNode; import org.seasar.doma.internal.jdbc.sql.node.WhitespaceNodeVisitor; import org.seasar.doma.internal.jdbc.sql.node.WordNode; import org.seasar.doma.internal.jdbc.sql.node.WordNodeVisitor; import org.seasar.doma.internal.util.StringUtil; import org.seasar.doma.internal.wrapper.WrapperException; import org.seasar.doma.internal.wrapper.Wrappers; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.JdbcException; import org.seasar.doma.jdbc.JdbcUnsupportedOperationException; import org.seasar.doma.jdbc.SqlKind; import org.seasar.doma.jdbc.SqlLogFormattingFunction; import org.seasar.doma.jdbc.SqlNode; import org.seasar.doma.jdbc.SqlNodeVisitor; import org.seasar.doma.message.Message; import org.seasar.doma.wrapper.Wrapper; /** * @author taedium * */ public class NodePreparedSqlBuilder implements SqlNodeVisitor, AnonymousNodeVisitor, BindVariableNodeVisitor, CommentNodeVisitor, ElseifNodeVisitor, ElseNodeVisitor, EmbeddedVariableNodeVisitor, EndNodeVisitor, EolNodeVisitor, ForBlockNodeVisitor, ForNodeVisitor, ForUpdateClauseNodeVisitor, FragmentNodeVisitor, FromClauseNodeVisitor, GroupByClauseNodeVisitor, HavingClauseNodeVisitor, IfBlockNodeVisitor, IfNodeVisitor, LogicalOperatorNodeVisitor, OrderByClauseNodeVisitor, OtherNodeVisitor, ParensNodeVisitor, SelectClauseNodeVisitor, SelectStatementNodeVisitor, WhereClauseNodeVisitor, WhitespaceNodeVisitor, WordNodeVisitor { protected static final Pattern clauseKeywordPattern = Pattern.compile( "(select|from|where|group by|having|order by|for update)", Pattern.CASE_INSENSITIVE); protected final Config config; protected final SqlKind kind; protected final String sqlFilePath; protected final ExpressionEvaluator evaluator; public NodePreparedSqlBuilder(Config config, SqlKind kind, String sqlFilePath) { this(config, kind, sqlFilePath, new ExpressionEvaluator(config .getDialect().getExpressionFunctions())); } public NodePreparedSqlBuilder(Config config, SqlKind kind, String sqlFilePath, ExpressionEvaluator evaluator) { assertNotNull(config, kind, evaluator); this.config = config; this.kind = kind; this.sqlFilePath = sqlFilePath; this.evaluator = evaluator; } public PreparedSql build(SqlNode sqlNode) { assertNotNull(sqlNode); Context context = new Context(config, evaluator); sqlNode.accept(this, context); return new PreparedSql(kind, context.getSqlBuf(), context.getFormattedSqlBuf(), sqlFilePath, context.getParameters()); } @Override public Void visitAnonymousNode(AnonymousNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitOtherNode(OtherNode node, Context p) { p.setAvailable(true); String other = node.getOther(); p.appendRawSql(other); p.appendFormattedSql(other); return null; } @Override public Void visitWhitespaceNode(WhitespaceNode node, Context p) { String whitespace = node.getWhitespace(); p.appendRawSql(whitespace); p.appendFormattedSql(whitespace); return null; } @Override public Void visitCommentNode(CommentNode node, Context p) { String comment = node.getComment(); p.appendRawSql(comment); p.appendFormattedSql(comment); return null; } @Override public Void visitBindVariableNode(BindVariableNode node, Context p) { SqlLocation location = node.getLocation(); String name = node.getVariableName(); EvaluationResult result = p.evaluate(location, name); Object value = result.getValue(); Class valueClass = result.getValueClass(); p.setAvailable(true); if (node.isWordNodeIgnored()) { handleSingleBindVarialbeNode(node, p, value, valueClass); } else if (node.isParensNodeIgnored()) { ParensNode parensNode = node.getParensNode(); OtherNode openedFragmentNode = parensNode.getOpenedFragmentNode(); openedFragmentNode.accept(this, p); if (Iterable.class.isAssignableFrom(valueClass)) { handleIterableBindVarialbeNode(node, p, (Iterable) value, valueClass); } else { throw new JdbcException(Message.DOMA2112, location.getSql(), location.getLineNumber(), location.getPosition(), node.getText(), valueClass); } OtherNode closedFragmentNode = parensNode.getClosedFragmentNode(); closedFragmentNode.accept(this, p); } else { assertUnreachable(); } return null; } @Override public Void visitEmbeddedVariableNode(EmbeddedVariableNode node, Context p) { SqlLocation location = node.getLocation(); String name = node.getVariableName(); EvaluationResult result = p.evaluate(location, name); Object value = result.getValue(); if (value != null) { String fragment = value.toString(); if (fragment.indexOf('\'') > -1) { throw new JdbcException(Message.DOMA2116, location.getSql(), location.getLineNumber(), location.getPosition(), node.getText()); } if (fragment.indexOf(';') > -1) { throw new JdbcException(Message.DOMA2117, location.getSql(), location.getLineNumber(), location.getPosition(), node.getText()); } if (fragment.indexOf("--") > -1) { throw new JdbcException(Message.DOMA2122, location.getSql(), location.getLineNumber(), location.getPosition(), node.getText()); } if (fragment.indexOf("/*") > -1) { throw new JdbcException(Message.DOMA2123, location.getSql(), location.getLineNumber(), location.getPosition(), node.getText()); } if (!startsWithClauseKeyword(fragment)) { p.setAvailable(true); } p.appendRawSql(fragment); p.appendFormattedSql(fragment); } for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } protected boolean startsWithClauseKeyword(String fragment) { Matcher matcher = clauseKeywordPattern.matcher(StringUtil .trimWhitespace(fragment)); return matcher.lookingAt(); } protected Void handleSingleBindVarialbeNode(BindVariableNode node, Context p, Object value, Class valueClass) { Wrapper wrapper = wrap(node.getLocation(), node.getText(), value, valueClass); p.addBindValue(wrapper); return null; } protected void handleIterableBindVarialbeNode(BindVariableNode node, Context p, Iterable values, Class valueClass) { int index = 0; for (Object v : values) { if (v == null) { SqlLocation location = node.getLocation(); throw new JdbcException(Message.DOMA2115, location.getSql(), location.getLineNumber(), location.getPosition(), node.getText(), index); } Wrapper wrapper = wrap(node.getLocation(), node.getText(), v, v.getClass()); p.addBindValue(wrapper); p.appendRawSql(", "); p.appendFormattedSql(", "); index++; } if (index > 0) { p.cutBackSqlBuf(2); p.cutBackFormattedSqlBuf(2); } } @Override public Void visitIfBlockNode(IfBlockNode node, Context p) { if (!handleIfNode(node, p)) { if (!handleElseifNode(node, p)) { handleElseNode(node, p); } } EndNode endNode = node.getEndNode(); endNode.accept(this, p); return null; } protected boolean handleIfNode(IfBlockNode node, Context p) { IfNode ifNode = node.getIfNode(); SqlLocation location = ifNode.getLocation(); String expression = ifNode.getExpression(); EvaluationResult ifResult = p.evaluate(location, expression); if (ifResult.getBooleanValue()) { ifNode.accept(this, p); return true; } return false; } protected boolean handleElseifNode(IfBlockNode node, Context p) { for (ElseifNode elseifNode : node.getElseifNodes()) { SqlLocation location = elseifNode.getLocation(); String expression = elseifNode.getExpression(); EvaluationResult elseifResult = p.evaluate(location, expression); if (elseifResult.getBooleanValue()) { elseifNode.accept(this, p); return true; } } return false; } protected void handleElseNode(IfBlockNode node, Context p) { ElseNode elseNode = node.getElseNode(); if (elseNode != null) { elseNode.accept(this, p); } } @Override public Void visitIfNode(IfNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitElseifNode(ElseifNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitElseNode(ElseNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitEndNode(EndNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitForBlockNode(ForBlockNode node, Context p) { ForNode forNode = node.getForNode(); SqlLocation location = forNode.getLocation(); EvaluationResult expressionResult = p.evaluate(location, forNode.getExpression()); Object expressionValue = expressionResult.getValue(); Class expressionValueClass = expressionResult.getValueClass(); if (!Iterable.class.isAssignableFrom(expressionValueClass)) { throw new JdbcException(Message.DOMA2129, location.getSql(), location.getLineNumber(), location.getPosition(), forNode.getExpression(), expressionValueClass); } Iterable iterable = (Iterable) expressionValue; String identifier = forNode.getIdentifier(); Value originalIdentifierValue = p.removeValue(identifier); String hasNextVariable = identifier + ForBlockNode.HAS_NEXT_SUFFIX; Value originalHasNextValue = p.removeValue(hasNextVariable); String indexVariable = identifier + ForBlockNode.INDEX_SUFFIX; Value originalIndexValue = p.removeValue(indexVariable); int index = 0; for (Iterator it = iterable.iterator(); it.hasNext();) { Object each = it.next(); Value value = each == null ? new Value(void.class, null) : new Value(each.getClass(), each); p.putValue(identifier, value); p.putValue(hasNextVariable, new Value(boolean.class, it.hasNext())); p.putValue(indexVariable, new Value(int.class, index)); for (SqlNode child : forNode.getChildren()) { child.accept(this, p); } index++; } if (originalIdentifierValue == null) { p.removeValue(identifier); } else { p.putValue(identifier, originalIdentifierValue); } if (originalHasNextValue == null) { p.removeValue(hasNextVariable); } else { p.putValue(hasNextVariable, originalHasNextValue); } if (originalIndexValue == null) { p.removeValue(indexVariable); } else { p.putValue(indexVariable, originalIndexValue); } EndNode endNode = node.getEndNode(); endNode.accept(this, p); return null; } @Override public Void visitForNode(ForNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitSelectStatementNode(SelectStatementNode node, Context p) { for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitSelectClauseNode(SelectClauseNode node, Context p) { WordNode wordNode = node.getWordNode(); wordNode.accept(this, p); for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitFromClauseNode(FromClauseNode node, Context p) { WordNode wordNode = node.getWordNode(); wordNode.accept(this, p); for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitWhereClauseNode(WhereClauseNode node, Context p) { handleConditionalClauseNode(node, p); return null; } @Override public Void visitGroupByClauseNode(GroupByClauseNode node, Context p) { WordNode wordNode = node.getWordNode(); wordNode.accept(this, p); for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitHavingClauseNode(HavingClauseNode node, Context p) { handleConditionalClauseNode(node, p); return null; } @Override public Void visitOrderByClauseNode(OrderByClauseNode node, Context p) { WordNode wordNode = node.getWordNode(); wordNode.accept(this, p); for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitForUpdateClauseNode(ForUpdateClauseNode node, Context p) { WordNode wordNode = node.getWordNode(); wordNode.accept(this, p); for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } protected void handleConditionalClauseNode(ClauseNode node, Context p) { Context context = new Context(p); for (SqlNode child : node.getChildren()) { child.accept(this, context); } if (context.isAvailable()) { node.getWordNode().accept(this, p); p.setAvailable(true); p.appendRawSql(context.getSqlBuf()); p.appendFormattedSql(context.getFormattedSqlBuf()); p.addAllParameters(context.getParameters()); } else { String fragment = context.getSqlBuf().toString(); if (startsWithClauseKeyword(fragment)) { p.setAvailable(true); p.appendRawSql(context.getSqlBuf()); p.appendFormattedSql(context.getFormattedSqlBuf()); p.addAllParameters(context.getParameters()); } } } @Override public Void visitLogicalOperatorNode(LogicalOperatorNode node, Context p) { if (p.isAvailable()) { WordNode wordNode = node.getWordNode(); wordNode.accept(this, p); } for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitWordNode(WordNode node, Context p) { p.setAvailable(true); String word = node.getWord(); p.appendRawSql(word); p.appendFormattedSql(word); return null; } @Override public Void visitFragmentNode(FragmentNode node, Context p) { p.setAvailable(true); String fragment = node.getFragment(); p.appendRawSql(fragment); p.appendFormattedSql(fragment); return null; } @Override public Void visitParensNode(ParensNode node, Context p) { if (node.isAttachedWithBindVariable()) { return null; } Context context = new Context(p); if (node.isEmpty()) { context.setAvailable(true); } for (SqlNode child : node.getChildren()) { child.accept(this, context); } if (context.isAvailable()) { node.getOpenedFragmentNode().accept(this, p); p.setAvailable(true); p.appendRawSql(context.getSqlBuf()); p.appendFormattedSql(context.getFormattedSqlBuf()); p.addAllParameters(context.getParameters()); node.getClosedFragmentNode().accept(this, p); } return null; } @Override public Void visitEolNode(EolNode node, Context p) { String eol = node.getEol(); p.appendRawSql(eol); p.appendFormattedSql(eol); for (SqlNode child : node.getChildren()) { child.accept(this, p); } return null; } @Override public Void visitUnknownNode(SqlNode node, Context p) { throw new JdbcUnsupportedOperationException(getClass().getName(), "visitUnknownNode"); } protected Wrapper wrap(SqlLocation location, String bindVariableText, Object value, Class valueClass) { try { return Wrappers.wrap(value, valueClass); } catch (WrapperException e) { throw new JdbcException(Message.DOMA2118, e, location.getSql(), location.getLineNumber(), location.getPosition(), bindVariableText, e); } } protected static class Context { private final Config config; private final ExpressionEvaluator evaluator; private final SqlLogFormattingFunction formattingFunction = new ConvertToLogFormatFunction(); private final StringBuilder rawSqlBuf = new StringBuilder(200); private final StringBuilder formattedSqlBuf = new StringBuilder(200); private final List parameters = new ArrayList(); private boolean available; protected Context(Context context) { this(context.config, context.evaluator); } protected Context(Config config, ExpressionEvaluator evaluator) { this.config = config; this.evaluator = evaluator; } protected void appendRawSql(CharSequence sql) { rawSqlBuf.append(sql); } protected void appendFormattedSql(CharSequence sql) { formattedSqlBuf.append(sql); } protected void cutBackSqlBuf(int size) { rawSqlBuf.setLength(rawSqlBuf.length() - size); } protected void cutBackFormattedSqlBuf(int size) { formattedSqlBuf.setLength(formattedSqlBuf.length() - size); } protected CharSequence getSqlBuf() { return rawSqlBuf; } protected CharSequence getFormattedSqlBuf() { return formattedSqlBuf; } protected void addBindValue(Wrapper value) { parameters.add(new BasicInParameter(value)); rawSqlBuf.append("?"); formattedSqlBuf.append(value.accept(config.getDialect() .getSqlLogFormattingVisitor(), formattingFunction)); } protected void addAllParameters(List values) { parameters.addAll(values); } protected List getParameters() { return parameters; } void setAvailable(boolean available) { this.available = available; } boolean isAvailable() { return available; } public void putValue(String variableName, Value value) { evaluator.putValue(variableName, value); } public Value removeValue(String variableName) { return evaluator.removeValue(variableName); } protected EvaluationResult evaluate(SqlLocation location, String expression) { try { ExpressionParser parser = new ExpressionParser(expression); ExpressionNode expressionNode = parser.parse(); return evaluator.evaluate(expressionNode); } catch (ExpressionException e) { throw new JdbcException(Message.DOMA2111, e, location.getSql(), location.getLineNumber(), location.getPosition(), e); } } @Override public String toString() { return rawSqlBuf.toString(); } } }