/******************************************************************************* * Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * File: $Source: /cvsroot/slrp/glitter/com.ibm.adtech.glitter/src/com/ibm/adtech/glitter/query/rewriter/ConjunctiveRewriter.java,v $ * Created by: Lee Feigenbaum (<a href="mailto:feigenbl@us.ibm.com">feigenbl@us.ibm.com</a>) * Created on: 10/23/06 * Revision: $Id: ConjunctiveRewriter.java 164 2007-07-31 14:11:09Z mroy $ * * Contributors: IBM Corporation - initial API and implementation * Cambridge Semantics Incorporated - Fork to Anzo *******************************************************************************/ package org.openanzo.glitter.query.rewriter; import java.util.ArrayList; import java.util.List; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.glitter.exception.GlitterRuntimeException; import org.openanzo.glitter.expression.builtin.LogicalAnd; import org.openanzo.glitter.query.TreeRewriter; import org.openanzo.glitter.syntax.abstrakt.BGP; import org.openanzo.glitter.syntax.abstrakt.Expression; import org.openanzo.glitter.syntax.abstrakt.FunctionCall; import org.openanzo.glitter.syntax.abstrakt.GraphPattern; import org.openanzo.glitter.syntax.abstrakt.Group; import org.openanzo.glitter.syntax.abstrakt.TreeNode; import org.openanzo.glitter.syntax.abstrakt.TriplePatternNode; import org.openanzo.glitter.syntax.concrete.ParseException; /** * The ConjunctiveRewriter takes advantage of the following equalities: { t1 t2 { t3 } } = t1 t2 t3 * * That is, triples within a single BGP are conjoined, as are GraphPatterns within a group. By flattening out nested groups and then merging multiple BGPs * within a group, we take a great deal of complexity out of the AST and enable other query-plan optimizations. * * This also conjoins multiple filtered basic graph patterns. * * (Note that on the surface {t1 {t2} } is not identical to {t1 t2} because the two triples have different blank node scopes. At this point (the AST rewriting * point), distinct blank nodes for identical labels in different scopes have already be created, so we can lose that difference.) * * (Note also that this equivalence might not hold for entaiments other than simple entailment, and as such a backend that implements other entailments should * be wary in its use of the ConjunctiveRewriter.) * * ConjunctiveRewriter only operates on Group nodes, and only manipulates Group, BGP, and Filter nodes. * * TODO @@ ConjunctiveRewriter also needs to ensure that every Group has a BGP. * * TODO @@ This could be improved to flatten single element groups regardless of filters in children { { ?s ?p ?o. FILTER (...) } }. * * @author Lee * */ public class ConjunctiveRewriter implements TreeRewriter { protected void conjoinSecondBGP(BGP one, BGP two) { for (TriplePatternNode tpn : two.getTriplePatterns()) one.addTriplePattern(tpn); } protected Expression conjoinExpressions(Expression one, Expression two) { if (one == null) return two; if (two == null) return one; ArrayList<Expression> args = new ArrayList<Expression>(); args.add(one); args.add(two); try { return new FunctionCall(new LogicalAnd(), args); } catch (ParseException e) { throw new GlitterRuntimeException(ExceptionConstants.GLITTER.PARSE_EXCEPTION, e, "LogicalAnd is not an aggregate function", e.getMessage()); } } private boolean conjoinGraphPattern(BGP theBGP, Group parent, GraphPattern gp, List<GraphPattern> toAdd) { if (gp instanceof BGP) { conjoinSecondBGP(theBGP, (BGP) gp); return true; } else if (gp instanceof Group) { return conjoinSecondGroup(theBGP, parent, (Group) gp, toAdd); } else { // any other graph pattern just gets added on to the parent toAdd.add(gp); return true; } } /** * If possible, flattens the child group into the parent group. * * @param theBGP * The single BGP for the parent group. All triples get added here. * @param parent * The parent group to which we are trying to add everything. * @param child * The child group which we are hoping to eliminate. * @param toAdd * A list of graph patterns that need to be added to the parent group. This list is kept on its own so as not to affect the iterations in * progress. * @return true if the child group was successfully eliminated, false otherwise */ protected boolean conjoinSecondGroup(BGP theBGP, Group parent, Group child, List<GraphPattern> toAdd) { boolean b = true; // if the child group has its own filters or assignments, then we can't conjoin it // (since those are limited to the scope of that group) if (child.getFilters().size() == 0 && child.getAssignments().size() == 0) { // we can only remove this child group if we were able to eliminate // all of its children for (GraphPattern gp : child.getPatterns()) b = conjoinGraphPattern(theBGP, parent, gp, toAdd) && b; // the caller of this function is responsible for removing the child group return b; } return false; } public TreeNode rewriteTreeNode(TreeNode node) { // our goal is to make each group contain only a single // BGP; we could also conjoin all of the filters into a single // filter, but that seems less important for the moment. if (node instanceof Group) { BGP theBGP = new BGP(new ArrayList<TriplePatternNode>()); Group g = (Group) node; ArrayList<GraphPattern> toAdd = new ArrayList<GraphPattern>(), toRemove = new ArrayList<GraphPattern>(); for (GraphPattern pattern : g.getPatterns()) { if (pattern instanceof BGP || pattern instanceof Group) { if (conjoinGraphPattern(theBGP, g, pattern, toAdd)) toRemove.add(pattern); } } for (GraphPattern gp : toRemove) g.removeGraphPattern(gp); for (GraphPattern gp : toAdd) g.addGraphPattern(gp); // we used to only add a BGP if it was non-empty // the special case of empty groups made backends more difficult // to implement in some cases, so now we ensure a single BGP for // every group, even if it is empty g.addGraphPattern(theBGP); } return node; } }