All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.rdf4j.sail.federation.optimizers.PrepareOwnedTupleExpr Maven / Gradle / Ivy

Go to download

The Federation SAIL allows multiple datasets to be virtually combined into a single dataset. The Federation SAIL combines multiple RDF stores that may exist on a remote server or are embedded in the same JVM. The Federation uses query optimizations to distribute sections of the query to different members based on the data contained in each of the members. These results are then joined together within the federation to provide the same result as if all the data was co-located within a single repository.

There is a newer version: 4.0.0-M1
Show newest version
/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.sail.federation.optimizers;

import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.rdf4j.model.BNode;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.util.Literals;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.Dataset;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.algebra.Distinct;
import org.eclipse.rdf4j.query.algebra.Join;
import org.eclipse.rdf4j.query.algebra.LeftJoin;
import org.eclipse.rdf4j.query.algebra.Projection;
import org.eclipse.rdf4j.query.algebra.ProjectionElem;
import org.eclipse.rdf4j.query.algebra.ProjectionElemList;
import org.eclipse.rdf4j.query.algebra.QueryModelNode;
import org.eclipse.rdf4j.query.algebra.Reduced;
import org.eclipse.rdf4j.query.algebra.StatementPattern;
import org.eclipse.rdf4j.query.algebra.StatementPattern.Scope;
import org.eclipse.rdf4j.query.algebra.TupleExpr;
import org.eclipse.rdf4j.query.algebra.Var;
import org.eclipse.rdf4j.query.algebra.evaluation.QueryOptimizer;
import org.eclipse.rdf4j.query.algebra.helpers.AbstractQueryModelVisitor;
import org.eclipse.rdf4j.query.algebra.helpers.TupleExprs;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.rio.turtle.TurtleUtil;
import org.eclipse.rdf4j.sail.federation.algebra.NaryJoin;
import org.eclipse.rdf4j.sail.federation.algebra.OwnedTupleExpr;

/**
 * Remove redundant {@link OwnedTupleExpr}.
 *
 * @author James Leigh
 */
public class PrepareOwnedTupleExpr extends AbstractQueryModelVisitor implements QueryOptimizer {

	private OwnedTupleExpr owner;

	private String pattern;

	private TupleExpr patternNode;

	/** local name to sparql name */
	private Map variables = new HashMap<>();

	private boolean reduce;

	private boolean reduced;

	private boolean distinct;

	@Override
	public void optimize(TupleExpr query, Dataset dataset, BindingSet bindings) {
		try {
			query.visit(this);
		} catch (RepositoryException e) {
			throw new UndeclaredThrowableException(e);
		}
	}

	@Override
	public void meetOther(QueryModelNode node) throws RepositoryException {
		if (node instanceof OwnedTupleExpr) {
			meetOwnedTupleExpr((OwnedTupleExpr) node);
		} else if (node instanceof NaryJoin) {
			meetMultiJoin((NaryJoin) node);
		} else {
			super.meetOther(node);
		}
	}

	private void meetOwnedTupleExpr(OwnedTupleExpr node) throws RepositoryException {
		OwnedTupleExpr before = this.owner;
		try {
			this.owner = node;
			meetNode(node);
			this.owner = null; // NOPMD
		} finally {
			this.owner = before;
		}
	}

	@Override
	protected void meetNode(QueryModelNode node) throws RepositoryException {
		super.meetNode(node);
		if (owner != null && patternNode != null && !(patternNode instanceof StatementPattern)) {
			StringBuilder builder = new StringBuilder();
			builder.append("SELECT");
			if (distinct) {
				builder.append(" DISTINCT");
			} else if (reduced || reduce) {
				builder.append(" REDUCED");
			}
			boolean mapping = false;
			Map bindings = new HashMap<>();
			ProjectionElemList list = new ProjectionElemList();
			for (String name : patternNode.getBindingNames()) {
				mapping = addBindingNames(builder, mapping, bindings, list, name);
			}
			builder.append("\nWHERE {\n").append(pattern).append("}");
			meetNodeLocal(builder, mapping, bindings, list);
		}
		reduced = false;
		distinct = false;
		pattern = null; // NOPMD
		patternNode = null; // NOPMD
	}

	private void meetNodeLocal(StringBuilder builder, boolean mapping, Map bindings,
			ProjectionElemList list) throws RepositoryException, AssertionError {
		try {
			QueryModelNode parent = patternNode.getParentNode();
			if (parent instanceof OwnedTupleExpr) {
				OwnedTupleExpr owned = (OwnedTupleExpr) parent;
				meetNodeLocalParentOwned(builder, mapping, bindings, list, owned);
			} else {
				meetNodeLocalParentNotOwned(builder, mapping, bindings, list);
			}
		} catch (MalformedQueryException e) {
			throw new AssertionError(e);
		}
	}

	private void meetNodeLocalParentOwned(StringBuilder builder, boolean mapping, Map bindings,
			ProjectionElemList list, OwnedTupleExpr owned) throws RepositoryException, MalformedQueryException {
		owned.prepare(QueryLanguage.SPARQL, builder.toString(), bindings);
		if (mapping) {
			Projection proj = new Projection(owned.clone(), list);
			owned.replaceWith(proj);
		}
	}

	private void meetNodeLocalParentNotOwned(StringBuilder builder, boolean mapping, Map bindings,
			ProjectionElemList list) throws RepositoryException, MalformedQueryException {
		OwnedTupleExpr owned = new OwnedTupleExpr(owner.getOwner(), patternNode.clone());
		owned.prepare(QueryLanguage.SPARQL, builder.toString(), bindings);
		if (mapping) {
			Projection proj = new Projection(owned, list);
			patternNode.replaceWith(proj);
		} else {
			patternNode.replaceWith(owned);
		}
	}

	private boolean addBindingNames(StringBuilder builder, boolean alreadyMapping, Map bindings,
			ProjectionElemList list, String name) {
		boolean mapping = alreadyMapping;
		if (variables.containsKey(name)) {
			String var = variables.get(name);
			builder.append(" ?").append(var);
			bindings.put(name, var);
			list.addElement(new ProjectionElem(var, name));
			if (!name.equals(var)) {
				mapping = true;
			}
		}
		return mapping;
	}

	@Override
	public void meet(Distinct node) throws RepositoryException {
		boolean before = reduce;
		try {
			reduce = true;
			node.getArg().visit(this);
		} finally {
			reduce = before;
		}
		if (patternNode == null) {
			return;
		}
		this.distinct = true;
		this.patternNode = node;
	}

	@Override
	public void meet(Reduced node) throws RepositoryException {
		boolean before = reduce;
		try {
			reduce = true;
			node.getArg().visit(this);
		} finally {
			reduce = before;
		}
		if (patternNode == null) {
			return;
		}
		this.reduced = true;
		this.patternNode = node;
	}

	@Override
	public void meet(Projection node) throws RepositoryException {
		TupleExpr arg = node.getArg();
		if (arg instanceof StatementPattern && arg.getBindingNames().equals(node.getBindingNames())) {
			meetNode(node);
		} else {
			arg.visit(this);
			if (patternNode == null) {
				return;
			}
			Map map = new HashMap<>();
			for (ProjectionElem e : node.getProjectionElemList().getElements()) {
				String source = variables.get(e.getSourceName());
				if (source == null) {
					source = safe(e.getSourceName());
				}
				map.put(e.getTargetName(), source);
			}
			this.variables = map;
			this.patternNode = node;
		}
	}

	@Override
	public void meet(LeftJoin node) throws RepositoryException {
		if (node.getCondition() == null) {
			Map vars = new HashMap<>();
			StringBuilder builder = new StringBuilder();
			node.getLeftArg().visit(this);
			if (patternNode != null) {
				builder.append(pattern);
				vars.putAll(variables);
				node.getRightArg().visit(this);
				if (patternNode != null) {
					builder.append("OPTIONAL {").append(pattern).append("}\n");
					vars.putAll(variables);
					this.variables = vars;
					this.pattern = builder.toString();
					this.patternNode = node;
				}
			}
		} else {
			super.meet(node);
		}
	}

	public void meetMultiJoin(NaryJoin node) throws RepositoryException {
		Map vars = new HashMap<>();
		StringBuilder builder = new StringBuilder();
		for (TupleExpr arg : node.getArgs()) {
			arg.visit(this);
			if (patternNode == null && owner != null) {
				return; // unsupported operation
			} else if (patternNode == null) {
				// no owner
				builder = null; // NOPMD
			} else if (builder != null) {
				if (TupleExprs.isVariableScopeChange(arg)) {
					builder.append("{");
				}
				builder.append(pattern);
				if (TupleExprs.isVariableScopeChange(arg)) {
					builder.append("}");
				}
				builder.append("\n");
				vars.putAll(variables);
			}
		}
		if (builder != null) {
			this.variables = vars;
			this.pattern = builder.toString();
			this.patternNode = node;
		}
	}

	@Override
	public void meet(Join node) throws RepositoryException {
		Map vars = new HashMap<>();
		StringBuilder builder = new StringBuilder();

		TupleExpr leftArg = node.getLeftArg();
		TupleExpr rightArg = node.getRightArg();

		leftArg.visit(this);
		if (patternNode != null) {
			if (TupleExprs.isVariableScopeChange(leftArg)) {
				builder.append("{");
			}
			builder.append(pattern);
			if (TupleExprs.isVariableScopeChange(leftArg)) {
				builder.append("}");
			}
			builder.append("\n");

			vars.putAll(variables);
			rightArg.visit(this);
			if (patternNode != null) {
				if (TupleExprs.isVariableScopeChange(rightArg)) {
					builder.append("{");
				}
				builder.append(pattern);
				if (TupleExprs.isVariableScopeChange(rightArg)) {
					builder.append("}");
				}
				builder.append("\n");

				vars.putAll(variables);
				this.variables = vars;
				this.pattern = builder.toString();
				this.patternNode = node;
			}
		}
	}

	@Override
	public void meet(StatementPattern node) throws RepositoryException {
		StringBuilder builder = new StringBuilder();
		Scope scope = node.getScope();
		Var subj = node.getSubjectVar();
		Var pred = node.getPredicateVar();
		Var obj = node.getObjectVar();
		Var ctx = node.getContextVar();
		boolean cokay = ctx == null && scope.equals(Scope.DEFAULT_CONTEXTS)
				|| ctx != null && scope.equals(Scope.NAMED_CONTEXTS);
		boolean sokay = !subj.hasValue() || subj.isAnonymous() || subj.getValue() instanceof IRI;
		boolean ookay = !obj.hasValue() || obj.isAnonymous() || obj.getValue() instanceof IRI
				|| obj.getValue() instanceof Literal;
		if (cokay && sokay && ookay) {
			variables.clear();
			if (ctx != null) {
				builder.append("GRAPH ");
				appendVar(builder, ctx.getName());
				builder.append(" {\n");
			}
			appendVar(builder, subj);
			appendVar(builder, pred);
			appendVar(builder, obj);
			builder.append(" .");
			appendFilter(builder, subj);
			appendFilter(builder, pred);
			appendFilter(builder, obj);
			if (ctx != null) {
				if (ctx.hasValue()) {
					builder.append("\nFILTER sameTerm(");
					appendVar(builder, ctx.getName());
					builder.append(", ");
					writeValue(builder, ctx.getValue());
					builder.append(")\n");
				}
				builder.append("}");
			}
			this.pattern = builder.toString();
			this.patternNode = node;
		} else {
			this.patternNode = null; // NOPMD
		}
	}

	private void appendVar(StringBuilder builder, Var var) {
		if (var.hasValue() && var.isAnonymous()) {
			Value value = var.getValue();
			writeValue(builder, value);
		} else {
			String varName = var.getName();
			appendVar(builder, varName);
		}
		builder.append(" ");
	}

	private void appendVar(StringBuilder builder, String varName) {
		builder.append("?");
		String name = safe(varName);
		builder.append(name);
		variables.put(varName, name);
	}

	private String safe(String name) {
		return name.replace('-', '_');
	}

	private void appendFilter(StringBuilder builder, Var var) {
		if (var.hasValue() && !var.isAnonymous()) {
			builder.append("\nFILTER sameTerm(");
			appendVar(builder, var.getName());
			builder.append(", ");
			writeValue(builder, var.getValue());
			builder.append(")");
		}
	}

	private void writeValue(StringBuilder builder, Value val) {
		if (val instanceof Resource) {
			writeResource(builder, (Resource) val);
		} else {
			writeLiteral(builder, (Literal) val);
		}
	}

	private void writeResource(StringBuilder builder, Resource res) {
		if (res instanceof IRI) {
			writeURI(builder, (IRI) res);
		} else {
			writeBNode(builder, (BNode) res);
		}
	}

	private void writeURI(StringBuilder builder, IRI uri) {
		builder.append("<");
		builder.append(TurtleUtil.encodeURIString(uri.stringValue()));
		builder.append(">");
	}

	private void writeBNode(StringBuilder builder, BNode bNode) {
		builder.append("_:");
		builder.append(bNode.stringValue());
	}

	private void writeLiteral(StringBuilder builder, Literal lit) {
		String label = lit.getLabel();

		if (label.indexOf('\n') > 0 || label.indexOf('\r') > 0 || label.indexOf('\t') > 0) {
			// Write label as long string
			builder.append("\"\"\"");
			builder.append(TurtleUtil.encodeLongString(label));
			builder.append("\"\"\"");
		} else {
			// Write label as normal string
			builder.append("\"");
			builder.append(TurtleUtil.encodeString(label));
			builder.append("\"");
		}

		IRI datatype = lit.getDatatype();
		if (Literals.isLanguageLiteral(lit)) {
			// Append the literal's language
			builder.append("@");
			builder.append(lit.getLanguage().get());
		} else {
			// Append the literal's data type (possibly written as an
			// abbreviated URI)
			builder.append("^^");
			writeURI(builder, datatype);
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy