/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Aug 24, 2011
*/
package com.bigdata.rdf.sparql.ast.optimizers;
import java.util.Collection;
import java.util.LinkedHashSet;
import org.openrdf.model.vocabulary.RDF;
import com.bigdata.bop.IBindingSet;
import com.bigdata.rdf.model.BigdataURI;
import com.bigdata.rdf.model.BigdataValue;
import com.bigdata.rdf.sparql.ast.AssignmentNode;
import com.bigdata.rdf.sparql.ast.ConstantNode;
import com.bigdata.rdf.sparql.ast.ConstructNode;
import com.bigdata.rdf.sparql.ast.DescribeModeEnum;
import com.bigdata.rdf.sparql.ast.GraphPatternGroup;
import com.bigdata.rdf.sparql.ast.GroupNodeBase;
import com.bigdata.rdf.sparql.ast.IGroupMemberNode;
import com.bigdata.rdf.sparql.ast.IQueryNode;
import com.bigdata.rdf.sparql.ast.JoinGroupNode;
import com.bigdata.rdf.sparql.ast.ProjectionNode;
import com.bigdata.rdf.sparql.ast.QueryNodeWithBindingSet;
import com.bigdata.rdf.sparql.ast.QueryRoot;
import com.bigdata.rdf.sparql.ast.QueryType;
import com.bigdata.rdf.sparql.ast.StatementPatternNode;
import com.bigdata.rdf.sparql.ast.TermNode;
import com.bigdata.rdf.sparql.ast.UnionNode;
import com.bigdata.rdf.sparql.ast.VarNode;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
import com.bigdata.rdf.store.AbstractTripleStore;
/**
* This optimizer rewrites the projection node of a DESCRIBE query into,
* generating a CONSTRUCT clause and extending the WHERE clause to capture the
* semantics of the DESCRIBE query. The query type is also changed to CONSTRUCT.
* <p>
* For example, the optimizer changes this:
*
* <pre>
* describe term1 term2 ...
* where {
* whereClause ...
* }
* </pre>
*
* Into this:
*
* <pre>
* construct {
* term1 ?p1a ?o1 .
* ?s1 ?p1b term1 .
* term2 ?p2a ?o2 .
* ?s2 ?p2b term2 .
* }
* where {
* whereClause ...
* {
* term1 ?p1a ?o1 .
* } union {
* ?s1 ?p1b term1 .
* } union {
* term2 ?p2a ?o2 .
* } union {
* ?s2 ?p2b term2 .
* }
* </pre>
* <p>
* Note: The {@link ASTConstructOptimizer} will add a {@link ProjectionNode}
* based on the generated CONSTRUCT template.
*
* @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/578"> Concise
* Bounded Description </a>
*/
public class ASTDescribeOptimizer implements IASTOptimizer {
// private static final Logger log = Logger.getLogger(DescribeOptimizer.class);
@SuppressWarnings("unchecked")
@Override
public QueryNodeWithBindingSet optimize(
final AST2BOpContext context, final QueryNodeWithBindingSet input) {
final IQueryNode queryNode = input.getQueryNode();
final IBindingSet[] bindingSet = input.getBindingSets();
final QueryRoot queryRoot = (QueryRoot) queryNode;
if (queryRoot.getQueryType() != QueryType.DESCRIBE) {
// Not a query that we will rewrite.
return new QueryNodeWithBindingSet(queryRoot, bindingSet);
}
// Change the query type. This helps prevent multiple rewrites.
queryRoot.setQueryType(QueryType.CONSTRUCT);
final GraphPatternGroup<IGroupMemberNode> where;
if (queryRoot.hasWhereClause()) {
// start with the existing WHERE clause.
if (queryRoot.getWhereClause() instanceof UnionNode) {
/**
* https://jira.blazegraph.com/browse/BLZG-1750:
* if "where" is a UNION node, the subsequent call where.addChild(union)
* only accept JoinGroupNodes (and casting UnionNode to JoinGroupNode fails).
* We therefore need to wrap the UNION node into a join group node.
*/
where = new JoinGroupNode();
where.addChild(queryRoot.getWhereClause());
queryRoot.setWhereClause(where);
} else {
where = queryRoot.getWhereClause();
}
} else {
// some describe queries don't have where clauses
queryRoot.setWhereClause(where = new JoinGroupNode());
}
final UnionNode union = new UnionNode();
where.addChild(union); // append UNION to WHERE clause.
final ConstructNode construct = new ConstructNode(context);
final ProjectionNode projection = queryRoot.getProjection();
if (projection == null) {
throw new RuntimeException("No projection?");
}
// The effective DescribeMode.
// final DescribeModeEnum describeMode = projection.getDescribeMode() == null ? QueryHints.DEFAULT_DESCRIBE_MODE
// : projection.getDescribeMode();
final DescribeModeEnum describeMode = context.getDescribeMode(projection);
// final IDescribeCache describeCache = context.getDescribeCache();
//
// if (describeCache != null) {
/**
* We need to keep the projection so we can correlate the original
* variables for the resources that are being described with the
* bindings on those variables in order to figure out what resources
* were described when we are maintaining a DESCRIBE cache.
*
* @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/584">
* DESCRIBE CACHE </a>
*/
/**
* We need to keep the projection since the DescribeMode annotation is
* attached to the projection.
*
* @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/578">
* Concise Bounded Description </a>
*/
projection.setReduced(true);
// } else {
//
// // remove the projection.
// queryRoot.setProjection(null);
//
// }
queryRoot.setConstruct(construct); // add CONSTRUCT node.
final Collection<TermNode> terms = new LinkedHashSet<TermNode>();
if (projection.isWildcard()) {
/*
* The projection node should have been rewritten first.
*/
throw new AssertionError("Wildcard projection was not rewritten.");
}
if (projection.isEmpty()) {
throw new RuntimeException(
"DESCRIBE, but no variables are projected.");
}
/*
* Note: A DESCRIBE may have only variables and constants in the
* projection. Generalized value expressions are not allowed.
*/
for (AssignmentNode n : projection) {
terms.add((TermNode) n.getValueExpressionNode());
}
final BigdataURI rdfSubject;
if (describeMode.isForward() && describeMode.isReifiedStatements()) {
/*
* We will need to look for the rdf:subject property, so resolve
* that against the lexicon. If the property is not in the lexicon
* then there can not be any reified statement models in the data.
*
* TODO REIFICATION DONE RIGHT : handle this differently if we are
* inlining statements about statements.
*/
final AbstractTripleStore db = context.getAbstractTripleStore();
final BigdataURI tmp = db.getValueFactory().asValue(RDF.SUBJECT);
db.getLexiconRelation().addTerms(new BigdataValue[] { tmp },
1/* numTerms */, true/* readOnly */);
if (tmp.getIV() == null) {
// Unknown.
rdfSubject = null;
} else {
rdfSubject = tmp;
// and set the valueCache on the BigdataURI.
rdfSubject.getIV().setValue(rdfSubject);
}
} else {
// Not interested in reified statement models.
rdfSubject = null;
}
int i = 0;
for (TermNode term : terms) {
final int termNum = i++;
if(describeMode.isForward())
{ // <term> ?pN-a ?oN
/*
* Note: Each statement has to be in a different part of the
* UNION. Also, note that we do not allow a bare statement
* pattern in a union. The statement pattern has to be embedded
* within a group.
*/
final StatementPatternNode sp = new StatementPatternNode(//
term, //
new VarNode("p" + termNum + "a"),//
new VarNode("o" + termNum)//
);
construct.addChild(sp);
final JoinGroupNode group = new JoinGroupNode();
group.addChild(sp);
union.addChild(group);
// union.addChild(sp);
}
if(describeMode.isReverse())
{ // ?sN ?pN-b <term>
final StatementPatternNode sp = new StatementPatternNode(//
new VarNode("s" + termNum),//
new VarNode("p" + termNum + "b"),//
term//
);
construct.addChild(sp);
final JoinGroupNode group = new JoinGroupNode();
group.addChild(sp);
union.addChild(group);
// union.addChild(sp);
}
if (describeMode.isForward() && describeMode.isReifiedStatements()) {
/*
* Pick up properties associated with reified statement models
* where the value of an rdf:subject assertion is the resource
* to be described.
*
* ?stmtN rdf:subject <term>
*/
final StatementPatternNode sp = new StatementPatternNode(//
new VarNode("stmt" + termNum),//
new ConstantNode(rdfSubject.getIV()),//
term//
);
construct.addChild(sp);
final JoinGroupNode group = new JoinGroupNode();
group.addChild(sp);
union.addChild(group);
}
}
return new QueryNodeWithBindingSet(queryRoot, bindingSet);
}
}