package com.temenos.interaction.jdbc.producer.sql;
/*
* OData4j uses 'visitors' to convert expressions into Strings. This version converts an expression into a SQL command.
*/
/*
* #%L
* interaction-jdbc-producer
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.sql.Timestamp;
import javax.ws.rs.core.Response.Status;
import org.joda.time.LocalDateTime;
import org.odata4j.expression.AddExpression;
import org.odata4j.expression.AndExpression;
import org.odata4j.expression.BoolParenExpression;
import org.odata4j.expression.CeilingMethodCallExpression;
import org.odata4j.expression.ConcatMethodCallExpression;
import org.odata4j.expression.DateTimeLiteral;
import org.odata4j.expression.DateTimeOffsetLiteral;
import org.odata4j.expression.DayMethodCallExpression;
import org.odata4j.expression.DivExpression;
import org.odata4j.expression.EndsWithMethodCallExpression;
import org.odata4j.expression.EntitySimpleProperty;
import org.odata4j.expression.EqExpression;
import org.odata4j.expression.FloorMethodCallExpression;
import org.odata4j.expression.GeExpression;
import org.odata4j.expression.GtExpression;
import org.odata4j.expression.HourMethodCallExpression;
import org.odata4j.expression.IndexOfMethodCallExpression;
import org.odata4j.expression.IsofExpression;
import org.odata4j.expression.LeExpression;
import org.odata4j.expression.LengthMethodCallExpression;
import org.odata4j.expression.LtExpression;
import org.odata4j.expression.MinuteMethodCallExpression;
import org.odata4j.expression.ModExpression;
import org.odata4j.expression.MonthMethodCallExpression;
import org.odata4j.expression.MulExpression;
import org.odata4j.expression.NeExpression;
import org.odata4j.expression.NotExpression;
import org.odata4j.expression.OrExpression;
import org.odata4j.expression.OrderByExpression;
import org.odata4j.expression.ParenExpression;
import org.odata4j.expression.ReplaceMethodCallExpression;
import org.odata4j.expression.RoundMethodCallExpression;
import org.odata4j.expression.SecondMethodCallExpression;
import org.odata4j.expression.StartsWithMethodCallExpression;
import org.odata4j.expression.StringLiteral;
import org.odata4j.expression.SubExpression;
import org.odata4j.expression.SubstringMethodCallExpression;
import org.odata4j.expression.SubstringOfMethodCallExpression;
import org.odata4j.expression.TimeLiteral;
import org.odata4j.expression.ToLowerMethodCallExpression;
import org.odata4j.expression.ToUpperMethodCallExpression;
import org.odata4j.expression.TrimMethodCallExpression;
import org.odata4j.expression.YearMethodCallExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.temenos.interaction.jdbc.SqlRelation;
import com.temenos.interaction.jdbc.exceptions.JdbcException;
import com.temenos.interaction.odataext.odataparser.odata4j.PrintExpressionVisitor;
public class SQLExpressionVisitor extends PrintExpressionVisitor {
private final static Logger logger = LoggerFactory.getLogger(SQLExpressionVisitor.class);
// Variables handling the SQLExpression tree,
private SQLExpressionNode rootNode = new SQLExpressionNode();
private SQLExpressionNode currentNode = rootNode;
@Override
public String toString() {
// Print out the expression tree
return rootNode.toSqlParameter();
}
/*
* Support for formatted calls from the super class. These append the
* argument as a simple string without formatting. Where local code requires
* formatting use appendFormatted().
*/
protected void append(String format, Object... args) {
append(String.format("%s", args));
}
/*
* Append with format string
*/
protected void appendFormatted(String format, Object... args) {
append(String.format(format, args));
}
protected void append(String str) {
if (!currentNode.addArgument(str)) {
throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Internal error adding:" + str);
}
}
/*
* Append an operator or function.
*/
private void append(SqlRelation rel) {
append(rel, false);
}
/*
* Append an operator of function optionally surrounded by spaces.
*/
private void append(SqlRelation rel, boolean spaced) {
// If there is no SQL equivalent throw.
if (null == rel.getSqlSymbol()) {
String msg = "Unsupported SQL relation \"" + rel + "\".";
logger.error(msg);
throw new UnsupportedOperationException(msg);
}
// Remember if symbol should be surrounded by spaces.
currentNode.setIsSpaced(spaced);
currentNode.setRelation(rel);
}
@Override
public void afterDescend() {
toParent();
}
/*
* Move a level up the expression tree.
*/
private void toParent() {
SQLExpressionNode parentNode = currentNode.getParent();
if (null == parentNode) {
throw new JdbcException(Status.INTERNAL_SERVER_ERROR, "Tried to go above expression tree root.");
}
// Child is complete. Print it as a parent argument.
parentNode.addArgument(currentNode.toSqlParameter());
currentNode = parentNode;
}
@Override
public void beforeDescend() {
// Create a new child node and move to it.
currentNode = new SQLExpressionNode(currentNode);
}
@Override
public void betweenDescend() {
toParent();
// Create next child node and move to it.
currentNode = new SQLExpressionNode(currentNode);
}
@Override
public void visit(OrderByExpression expr) {
// Don't append the 'orderBy' tag.
}
// Double quote column names.
@Override
public void visit(EntitySimpleProperty expr) {
appendFormatted("\"%s\"", expr.getPropertyName());
}
@Override
public void visit(EqExpression expr) {
append(SqlRelation.EQ);
}
@Override
public void visit(NeExpression expr) {
append(SqlRelation.NE);
}
@Override
public void visit(GtExpression expr) {
append(SqlRelation.GT);
}
@Override
public void visit(LtExpression expr) {
append(SqlRelation.LT);
}
@Override
public void visit(GeExpression expr) {
append(SqlRelation.GE);
}
@Override
public void visit(LeExpression expr) {
append(SqlRelation.LE);
}
@Override
public void visit(AndExpression expr) {
append(SqlRelation.AND, true);
}
@Override
public void visit(OrExpression expr) {
append(SqlRelation.OR, true);
}
@Override
public void visit(AddExpression expr) {
append(SqlRelation.ADD);
}
@Override
public void visit(SubExpression expr) {
append(SqlRelation.SUB);
}
@Override
public void visit(MulExpression expr) {
append(SqlRelation.MUL);
}
@Override
public void visit(DivExpression expr) {
append(SqlRelation.DIV);
}
@Override
public void visit(ModExpression expr) {
append(SqlRelation.MOD);
}
@Override
public void visit(NotExpression expr) {
append(SqlRelation.NOT, true);
}
@Override
public void visit(BoolParenExpression expr) {
// Make a note that The current term is bracketed.
currentNode.setIsBracketed();
}
@Override
public void visit(ParenExpression expr) {
// Make a note that The current term is bracketed.
currentNode.setIsBracketed();
}
// Literal strings may contain spaces or dots. So single quote.
@Override
public void visit(StringLiteral expr) {
appendFormatted("'%s'", expr.getValue());
}
@Override
public void visit(SubstringMethodCallExpression expr) {
append(SqlRelation.SUBSTR);
}
@Override
public void visit(SubstringOfMethodCallExpression expr) {
append(SqlRelation.SUBSTROF);
}
@Override
public void visit(ToUpperMethodCallExpression expr) {
append(SqlRelation.TOUPPER);
}
@Override
public void visit(ToLowerMethodCallExpression expr) {
append(SqlRelation.TOLOWER);
}
@Override
public void visit(ReplaceMethodCallExpression expr) {
append(SqlRelation.REPLACE);
}
@Override
public void visit(LengthMethodCallExpression expr) {
append(SqlRelation.LENGTH);
}
@Override
public void visit(TrimMethodCallExpression expr) {
append(SqlRelation.TRIM);
}
@Override
public void visit(YearMethodCallExpression expr) {
append(SqlRelation.YEAR);
}
@Override
public void visit(MonthMethodCallExpression expr) {
append(SqlRelation.MONTH);
}
@Override
public void visit(DayMethodCallExpression expr) {
append(SqlRelation.DAY);
}
@Override
public void visit(HourMethodCallExpression expr) {
append(SqlRelation.HOUR);
}
@Override
public void visit(MinuteMethodCallExpression expr) {
append(SqlRelation.MINUTE);
}
@Override
public void visit(SecondMethodCallExpression expr) {
append(SqlRelation.SECOND);
}
@Override
public void visit(RoundMethodCallExpression expr) {
append(SqlRelation.ROUND);
}
@Override
public void visit(FloorMethodCallExpression expr) {
append(SqlRelation.FLOOR);
}
@Override
public void visit(CeilingMethodCallExpression expr) {
append(SqlRelation.CEILING);
}
@Override
public void visit(IsofExpression expr) {
throw new UnsupportedOperationException();
}
@Override
public void visit(EndsWithMethodCallExpression expr) {
append(SqlRelation.ENDSWITH);
}
@Override
public void visit(StartsWithMethodCallExpression expr) {
append(SqlRelation.STARTSWITH);
}
@Override
public void visit(IndexOfMethodCallExpression expr) {
append(SqlRelation.INDEXOF);
}
@Override
public void visit(ConcatMethodCallExpression expr) {
append(SqlRelation.CONCAT);
}
@Override
public void visit(DateTimeLiteral expr) {
// Get the joda representation
LocalDateTime jodaTime = expr.getValue();
// Convert it to SQL representation
Timestamp timeStamp = new Timestamp(jodaTime.toDateTime().getMillis());
// Print it out.
String timeStampStr = timeStamp.toString();
appendFormatted("'%s'", timeStampStr);
}
@Override
public void visit(DateTimeOffsetLiteral expr) {
throw new UnsupportedOperationException("DateTimeOffsetLiteral not supported.");
}
@Override
public void visit(TimeLiteral expr) {
throw new UnsupportedOperationException("TimeLiteral not supported.");
}
}