/* * 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.SQLDataException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.select.Distinct; import net.sf.jsqlparser.statement.select.Join; import net.sf.jsqlparser.statement.select.Limit; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.OrderByVisitor; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.SelectItem; import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.statement.select.Top; import net.sf.jsqlparser.statement.select.Union; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xenei.jdbc4sparql.iface.name.ColumnName; import org.xenei.jdbc4sparql.iface.name.ItemName; import org.xenei.jdbc4sparql.iface.name.TableName; 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.SparqlExprVisitor.ExprColumn; import com.hp.hpl.jena.graph.Node; import com.hp.hpl.jena.query.Query; import com.hp.hpl.jena.sparql.core.Var; import com.hp.hpl.jena.sparql.expr.E_Bound; import com.hp.hpl.jena.sparql.expr.E_Equals; import com.hp.hpl.jena.sparql.expr.Expr; import com.hp.hpl.jena.sparql.expr.ExprFunction; import com.hp.hpl.jena.sparql.expr.ExprFunction2; import com.hp.hpl.jena.sparql.expr.ExprVar; /** * Implementation of SelectVisitor and OrderByVisitor that merge the SQL * commands into the SparqlQueryBuilder. */ public class SparqlSelectVisitor implements SelectVisitor, OrderByVisitor { // the query builder private final SparqlQueryBuilder queryBuilder; private static Logger LOG = LoggerFactory .getLogger(SparqlSelectVisitor.class); /** * Constructor * * @param queryBuilder * The builder to user. */ SparqlSelectVisitor(final SparqlQueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; } private void applyOuterExpr(final Expr aExpr, final ItemName mapFrom, final ItemName mapTo) { if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG.debug("apply outer expr {}", aExpr); } if (aExpr instanceof ExprFunction) { applyOuterExprSub(aExpr, aExpr, mapFrom, mapTo); } } private void applyOuterExprSub(final Expr aExpr, final Expr toApply, final ItemName mapFrom, final ItemName mapTo) { if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG.debug("apply outer expr sub {} {}", aExpr, toApply); } final Expr expr = aExpr; if (expr instanceof ExprFunction) { for (final Expr subExpr : ((ExprFunction) expr).getArgs()) { applyOuterExprSub(subExpr, toApply, mapFrom, mapTo); } } else if (expr instanceof ExprVar) { QueryColumnInfo columnInfo = null; if (expr instanceof ExprColumn) { columnInfo = ((ExprColumn)expr).getColumnInfo(); } else { columnInfo = queryBuilder.getColumn(((ExprVar) expr).asVar()); } TableName tName = columnInfo.getName().getTableName(); final QueryTableInfo tableInfo = queryBuilder.getTable(tName); tableInfo.addDataFilter(columnInfo); if ((tableInfo != null) && (tableInfo.isOptional())) { tableInfo.addJoinElement(toApply); } } } private void deparseDistinct(final Distinct distinct) { if (distinct == null) { return; } queryBuilder.setDistinct(); if (distinct.getOnSelectItems() != null) { throw new UnsupportedOperationException( "DISTINCT ON() is not supported"); } } private void deparseInnerJoin(final Join join, final ItemName tableName) { // inner join // select * from table join othertable on table.id = othertable.fk final String fmt = "%s INNER JOIN Is not supported"; if (join.isRight()) { // this should never happen anyway throw new UnsupportedOperationException(String.format(fmt, "RIGHT")); } else if (join.isNatural()) { // this is one case we will not support // as it is generally // considered bad. throw new UnsupportedOperationException(String.format(fmt, "NATURAL")); } else if (join.isFull()) { // this should never happen anyway throw new UnsupportedOperationException(String.format(fmt, "FULL")); } else if (join.isLeft()) { // this should never happen anyway throw new UnsupportedOperationException(String.format(fmt, "LEFT")); } else { final SparqlFromVisitor fromVisitor = new SparqlFromVisitor( queryBuilder); join.getRightItem().accept(fromVisitor); if (join.getOnExpression() != null) { final SparqlExprVisitor expressionVisitor = new SparqlExprVisitor( queryBuilder, SparqlQueryBuilder.REQUIRED, false); join.getOnExpression().accept(expressionVisitor); queryBuilder.addFilter(expressionVisitor.getResult()); for (final ExprColumn exprCol : expressionVisitor.getColumns()) { final QueryColumnInfo paramColumnInfo = exprCol .getColumnInfo(); queryBuilder.getTable( paramColumnInfo.getName().getTableName()) .addDataFilter(paramColumnInfo); } } if (join.getUsingColumns() != null) { for (final Object c : join.getUsingColumns()) { queryBuilder.addUsing(((Column) c).getColumnName()); } } } } // take apart the join and figure out how to merge it. private void deparseJoin(final Join join, final TableName tableName) { if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG.debug("deparse join {}", join); } if (join.isSimple()) { final SparqlFromVisitor fromVisitor = new SparqlFromVisitor( queryBuilder); join.getRightItem().accept(fromVisitor); } else if (join.isOuter()) { deparseOuterJoin(join, tableName); } else { deparseInnerJoin(join, tableName); } } // process a limit private void deparseLimit(final Limit limit) { if (limit == null) { return; } if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG.debug("deparse limit {}", limit); } // LIMIT n OFFSET skip if (limit.isOffsetJdbcParameter()) { throw new UnsupportedOperationException( "LIMIT with OFFSET JDBC Parameter is not supported"); } else if (limit.getOffset() != 0) { queryBuilder.setOffset(limit.getOffset()); } if (limit.isRowCountJdbcParameter()) { throw new UnsupportedOperationException( "LIMIT with JDBC Parameter is not supported"); // buffer.append("?"); } else if (limit.getRowCount() != 0) { queryBuilder.setLimit(limit.getRowCount()); } else { throw new UnsupportedOperationException( "LIMIT with no parameter is not supported"); } } private void deparseOrderBy(final List<?> orderByElements) { if (orderByElements == null) { return; } if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG .debug("deparse orderby {}", orderByElements); } for (final Object name : orderByElements) { final OrderByElement orderByElement = (OrderByElement) name; orderByElement.accept(this); } } private void deparseOuterJoin(final Join join, final ItemName tableName) { final String fmt = "%s OUTER JOIN Is not supported"; if (join.isRight()) { throw new UnsupportedOperationException(String.format(fmt, "RIGHT")); } else if (join.isNatural()) { // this is one case we will not support // as it is generally // considered bad. throw new UnsupportedOperationException(String.format(fmt, "NATURAL")); } else if (join.isFull()) { throw new UnsupportedOperationException(String.format(fmt, "FULL")); } else { // handles left and not specified final SparqlFromVisitor fromVisitor = new SparqlFromVisitor( queryBuilder, SparqlQueryBuilder.OPTIONAL); join.getRightItem().accept(fromVisitor); if (join.getOnExpression() != null) { final SparqlExprVisitor expressionVisitor = new SparqlExprVisitor( queryBuilder, SparqlQueryBuilder.REQUIRED, false); join.getOnExpression().accept(expressionVisitor); applyOuterExpr(expressionVisitor.getResult(), fromVisitor.getName(), tableName); } if (join.getUsingColumns() != null) { for (final Object c : join.getUsingColumns()) { queryBuilder.addUsing(((Column) c).getColumnName()); } } } } /** * Get the SPARQL query generated from the querybuilder. * * @return The SPARQL query. * @throws SQLDataException */ public Query getQuery() throws SQLDataException { return queryBuilder.build(); } @Override public void visit(final OrderByElement orderBy) { if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG.debug("visit orderby: {}", orderBy); } final SparqlExprVisitor expressionVisitor = new SparqlExprVisitor( queryBuilder, SparqlQueryBuilder.REQUIRED, false); orderBy.getExpression().accept(expressionVisitor); queryBuilder.addOrderBy(expressionVisitor.getResult(), orderBy.isAsc()); if (!expressionVisitor.isEmpty()) { throw new IllegalStateException( "Order By processing failed -- stack not empty"); } } @Override public void visit(final PlainSelect plainSelect) { if (LOG.isDebugEnabled()) { SparqlSelectVisitor.LOG.debug("visit plainSelect: {}", plainSelect); } TableName lastTableName = null; final SparqlSelectItemVisitor selectItemVisitor = new SparqlSelectItemVisitor( queryBuilder); // Process FROM to get table names loaded in the builder if (plainSelect.getFromItem() != null) { final SparqlFromVisitor fromVisitor = new SparqlFromVisitor( queryBuilder); plainSelect.getFromItem().accept(fromVisitor); lastTableName = fromVisitor.getName(); } deparseDistinct(plainSelect.getDistinct()); // process Joins to pick up new tables if (plainSelect.getJoins() != null) { for (final Iterator<?> iter = plainSelect.getJoins().iterator(); iter .hasNext();) { final Join join = (Join) iter.next(); deparseJoin(join, lastTableName); } } // add required columns -- All tables must be identified before this. try { queryBuilder.addDefinedColumns(); } catch (final SQLDataException e1) { throw new IllegalStateException(e1.getMessage(), e1); } queryBuilder.setSegmentCount(); // process the select for (final Iterator<?> iter = plainSelect.getSelectItems().iterator(); iter .hasNext();) { final SelectItem selectItem = (SelectItem) iter.next(); selectItem.accept(selectItemVisitor); } // process the where to add filters. if (plainSelect.getWhere() != null) { final SparqlExprVisitor expressionVisitor = new SparqlExprVisitor( queryBuilder, SparqlQueryBuilder.OPTIONAL, false); plainSelect.getWhere().accept(expressionVisitor); final Expr expr = expressionVisitor.getResult(); queryBuilder.addFilter(expr); for (final ExprColumn exprCol : expressionVisitor.getColumns()) { final QueryColumnInfo paramColumnInfo = exprCol.getColumnInfo(); queryBuilder.getTable(paramColumnInfo.getName().getTableName()) .addDataFilter(paramColumnInfo); } } if (plainSelect.getGroupByColumnReferences() != null) { final SparqlExprVisitor visitor = new SparqlExprVisitor( queryBuilder, SparqlQueryBuilder.REQUIRED, false); for (final Iterator<?> iter = plainSelect .getGroupByColumnReferences().iterator(); iter.hasNext();) { final Expression e = (Expression) iter.next(); e.accept(visitor); queryBuilder.addGroupBy(visitor.getResult()); } } if (plainSelect.getHaving() != null) { final SparqlExprVisitor visitor = new SparqlExprVisitor( queryBuilder, SparqlQueryBuilder.REQUIRED, false); plainSelect.getHaving().accept(visitor); queryBuilder.setHaving(visitor.getResult()); } deparseOrderBy(plainSelect.getOrderByElements()); // TOP is implements in SPARQL as LIMIT final Top top = plainSelect.getTop(); Limit limit = plainSelect.getLimit(); if ((top != null) && (limit != null)) { throw new IllegalStateException( "Top and Limit may not both be specified"); } if (top != null) { if (top.isRowCountJdbcParameter()) { throw new UnsupportedOperationException( "TOP with JDBC Parameter is not supported"); } limit = new Limit(); limit.setRowCount(top.getRowCount()); } deparseLimit(plainSelect.getLimit()); } @Override public void visit(final Union union) { // TODO implement ALL if (union.isDistinct()) { queryBuilder.setDistinct(); } final List<SparqlQueryBuilder> unionBuilders = new ArrayList<SparqlQueryBuilder>(); for (final Iterator<?> iter = union.getPlainSelects().iterator(); iter .hasNext();) { final PlainSelect ps = (PlainSelect) iter.next(); final SparqlQueryBuilder sqb = new SparqlQueryBuilder(queryBuilder); ps.accept(new SparqlSelectVisitor(sqb)); unionBuilders.add(sqb); } try { queryBuilder.addUnion(unionBuilders); } catch (final SQLDataException e) { throw new IllegalStateException(e); } deparseOrderBy(union.getOrderByElements()); deparseLimit(union.getLimit()); throw new UnsupportedOperationException("UNION is not supported"); }; }