/* * This file is part of jdbc4sparql jsqlparser implementation. * * jdbc4sparql jsqlparser implementation is free software: you can redistribute * it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * jdbc4sparql jsqlparser implementation 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with jdbc4sparql jsqlparser implementation. If not, see * <http://www.gnu.org/licenses/>. */ package org.xenei.jdbc4sparql.sparql.parser.jsqlparser; import java.sql.SQLException; import java.util.HashSet; import java.util.Set; import java.util.Stack; import net.sf.jsqlparser.expression.AllComparisonExpression; import net.sf.jsqlparser.expression.AnyComparisonExpression; import net.sf.jsqlparser.expression.BinaryExpression; import net.sf.jsqlparser.expression.CaseExpression; import net.sf.jsqlparser.expression.DateValue; import net.sf.jsqlparser.expression.DoubleValue; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.Function; import net.sf.jsqlparser.expression.InverseExpression; import net.sf.jsqlparser.expression.JdbcParameter; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.NullValue; import net.sf.jsqlparser.expression.Parenthesis; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.TimeValue; import net.sf.jsqlparser.expression.TimestampValue; import net.sf.jsqlparser.expression.WhenClause; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseOr; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseXor; import net.sf.jsqlparser.expression.operators.arithmetic.Concat; import net.sf.jsqlparser.expression.operators.arithmetic.Division; import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication; import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.Between; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; import net.sf.jsqlparser.expression.operators.relational.GreaterThan; import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; import net.sf.jsqlparser.expression.operators.relational.InExpression; import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; import net.sf.jsqlparser.expression.operators.relational.LikeExpression; import net.sf.jsqlparser.expression.operators.relational.Matches; import net.sf.jsqlparser.expression.operators.relational.MinorThan; import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals; import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.select.SubSelect; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xenei.jdbc4sparql.iface.name.ColumnName; import org.xenei.jdbc4sparql.iface.name.NameSegments; import org.xenei.jdbc4sparql.impl.virtual.VirtualSchema; import org.xenei.jdbc4sparql.impl.virtual.VirtualTable; import org.xenei.jdbc4sparql.sparql.SparqlQueryBuilder; import org.xenei.jdbc4sparql.sparql.items.QueryColumnInfo; import org.xenei.jdbc4sparql.sparql.items.QueryTableInfo; import org.xenei.jdbc4sparql.sparql.parser.jsqlparser.proxies.ExprInfoFactory; import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; import com.hp.hpl.jena.graph.Node; import com.hp.hpl.jena.graph.NodeFactory; import com.hp.hpl.jena.sparql.expr.E_Add; import com.hp.hpl.jena.sparql.expr.E_Bound; import com.hp.hpl.jena.sparql.expr.E_Divide; import com.hp.hpl.jena.sparql.expr.E_Equals; import com.hp.hpl.jena.sparql.expr.E_GreaterThan; import com.hp.hpl.jena.sparql.expr.E_GreaterThanOrEqual; import com.hp.hpl.jena.sparql.expr.E_LessThan; import com.hp.hpl.jena.sparql.expr.E_LessThanOrEqual; import com.hp.hpl.jena.sparql.expr.E_LogicalAnd; import com.hp.hpl.jena.sparql.expr.E_LogicalNot; import com.hp.hpl.jena.sparql.expr.E_LogicalOr; import com.hp.hpl.jena.sparql.expr.E_Multiply; import com.hp.hpl.jena.sparql.expr.E_NotEquals; import com.hp.hpl.jena.sparql.expr.E_OneOf; import com.hp.hpl.jena.sparql.expr.E_Regex; import com.hp.hpl.jena.sparql.expr.E_Subtract; import com.hp.hpl.jena.sparql.expr.Expr; import com.hp.hpl.jena.sparql.expr.ExprFunction; import com.hp.hpl.jena.sparql.expr.ExprVar; import com.hp.hpl.jena.sparql.expr.nodevalue.NodeValueDT; import com.hp.hpl.jena.sparql.expr.nodevalue.NodeValueDouble; import com.hp.hpl.jena.sparql.expr.nodevalue.NodeValueInteger; import com.hp.hpl.jena.sparql.expr.nodevalue.NodeValueString; /** * An expression visitor. Merges SQL expressions into the SparqlQueryBuilder. */ public class SparqlExprVisitor implements ExpressionVisitor { // the query builder private final SparqlQueryBuilder builder; // A stack of expression elements. private final Stack<Expr> stack; private final Set<ExprColumn> columns; private final boolean optionalColumns; private final AliasInfo alias; private static Logger LOG = LoggerFactory .getLogger(SparqlExprVisitor.class); /** * Constructor * * @param builder * The SparqlQueryBuilder to use. */ public SparqlExprVisitor(final SparqlQueryBuilder builder, final boolean optionalColumns, final boolean aliasRequired, final String alias) { this.builder = builder; this.optionalColumns = optionalColumns; this.alias = new AliasInfo(alias, aliasRequired); stack = new Stack<Expr>(); columns = new HashSet<ExprColumn>(); } /** * Constructor * * @param builder * The SparqlQueryBuilder to use. */ public SparqlExprVisitor(final SparqlQueryBuilder builder, final boolean optionalColumns, final boolean aliasRequired) { this(builder, optionalColumns, aliasRequired, null); } public Set<ExprColumn> getColumns() { return columns; } /** * Get the final result of the process. * * @return */ public Expr getResult() { return stack.pop(); } /** * * @return True if the stack is empty (no result). */ public boolean isEmpty() { return stack.isEmpty(); } private void logVisit(final String part, final Expression name) { final String aliasLog = alias == null ? "" : String.format(" as %s", alias); SparqlExprVisitor.LOG.debug("visit {}: {}{}", part, name, aliasLog); } // process a binary expression. private void process(final BinaryExpression biExpr) { // put on in reverse order so they can be popped back off in the proper // order. biExpr.getRightExpression().accept(this); biExpr.getLeftExpression().accept(this); } private AliasInfo getAlias() { if (alias.isUsed()) { return alias; } // must copy default alias not calculated alias. final AliasInfo retval = new AliasInfo(alias.alias, alias.isAliasRequired()); alias.setUsed(true); alias.aliasRequired = false; return retval; } private Expr processAlias(final Expr expr, final AliasInfo alias) { if (alias.isAliasRequired()) { final ColumnName cName = new ColumnName(builder.getCatalogName(), VirtualSchema.NAME, VirtualTable.NAME, alias.getAlias()); return ExprInfoFactory.getInstance(expr, columns, cName); } return expr; } @Override public void visit(final Addition addition) { if (LOG.isDebugEnabled()) { logVisit("Addition", addition); } final AliasInfo exprAlias = getAlias(); process(addition); stack.push(processAlias(new E_Add(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final AllComparisonExpression allComparisonExpression) { throw new UnsupportedOperationException("ALL is not supported"); } @Override public void visit(final AndExpression andExpression) { if (LOG.isDebugEnabled()) { logVisit("And", andExpression); } final AliasInfo exprAlias = getAlias(); process(andExpression); stack.push(processAlias(new E_LogicalAnd(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final AnyComparisonExpression anyComparisonExpression) { throw new UnsupportedOperationException("ANY is not supported"); } @Override public void visit(final Between between) { if (LOG.isDebugEnabled()) { logVisit("Between", between); } final AliasInfo exprAlias = getAlias(); between.getBetweenExpressionEnd().accept(this); between.getBetweenExpressionStart().accept(this); between.getLeftExpression().accept(this); // rewrite as x <= a >= y final Expr a = stack.pop(); final Expr left = new E_LessThanOrEqual(stack.pop(), a); final Expr right = new E_GreaterThanOrEqual(a, stack.pop()); stack.push(processAlias(new E_LogicalAnd(left, right), exprAlias)); } @Override public void visit(final BitwiseAnd bitwiseAnd) { throw new UnsupportedOperationException("'&' is not supported"); } @Override public void visit(final BitwiseOr bitwiseOr) { throw new UnsupportedOperationException("'|' is not supported"); } @Override public void visit(final BitwiseXor bitwiseXor) { throw new UnsupportedOperationException("'^' is not supported"); } @Override public void visit(final CaseExpression caseExpression) { throw new UnsupportedOperationException("CASE is not supported"); } @Override public void visit(final Column tableColumn) { if (LOG.isDebugEnabled()) { logVisit("Column", tableColumn); } final String schema = StringUtils.defaultString(tableColumn.getTable() .getSchemaName(), builder.getDefaultSchemaName()); final String table = StringUtils.defaultString(tableColumn.getTable() .getName(), builder.getDefaultTableName()); QueryColumnInfo columnInfo = null; QueryTableInfo tableInfo = null; if ((table == null) || (schema == null)) { final ColumnName cName = new ColumnName(builder.getCatalogName(), StringUtils.defaultString(schema), StringUtils.defaultString(table), tableColumn.getColumnName()); // find the column by name -- must only be one or it throws an // exception. columnInfo = builder.getColumn(cName.clone(NameSegments.FFFT)); } else { // column name will not be in infoSet yet if we are processing a // join. final ColumnName cName = new ColumnName(builder.getCatalogName(), schema, table, tableColumn.getColumnName()); try { columnInfo = builder.addColumn(cName, optionalColumns); } catch (final SQLException e) { throw new IllegalStateException(e.getMessage(), e); } } tableInfo = builder.getTable(columnInfo.getName().getTableName()); final AliasInfo exprAlias = getAlias(); if (exprAlias.isAliasRequired() && !exprAlias.isUsed()) { final ColumnName aliasName = ColumnName.getNameInstance( columnInfo.getName(), exprAlias.getAlias()); columnInfo = tableInfo.addColumnToQuery(columnInfo.getColumn(), aliasName, optionalColumns); } else { columnInfo = tableInfo.addColumnToQuery(columnInfo.getColumn(), optionalColumns); } final ExprColumn column = new ExprColumn(columnInfo); columns.add(column); stack.push(column); } @Override public void visit(final Concat concat) { throw new UnsupportedOperationException("CONCAT is not supported"); } @Override public void visit(final DateValue dateValue) { if (LOG.isDebugEnabled()) { logVisit("DateValue", dateValue); } final String val = dateValue.getValue().toString(); final Node n = NodeFactory.createLiteral(val, XSDDatatype.XSDdate); stack.push(processAlias(new NodeValueDT(val, n), getAlias())); } @Override public void visit(final Division division) { if (LOG.isDebugEnabled()) { logVisit("Division", division); } final AliasInfo exprAlias = getAlias(); process(division); stack.push(processAlias(new E_Divide(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final DoubleValue doubleValue) { if (LOG.isDebugEnabled()) { logVisit("DoubleValue", doubleValue); } stack.push(processAlias(new NodeValueDouble(doubleValue.getValue()), getAlias())); } @Override public void visit(final EqualsTo equalsTo) { if (LOG.isDebugEnabled()) { logVisit("EqualsTo", equalsTo); } final AliasInfo exprAlias = getAlias(); process(equalsTo); stack.push(processAlias(new E_Equals(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final ExistsExpression existsExpression) { throw new UnsupportedOperationException("EXISTS is not supported"); } @Override public void visit(final Function function) { if (LOG.isDebugEnabled()) { logVisit("Function", function); } final StandardFunctionHandler sfh = new StandardFunctionHandler(builder); try { final Expr exprInfo = sfh.handle(function, getAlias()); stack.push(exprInfo); } catch (final SQLException e) { throw new UnsupportedOperationException(String.format( "function %s is not supported (%s)", function.getName(), e.getMessage())); } } @Override public void visit(final GreaterThan greaterThan) { if (LOG.isDebugEnabled()) { logVisit("GreaterThan", greaterThan); } final AliasInfo exprAlias = getAlias(); process(greaterThan); stack.push(processAlias(new E_GreaterThan(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final GreaterThanEquals greaterThanEquals) { if (LOG.isDebugEnabled()) { logVisit("GreaterThanEquals", greaterThanEquals); } final AliasInfo exprAlias = getAlias(); process(greaterThanEquals); stack.push(processAlias( new E_GreaterThanOrEqual(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final InExpression inExpression) { if (LOG.isDebugEnabled()) { logVisit("InExpression", inExpression); } final AliasInfo exprAlias = getAlias(); final SparqlItemsListVisitor listVisitor = new SparqlItemsListVisitor( builder); inExpression.getItemsList().accept(listVisitor); inExpression.getLeftExpression().accept(this); stack.push(processAlias( new E_OneOf(stack.pop(), listVisitor.getResult()), exprAlias)); } @Override public void visit(final InverseExpression inverseExpression) { throw new UnsupportedOperationException( "inverse expressions are not supported"); } @Override public void visit(final IsNullExpression isNullExpression) { if (LOG.isDebugEnabled()) { logVisit("isNull", isNullExpression); } final AliasInfo exprAlias = getAlias(); isNullExpression.getLeftExpression().accept(this); ExprFunction func = null; final Expr expr = stack.pop(); if (expr instanceof ExprColumn) { func = new E_Bound(new ExprVar(((ExprColumn) expr).getColumnInfo() .getGUIDVar())); } else { func = new E_Bound(expr); } // SQL has is NULL = T when empty // SPARQL has bound()= F when empty // so this NOT check looks backward at first. if (!isNullExpression.isNot()) { func = new E_LogicalNot(func); } stack.push(processAlias(func, exprAlias)); } @Override public void visit(final JdbcParameter jdbcParameter) { throw new UnsupportedOperationException( "JDBC Parameters are not supported"); } @Override public void visit(final LikeExpression likeExpression) { if (LOG.isDebugEnabled()) { logVisit("LikeExpression", likeExpression); } final AliasInfo exprAlias = getAlias(); process(likeExpression); final Expr left = stack.pop(); final Expr right = stack.pop(); if (right instanceof NodeValueString) { final RegexNodeValue rnv = RegexNodeValue .create(((NodeValueString) right).getString()); if (rnv.isWildcard()) { stack.push(processAlias(new E_Regex(left, rnv, new NodeValueString("")), exprAlias)); } else { stack.push(processAlias(new E_Equals(left, rnv), exprAlias)); } } else { throw new UnsupportedOperationException( "LIKE must have string for right hand argument"); } } @Override public void visit(final LongValue longValue) { if (LOG.isDebugEnabled()) { logVisit("Long", longValue); } stack.push(processAlias(new NodeValueInteger(longValue.getValue()), getAlias())); } @Override public void visit(final Matches matches) { throw new UnsupportedOperationException("MATCHES is not supported"); } @Override public void visit(final MinorThan minorThan) { if (LOG.isDebugEnabled()) { logVisit("MinorThan", minorThan); } final AliasInfo exprAlias = getAlias(); process(minorThan); stack.push(processAlias(new E_LessThan(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final MinorThanEquals minorThanEquals) { if (LOG.isDebugEnabled()) { logVisit("MinorThanEquals", minorThanEquals); } final AliasInfo exprAlias = getAlias(); process(minorThanEquals); stack.push(processAlias( new E_LessThanOrEqual(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final Multiplication multiplication) { if (LOG.isDebugEnabled()) { logVisit("Multiplication", multiplication); } final AliasInfo exprAlias = getAlias(); process(multiplication); stack.push(processAlias(new E_Multiply(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final NotEqualsTo notEqualsTo) { if (LOG.isDebugEnabled()) { logVisit("Not Equals", notEqualsTo); } final AliasInfo exprAlias = getAlias(); process(notEqualsTo); stack.push(processAlias(new E_NotEquals(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final NullValue nullValue) { if (LOG.isDebugEnabled()) { logVisit("Null Value", nullValue); } throw new UnsupportedOperationException( "Figure out how to process NULL"); } @Override public void visit(final OrExpression orExpression) { if (LOG.isDebugEnabled()) { logVisit("Or Expression", orExpression); } final AliasInfo exprAlias = getAlias(); process(orExpression); stack.push(processAlias(new E_LogicalOr(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final Parenthesis parenthesis) { if (LOG.isDebugEnabled()) { logVisit("Parenthesis", parenthesis); } parenthesis.getExpression().accept(this); } @Override public void visit(final StringValue stringValue) { if (LOG.isDebugEnabled()) { logVisit("String Value", stringValue); } stack.push(processAlias(new NodeValueString(stringValue.getValue()), getAlias())); } @Override public void visit(final SubSelect subSelect) { throw new UnsupportedOperationException("SUB SELECT is not supported"); } @Override public void visit(final Subtraction subtraction) { if (LOG.isDebugEnabled()) { logVisit("Subtraction", subtraction); } final AliasInfo exprAlias = getAlias(); process(subtraction); stack.push(processAlias(new E_Subtract(stack.pop(), stack.pop()), exprAlias)); } @Override public void visit(final TimestampValue timestampValue) { if (LOG.isDebugEnabled()) { logVisit("Timestamp", timestampValue); } final String parts[] = timestampValue.getValue().toString().split(" "); final String val = String.format("%sT%s", parts[0], parts[1]); final Node n = NodeFactory.createLiteral(val, XSDDatatype.XSDdateTime); stack.push(processAlias(new NodeValueDT(val, n), getAlias())); } @Override public void visit(final TimeValue timeValue) { if (LOG.isDebugEnabled()) { logVisit("TimeValue", timeValue); } final String val = timeValue.getValue().toString(); final Node n = NodeFactory.createLiteral(val, XSDDatatype.XSDtime); stack.push(processAlias(new NodeValueDT(val, n), getAlias())); } @Override public void visit(final WhenClause whenClause) { throw new UnsupportedOperationException("WHEN is not supported"); } /** * A class that extends ExprVar and contains a QueryColumnInfo. * */ public static class ExprColumn extends ExprVar { private final QueryColumnInfo columnInfo; public ExprColumn(final QueryColumnInfo columnInfo) { super(columnInfo.getVar()); this.columnInfo = columnInfo; } /** * Get the enclosed column Info. * * @return the QueryColumnInfo */ public QueryColumnInfo getColumnInfo() { return columnInfo; } } public class AliasInfo { private String alias; private boolean aliasRequired; private boolean used; public AliasInfo(final String alias, final boolean aliasRequired) { this.alias = alias; this.aliasRequired = aliasRequired; this.used = false; } public String getAlias() { if (StringUtils.isBlank(alias)) { alias = makeAlias(); } return alias; } public boolean isAliasRequired() { return aliasRequired; } private String makeAlias() { return String.format("var%s", builder.getAliasCount()); } public boolean isUsed() { return used; } public void setUsed(final boolean used) { this.used = used; } } }