Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.rya.mongodb.aggregation; import java.util.Arrays; import org.apache.rya.mongodb.MongoDBRdfConfiguration; import org.apache.rya.mongodb.StatefulMongoDBRdfConfiguration; import org.bson.Document; import org.eclipse.rdf4j.query.algebra.Distinct; import org.eclipse.rdf4j.query.algebra.Extension; import org.eclipse.rdf4j.query.algebra.Filter; import org.eclipse.rdf4j.query.algebra.Join; import org.eclipse.rdf4j.query.algebra.MultiProjection; import org.eclipse.rdf4j.query.algebra.Projection; import org.eclipse.rdf4j.query.algebra.Reduced; import org.eclipse.rdf4j.query.algebra.StatementPattern; import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.algebra.ValueExpr; import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor; import com.google.common.base.Preconditions; import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; /** * Visitor that transforms a SPARQL query tree by replacing as much of the tree * as possible with one or more {@code AggregationPipelineQueryNode}s. * <p> * Each {@link AggregationPipelineQueryNode} contains a MongoDB aggregation * pipeline which is equivalent to the replaced portion of the original query. * Evaluating this node executes the pipeline and converts the results into * query solutions. If only part of the query was transformed, the remaining * query logic (higher up in the query tree) can be applied to those * intermediate solutions as normal. * <p> * In general, processes the tree in bottom-up order: A leaf node * ({@link StatementPattern}) is replaced with a pipeline that matches the * corresponding statements. Then, if the parent node's semantics are supported * by the visitor, stages are appended to the pipeline and the subtree at the * parent node is replaced with the extended pipeline. This continues up the * tree until reaching a node that cannot be transformed, in which case that * node's child is now a single {@code AggregationPipelineQueryNode} (a leaf * node) instead of the previous subtree, or until the entire tree has been * subsumed into a single pipeline node. * <p> * Nodes which are transformed into pipeline stages: * <p><ul> * <li>A {@code StatementPattern} node forms the beginning of each pipeline. * <li>Single-argument operations {@link Projection}, {@link MultiProjection}, * {@link Extension}, {@link Distinct}, and {@link Reduced} will be transformed * into pipeline stages whenever the child {@link TupleExpr} represents a * pipeline. * <li>A {@link Filter} operation will be appended to the pipeline when its * child {@code TupleExpr} represents a pipeline and the filter condition is a * type of {@link ValueExpr} understood by {@code AggregationPipelineQueryNode}. * <li>A {@link Join} operation will be appended to the pipeline when one child * is a {@code StatementPattern} and the other is an * {@code AggregationPipelineQueryNode}. * </ul> */ public class SparqlToPipelineTransformVisitor extends AbstractQueryModelVisitor<Exception> { private final MongoCollection<Document> inputCollection; /** * Instantiate a visitor directly from a {@link MongoCollection}. * @param inputCollection Stores triples. */ public SparqlToPipelineTransformVisitor(MongoCollection<Document> inputCollection) { this.inputCollection = Preconditions.checkNotNull(inputCollection); } /** * Instantiate a visitor from a {@link MongoDBRdfConfiguration}. * @param conf Contains database connection information. */ public SparqlToPipelineTransformVisitor(StatefulMongoDBRdfConfiguration conf) { Preconditions.checkNotNull(conf); MongoClient mongo = conf.getMongoClient(); MongoDatabase db = mongo.getDatabase(conf.getMongoDBName()); this.inputCollection = db.getCollection(conf.getTriplesCollectionName()); } @Override public void meet(StatementPattern sp) { sp.replaceWith(new AggregationPipelineQueryNode(inputCollection, sp)); } @Override public void meet(Join join) throws Exception { // If one branch is a single statement pattern, then try replacing the // other with a pipeline. AggregationPipelineQueryNode pipelineNode = null; StatementPattern joinWithSP = null; if (join.getRightArg() instanceof StatementPattern) { join.getLeftArg().visit(this); if (join.getLeftArg() instanceof AggregationPipelineQueryNode) { pipelineNode = (AggregationPipelineQueryNode) join.getLeftArg(); joinWithSP = (StatementPattern) join.getRightArg(); } } else if (join.getLeftArg() instanceof StatementPattern) { join.getRightArg().visit(this); if (join.getRightArg() instanceof AggregationPipelineQueryNode) { pipelineNode = (AggregationPipelineQueryNode) join.getRightArg(); joinWithSP = (StatementPattern) join.getLeftArg(); } } else { // Otherwise, visit the children to try to replace smaller subtrees join.visitChildren(this); } // If this is now a join between a pipeline node and a statement // pattern, add the join step at the end of the pipeline, and replace // this node with the extended pipeline node. if (pipelineNode != null && joinWithSP != null && pipelineNode.joinWith(joinWithSP)) { join.replaceWith(pipelineNode); } } @Override public void meet(Projection projectionNode) throws Exception { projectionNode.visitChildren(this); if (projectionNode.getArg() instanceof AggregationPipelineQueryNode && projectionNode.getParentNode() != null) { AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) projectionNode.getArg(); if (pipelineNode.project(Arrays.asList(projectionNode.getProjectionElemList()))) { projectionNode.replaceWith(pipelineNode); } } } @Override public void meet(MultiProjection projectionNode) throws Exception { projectionNode.visitChildren(this); if (projectionNode.getArg() instanceof AggregationPipelineQueryNode && projectionNode.getParentNode() != null) { AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) projectionNode.getArg(); if (pipelineNode.project(projectionNode.getProjections())) { projectionNode.replaceWith(pipelineNode); } } } @Override public void meet(Extension extensionNode) throws Exception { extensionNode.visitChildren(this); if (extensionNode.getArg() instanceof AggregationPipelineQueryNode && extensionNode.getParentNode() != null) { AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) extensionNode.getArg(); if (pipelineNode.extend(extensionNode.getElements())) { extensionNode.replaceWith(pipelineNode); } } } @Override public void meet(Reduced reducedNode) throws Exception { reducedNode.visitChildren(this); if (reducedNode.getArg() instanceof AggregationPipelineQueryNode && reducedNode.getParentNode() != null) { reducedNode.replaceWith(reducedNode.getArg()); } } @Override public void meet(Distinct distinctNode) throws Exception { distinctNode.visitChildren(this); if (distinctNode.getArg() instanceof AggregationPipelineQueryNode && distinctNode.getParentNode() != null) { AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) distinctNode.getArg(); pipelineNode.distinct(); distinctNode.replaceWith(pipelineNode); } } @Override public void meet(Filter filterNode) throws Exception { filterNode.visitChildren(this); if (filterNode.getArg() instanceof AggregationPipelineQueryNode && filterNode.getParentNode() != null) { AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) filterNode.getArg(); if (pipelineNode.filter(filterNode.getCondition())) { filterNode.replaceWith(pipelineNode); } } } }