jenkins-bot has submitted this change and it was merged.
Change subject: Resolve labels
......................................................................
Resolve labels
Implements a "service" that resolves label like things in a way that doesn't
change the cardinality of the result set. You can call it like this:
```
SELECT *
WHERE {
SERVICE wikibase:label.en.de.fr {
wd:Q123 rdfs:label ?q123Label .
wd:Q123 rdfs:altLabel ?q123Alt .
wd:Q123 schema:description ?q123Desc .
wd:Q321 rdf:label ?q321Label .
}
}
```
or like this:
```
SELECT ?sLabel ?sAltLabel ?sDescription ?oLabel
WHERE {
?s wdt:P22 ?o .
SERVICE wikibase:label.en.de.fr {
}
}
```
It works by resolving the labels one by one on each solution. Blazegraph
naturally pushes SERVICE calls to the last step in the query which is
perfect for this.
Change-Id: I37df29ad442692d350fdd35cd17badb05170b416
---
M blazegraph/pom.xml
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/PrefixDelegatingServiceFactory.java
M
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseOptimizers.java
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/EmptyLabelServiceOptimizer.java
A
blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/LabelService.java
A
blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphStorageTestCase.java
M
blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphTestBase.java
A
blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/label/LabelServiceUnitTest.java
M common/src/main/java/org/wikidata/query/rdf/common/uri/Ontology.java
M common/src/main/java/org/wikidata/query/rdf/common/uri/RDFS.java
M common/src/main/java/org/wikidata/query/rdf/common/uri/SKOS.java
M dist/pom.xml
M dist/src/config/web.xml
M dist/src/script/runBlazegraph.sh
M pom.xml
M src/build/forbidden/all.txt
M src/build/forbidden/core.txt
A testTools/pom.xml
A testTools/src/main/java/org/wikidata/query/rdf/test/Matchers.java
R testTools/src/main/java/org/wikidata/query/rdf/test/StatementHelper.java
M tools/pom.xml
M
tools/src/main/java/org/wikidata/query/rdf/tool/wikibase/WikibaseRepository.java
M tools/src/test/java/org/wikidata/query/rdf/tool/ExpandedStatementBuilder.java
M tools/src/test/java/org/wikidata/query/rdf/tool/IOBlastingIntegrationTest.java
D tools/src/test/java/org/wikidata/query/rdf/tool/Matchers.java
M
tools/src/test/java/org/wikidata/query/rdf/tool/MultipleResultsQueryIntegrationTest.java
M tools/src/test/java/org/wikidata/query/rdf/tool/MungeIntegrationTest.java
M
tools/src/test/java/org/wikidata/query/rdf/tool/WikibaseDateExtensionIntegrationTest.java
M tools/src/test/java/org/wikidata/query/rdf/tool/rdf/MungerUnitTest.java
M
tools/src/test/java/org/wikidata/query/rdf/tool/rdf/NormalizingRdfHandlerUnitTest.java
M
tools/src/test/java/org/wikidata/query/rdf/tool/rdf/RdfRepositoryIntegrationTest.java
32 files changed, 1,468 insertions(+), 301 deletions(-)
Approvals:
Smalyshev: Looks good to me, approved
jenkins-bot: Verified
diff --git a/blazegraph/pom.xml b/blazegraph/pom.xml
index bd07ee1..db81d01 100644
--- a/blazegraph/pom.xml
+++ b/blazegraph/pom.xml
@@ -22,17 +22,29 @@
<dependency>
<groupId>com.bigdata</groupId>
<artifactId>bigdata</artifactId>
- <version>${blazegraph.version}</version>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wikidata.query.rdf</groupId>
<artifactId>common</artifactId>
+ </dependency>
+ <dependency>
+ <!-- Blazegraph needs http client to run services. -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-client</artifactId>
+ <version>9.2.10.v20150310</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wikidata.query.rdf</groupId>
+ <artifactId>testTools</artifactId>
<version>${project.parent.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
<build>
- <finalName>wikidata-query-blazegraph-${project.version}</finalName>
+ <finalName>wikidata-query-blazegraph-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/PrefixDelegatingServiceFactory.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/PrefixDelegatingServiceFactory.java
new file mode 100644
index 0000000..45daf08
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/PrefixDelegatingServiceFactory.java
@@ -0,0 +1,60 @@
+package org.wikidata.query.rdf.blazegraph;
+
+import org.openrdf.model.URI;
+
+import com.bigdata.rdf.sparql.ast.service.IServiceOptions;
+import com.bigdata.rdf.sparql.ast.service.ServiceCall;
+import com.bigdata.rdf.sparql.ast.service.ServiceCallCreateParams;
+import com.bigdata.rdf.sparql.ast.service.ServiceFactory;
+
+/**
+ * ServiceFactory that sends service calls that match a prefix to a different
+ * factory than those that don't. Useful for setting ServiceRegistry's
+ * defaultService so some prefixes can be reserved for different types of
+ * services.
+ */
+public class PrefixDelegatingServiceFactory implements ServiceFactory {
+ /**
+ * Service factory to use if the prefix doesn't match.
+ */
+ private final ServiceFactory defaultFactory;
+ /**
+ * Prefix to check.
+ */
+ private final String prefix;
+ /**
+ * Service factory to use if the prefix does match.
+ */
+ private final ServiceFactory prefixedFactory;
+
+ public PrefixDelegatingServiceFactory(ServiceFactory defaultFactory,
String prefix, ServiceFactory prefixedFactory) {
+ this.defaultFactory = defaultFactory;
+ this.prefix = prefix;
+ this.prefixedFactory = prefixedFactory;
+ }
+
+ @Override
+ public IServiceOptions getServiceOptions() {
+ /*
+ * Sadly we can't figure out which service options to use so we just
use
+ * the default ones. This is almost certainly wrong bug doesn't seem to
+ * cause any trouble yet.
+ */
+ return defaultFactory.getServiceOptions();
+ }
+
+ @Override
+ public ServiceCall<?> create(ServiceCallCreateParams params) {
+ return getServiceFactory(params.getServiceURI()).create(params);
+ }
+
+ /**
+ * Get the service factory to use given this URI.
+ */
+ private ServiceFactory getServiceFactory(URI uri) {
+ if (uri.stringValue().startsWith(prefix)) {
+ return prefixedFactory;
+ }
+ return defaultFactory;
+ }
+}
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
index dcdb3b0..a29a5fa 100644
---
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseContextListener.java
@@ -2,6 +2,8 @@
import javax.servlet.ServletContextEvent;
+import org.wikidata.query.rdf.blazegraph.label.LabelService;
+
import com.bigdata.rdf.sail.webapp.BigdataRDFServletContextListener;
import com.bigdata.rdf.sparql.ast.service.IServiceOptions;
import com.bigdata.rdf.sparql.ast.service.ServiceCall;
@@ -13,17 +15,25 @@
* Context listener to enact configurations we need on initialization.
*/
public class WikibaseContextListener extends BigdataRDFServletContextListener {
+ /**
+ * Replaces the default Blazegraph services with ones that do not allow
+ * remote services and a label resolution service.
+ */
+ public static void initializeServices() {
+ ServiceRegistry.getInstance().setDefaultServiceFactory(new
DisableRemotesServiceFactory());
+ LabelService.register();
+ }
@Override
public void contextInitialized(final ServletContextEvent e) {
super.contextInitialized(e);
- ServiceRegistry.getInstance().setDefaultServiceFactory(new
DisableRemotesServiceFactory());
+ initializeServices();
}
/**
* Service factory that disables remote access.
*/
- private final class DisableRemotesServiceFactory implements ServiceFactory
{
+ private static final class DisableRemotesServiceFactory implements
ServiceFactory {
@Override
public IServiceOptions getServiceOptions() {
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseOptimizers.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseOptimizers.java
new file mode 100644
index 0000000..cc4a7e4
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/WikibaseOptimizers.java
@@ -0,0 +1,16 @@
+package org.wikidata.query.rdf.blazegraph;
+
+import org.wikidata.query.rdf.blazegraph.label.EmptyLabelServiceOptimizer;
+
+import com.bigdata.rdf.sparql.ast.optimizers.DefaultOptimizerList;
+
+/**
+ * Optimizer list for Wikibase.
+ */
+public class WikibaseOptimizers extends DefaultOptimizerList {
+ private static final long serialVersionUID = 2364845438265527328L;
+
+ public WikibaseOptimizers() {
+ add(new EmptyLabelServiceOptimizer());
+ }
+}
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/EmptyLabelServiceOptimizer.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/EmptyLabelServiceOptimizer.java
new file mode 100644
index 0000000..113413b
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/EmptyLabelServiceOptimizer.java
@@ -0,0 +1,98 @@
+package org.wikidata.query.rdf.blazegraph.label;
+
+import org.apache.log4j.Logger;
+import org.openrdf.model.URI;
+import org.openrdf.model.impl.URIImpl;
+import org.openrdf.model.vocabulary.RDFS;
+import org.openrdf.model.vocabulary.SKOS;
+import org.wikidata.query.rdf.common.uri.Ontology;
+import org.wikidata.query.rdf.common.uri.SchemaDotOrg;
+
+import com.bigdata.bop.IBindingSet;
+import com.bigdata.bop.IConstant;
+import com.bigdata.bop.IVariable;
+import com.bigdata.rdf.internal.IV;
+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.JoinGroupNode;
+import com.bigdata.rdf.sparql.ast.ProjectionNode;
+import com.bigdata.rdf.sparql.ast.StatementPatternNode;
+import com.bigdata.rdf.sparql.ast.StaticAnalysis;
+import com.bigdata.rdf.sparql.ast.VarNode;
+import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
+import com.bigdata.rdf.sparql.ast.optimizers.AbstractJoinGroupOptimizer;
+import com.bigdata.rdf.sparql.ast.service.ServiceNode;
+
+/**
+ * Rewrites empty calls to the label service to attempt to resolve labels based
+ * on the query's projection.
+ */
+@SuppressWarnings("rawtypes")
+public class EmptyLabelServiceOptimizer extends AbstractJoinGroupOptimizer {
+ private static final Logger log =
Logger.getLogger(EmptyLabelServiceOptimizer.class);
+
+ /**
+ * Schema.org's description property as a URI.
+ */
+ private static final URI DESCRIPTION = new
URIImpl(SchemaDotOrg.DESCRIPTION);
+
+ @Override
+ protected void optimizeJoinGroup(AST2BOpContext ctx, StaticAnalysis sa,
IBindingSet[] bSets, JoinGroupNode op) {
+ for (ServiceNode service : op.getServiceNodes()) {
+ BigdataValue serviceRef = service.getServiceRef().getValue();
+ if (serviceRef == null) {
+ continue;
+ }
+ if (!serviceRef.stringValue().startsWith(Ontology.LABEL)) {
+ continue;
+ }
+ JoinGroupNode g = (JoinGroupNode) service.getGraphPattern();
+ if (!g.args().isEmpty()) {
+ continue;
+ }
+ addResolutions(ctx, g, sa.getQueryRoot().getProjection());
+ // We can really only do this once....
+ return;
+ }
+ }
+
+ /**
+ * Infer that the user wanted to resolve some variables using the label
+ * service.
+ */
+ private void addResolutions(AST2BOpContext ctx, JoinGroupNode g,
ProjectionNode p) {
+ for (AssignmentNode a : p) {
+ IVariable<IV> var = a.getVar();
+ if (a.getValueExpression() != var) {
+ continue;
+ }
+ /*
+ * Try and match a variable name we can resolve via labels. Note
+ * that we should match AltLabel before Label because Label is a
+ * suffix of it....
+ */
+ boolean replaced = addResolutionIfSuffix(ctx, g, "AltLabel",
SKOS.ALT_LABEL, var)
+ || addResolutionIfSuffix(ctx, g, "Label", RDFS.LABEL, var)
+ || addResolutionIfSuffix(ctx, g, "Description",
DESCRIPTION, var);
+ if (replaced && log.isDebugEnabled()) {
+ log.debug("Resolving " + var + " using a label lookup.");
+ }
+ }
+ }
+
+ /**
+ * Add the join group to resolve a variable if it matches a suffix,
+ * returning true if it matched, false otherwise.
+ */
+ private boolean addResolutionIfSuffix(AST2BOpContext ctx, JoinGroupNode g,
String suffix, URI labelType,
+ IVariable<IV> var) {
+ if (!var.getName().endsWith(suffix)) {
+ return false;
+ }
+ String source = var.getName().substring(0, var.getName().length() -
suffix.length());
+ IConstant<IV> labelTypeAsConstant =
ctx.getAbstractTripleStore().getVocabulary().getConstant(labelType);
+ g.addArg(new StatementPatternNode(new VarNode(source), new
ConstantNode(labelTypeAsConstant), new VarNode(var)));
+ return true;
+ }
+}
diff --git
a/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/LabelService.java
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/LabelService.java
new file mode 100644
index 0000000..3749d3b
--- /dev/null
+++
b/blazegraph/src/main/java/org/wikidata/query/rdf/blazegraph/label/LabelService.java
@@ -0,0 +1,530 @@
+package org.wikidata.query.rdf.blazegraph.label;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.openrdf.model.Literal;
+import org.openrdf.model.impl.LiteralImpl;
+import org.openrdf.model.vocabulary.RDFS;
+import org.wikidata.query.rdf.blazegraph.PrefixDelegatingServiceFactory;
+import org.wikidata.query.rdf.common.uri.Ontology;
+import org.wikidata.query.rdf.common.uri.WikibaseUris;
+
+import com.bigdata.bop.BOp;
+import com.bigdata.bop.Constant;
+import com.bigdata.bop.IBindingSet;
+import com.bigdata.bop.IValueExpression;
+import com.bigdata.bop.IVariable;
+import com.bigdata.rdf.internal.IV;
+import com.bigdata.rdf.internal.VTE;
+import com.bigdata.rdf.internal.impl.TermId;
+import com.bigdata.rdf.lexicon.LexiconRelation;
+import com.bigdata.rdf.model.BigdataValue;
+import com.bigdata.rdf.sparql.ast.JoinGroupNode;
+import com.bigdata.rdf.sparql.ast.StatementPatternNode;
+import com.bigdata.rdf.sparql.ast.VarNode;
+import com.bigdata.rdf.sparql.ast.service.BigdataNativeServiceOptions;
+import com.bigdata.rdf.sparql.ast.service.BigdataServiceCall;
+import com.bigdata.rdf.sparql.ast.service.IServiceOptions;
+import com.bigdata.rdf.sparql.ast.service.ServiceCall;
+import com.bigdata.rdf.sparql.ast.service.ServiceCallCreateParams;
+import com.bigdata.rdf.sparql.ast.service.ServiceFactory;
+import com.bigdata.rdf.sparql.ast.service.ServiceRegistry;
+import com.bigdata.rdf.spo.ISPO;
+import com.bigdata.rdf.store.AbstractTripleStore;
+import com.bigdata.striterator.IChunkedOrderedIterator;
+
+import cutthecrap.utils.striterators.ICloseableIterator;
+
+/**
+ * Implements a "service" that resolves label like things in a way that doesn't
+ * change the cardinality of the result set. You can call it like this: <code>
+ * SELECT *
+ * WHERE {
+ * SERVICE wikibase:label.en.de.fr {
+ * wd:Q123 rdfs:label ?q123Label .
+ * wd:Q123 rdfs:altLabel ?q123Alt .
+ * wd:Q123 schema:description ?q123Desc .
+ * wd:Q321 rdf:label ?q321Label .
+ * }
+ * }
+ * </code> or like this:<code>
+ * SELECT ?sLabel ?sAltLabel ?sDescription ?oLabel
+ * WHERE {
+ * ?s wdt:P22 ?o .
+ * SERVICE wikibase:label.en.de.fr {
+ * }
+ * }
+ * </code>
+ * <p>
+ * If the label isn't available in any of the fallback languages it'll come
back
+ * as the entityId. Alt labels and descriptions just come back unbound if they
+ * don't exist. If multiple values are defined they come back as a comma
+ * separated list.
+ *
+ * <p>
+ * This works by resolving the label-like thing per incoming binding, one at a
+ * time. It would probably be faster to do something bulkish but we don't do
+ * that yet. The code to do the comma separated lists and entityIds is pretty
+ * simple once you've resolve the data.
+ * <p>
+ * The second invocation pattern works using {@code EmptyLabelServiceOptimizer}
+ * to inspect the query and automatically build the first form out of the
second
+ * form by inspecting the query's projection.
+ */
+public class LabelService implements ServiceFactory {
+ /**
+ * Options configuring this service as a native Blazegraph service.
+ */
+ private static final BigdataNativeServiceOptions SERVICE_OPTIONS = new
BigdataNativeServiceOptions();
+
+ /**
+ * Register the service so it is recognized by Blazegraph.
+ */
+ public static void register() {
+ ServiceRegistry.getInstance().setDefaultServiceFactory(
+ new
PrefixDelegatingServiceFactory(ServiceRegistry.getInstance().getDefaultServiceFactory(),
+ Ontology.LABEL, new LabelService()));
+ }
+
+ @Override
+ public IServiceOptions getServiceOptions() {
+ return SERVICE_OPTIONS;
+ }
+
+ @Override
+ public ServiceCall<?> create(ServiceCallCreateParams params) {
+ /*
+ * Luckily service calls are always pushed to the last operation in a
+ * query. We still check it and tell users we won't resolve labels for
+ * unbound subjects.
+ */
+ // TODO this whole class just throws RuntimeException instead of ??
+ return new LabelServiceCall(new
ResolutionContext(params.getTripleStore(), findLanguageFallbacks(params)),
+ findResolutions(params));
+ }
+
+ /**
+ * Resolve the language fallbacks from the statement pattern node in the
+ * query.
+ */
+ private Map<String, Integer> findLanguageFallbacks(ServiceCallCreateParams
params) {
+ String uri = params.getServiceURI().stringValue();
+ if (!uri.startsWith(Ontology.LABEL + ".")) {
+ throw new IllegalArgumentException("You must provide the label
service a list of languages.");
+ }
+ String fallbacks = uri.substring(Ontology.LABEL.length() + 1);
+ // TODO there has to be a better data structure for this.
+ /*
+ * Lucene has tons of things for this, but yeah. Maybe it doesn't
+ * matter.
+ */
+ Map<String, Integer> fallbacksMap = new HashMap<>();
+ String[] lang = fallbacks.split("\\.");
+ for (int i = 0; i < lang.length; i++) {
+ fallbacksMap.put(lang[i], i);
+ }
+ if (fallbacksMap.isEmpty()) {
+ throw new IllegalArgumentException("You must provide the label
service a list of languages.");
+ }
+ return fallbacksMap;
+ }
+
+ /**
+ * Create the resolutions list from the service call parameters.
+ */
+ private List<Resolution> findResolutions(ServiceCallCreateParams params) {
+ JoinGroupNode g = (JoinGroupNode)
params.getServiceNode().getGraphPattern();
+ List<Resolution> resolutions = new ArrayList<>(g.args().size());
+ for (BOp st : g.args()) {
+ resolutions.add(new Resolution((StatementPatternNode) st));
+ }
+ return resolutions;
+ }
+
+ /**
+ * Represents the call site in a particular SPARQL query.
+ */
+ @SuppressWarnings("checkstyle:visibilitymodifier")
+ private static class LabelServiceCall implements BigdataServiceCall {
+ /*
+ * Suppress VisibilityModifier check because members are package
private
+ * so non-static inner classes can access them without the messy
+ * accessor methods. This isn't an information leak because this class
+ * is already a private inner class.
+ */
+ /**
+ * The context in which the resolutions will be done.
+ */
+ final ResolutionContext context;
+ /**
+ * Things to resolve.
+ */
+ final List<Resolution> resolutions;
+
+ /**
+ * Build with all the right stuff resolved.
+ */
+ public LabelServiceCall(ResolutionContext context, List<Resolution>
resolutions) {
+ this.context = context;
+ this.resolutions = resolutions;
+ }
+
+ @Override
+ public IServiceOptions getServiceOptions() {
+ return SERVICE_OPTIONS;
+ }
+
+ @Override
+ public ICloseableIterator<IBindingSet> call(final IBindingSet[]
bindingSets) throws Exception {
+ return new Chunk(bindingSets);
+ }
+
+ /**
+ * A chunk of calls to resolve labels.
+ */
+ private class Chunk implements ICloseableIterator<IBindingSet> {
+ /**
+ * Binding sets being resolved in this chunk.
+ */
+ private final IBindingSet[] bindingSets;
+ /**
+ * Has this chunk been closed?
+ */
+ private boolean closed;
+ /**
+ * Index of the next binding set to handle when next is next
called.
+ */
+ private int i;
+
+ public Chunk(IBindingSet[] bindingSets) {
+ this.bindingSets = bindingSets;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !closed && i < bindingSets.length;
+ }
+
+ @Override
+ public IBindingSet next() {
+ IBindingSet binding = bindingSets[i++];
+ context.binding(binding);
+ for (Resolution resolution : resolutions) {
+ context.resolve(resolution);
+ }
+ return binding;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+ }
+ }
+
+ /**
+ * Description of a specific resolution request for the service. The
service
+ * can resolve many such requests at once.
+ */
+ @SuppressWarnings("rawtypes")
+ private static final class Resolution {
+ /**
+ * Subject of the service call.
+ */
+ private final IValueExpression subject;
+ /**
+ * URI for the label to resolve.
+ */
+ private final IValueExpression label;
+ /**
+ * The target variable to which to bind the label.
+ */
+ private final IVariable target;
+
+ private Resolution(StatementPatternNode st) {
+ subject = st.s().getValueExpression();
+ label = st.p().getValueExpression();
+ target = getVariableToBind(st);
+ }
+
+ /**
+ * Subject of the service call.
+ */
+ public IValueExpression subject() {
+ return subject;
+ }
+
+ /**
+ * URI for the label to resolve.
+ */
+ public IValueExpression labelType() {
+ return label;
+ }
+
+ /**
+ * The target variable to which to bind the label.
+ */
+ public IVariable target() {
+ return target;
+ }
+
+ /**
+ * Resolve the variable that needs to be bound from the statement
+ * pattern node in the query.
+ */
+ private IVariable<IV> getVariableToBind(StatementPatternNode st) {
+ try {
+ return ((VarNode) st.o()).getValueExpression();
+ } catch (ClassCastException e) {
+ throw new RuntimeException("Expected a variable in the object
position to which to bind the language.");
+ }
+ }
+ }
+
+ /**
+ * Context in which Resolutions are resolved. This only goes from subjects
+ * and label types to labels. It doesn't go from label types and label
+ * values to subjects. That wouldn't be an efficient process anyway even
+ * though it is technically possible.
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private static class ResolutionContext {
+ /**
+ * The TripleStore to resolve the BindingSets against.
+ */
+ private final AbstractTripleStore tripleStore;
+ /**
+ * The LexiconRelation for the TripleStore we're working with.
+ */
+ private final LexiconRelation lexiconRelation;
+ /**
+ * The language fallbacks as a map from language code to order of
+ * precidence.
+ */
+ private final Map<String, Integer> languageFallbacks;
+ /**
+ * List of labels with the best language. Cleared and rebuilt as on
+ * every new call to resolve.
+ */
+ private final List<IV> bestLabels = new ArrayList<>();
+ /**
+ * The binding currently being resolved.
+ */
+ private IBindingSet binding;
+ /**
+ * The subject for this resolution as resolved in this BindingSet.
+ */
+ private IV resolvedSubject;
+ /**
+ * The label type for the current resolution as resolved in this
+ * BindingSet.
+ */
+ private IV resolvedLabelType;
+ /**
+ * The IV the represents rdfs:label. Its built lazily when needed and
+ * cached.
+ */
+ private IV rdfsLabelIv;
+
+ public ResolutionContext(AbstractTripleStore tripleStore, Map<String,
Integer> languageFallbacks) {
+ this.tripleStore = tripleStore;
+ this.languageFallbacks = languageFallbacks;
+ lexiconRelation = tripleStore.getLexiconRelation();
+ }
+
+ /**
+ * Set the current BindingSet to be worked on.
+ */
+ public void binding(IBindingSet binding) {
+ this.binding = binding;
+ }
+
+ /**
+ * Resolve the target of the resolution in the current BindingSet.
+ */
+ public void resolve(Resolution resolution) {
+ resolvedSubject = resolveToIvOrError(resolution.subject(),
"subject");
+ resolvedLabelType = resolveToIvOrError(resolution.labelType(),
"label type");
+ // TODO this is one at a time - maybe a batch things?
+ fillBestLabels();
+ IV label = pickOrBuildBestLabel();
+ if (label != null) {
+ binding.set(resolution.target(), new Constant(label));
+ }
+ }
+
+ /**
+ * Gets the best label from a lookup. The best labels are put into the
+ * bestLabels list parameter. That parameter is cleared before the
+ * method starts and returning an empty list means there are no good
+ * labels.
+ */
+ private void fillBestLabels() {
+ IChunkedOrderedIterator<ISPO> lookup =
tripleStore.getAccessPath(resolvedSubject, resolvedLabelType, null)
+ .iterator();
+ try {
+ bestLabels.clear();
+ int bestLabelRank = Integer.MAX_VALUE;
+ while (lookup.hasNext()) {
+ ISPO spo = lookup.next();
+ IV o = spo.o();
+ if (!o.isLiteral()) {
+ // Not a literal, no chance its a label then
+ continue;
+ }
+ /*
+ * Hydrate all of the objects into language literals so we
+ * can check the language. This is slow because it has to
go
+ * to the term dictionary but there isn't anything we can
do
+ * about it for now.
+ */
+ Literal literal = (Literal) lexiconRelation.getTerm(o);
+ String language = literal.getLanguage();
+ if (language == null) {
+ // Not a language label, skip.
+ continue;
+ }
+ Integer languageOrdinal = languageFallbacks.get(language);
+ if (languageOrdinal == null) {
+ // Not a language the user wants
+ continue;
+ }
+ if (languageOrdinal == bestLabelRank) {
+ bestLabels.add(o);
+ }
+ if (languageOrdinal < bestLabelRank) {
+ bestLabelRank = languageOrdinal;
+ bestLabels.clear();
+ bestLabels.add(o);
+ }
+ }
+ } finally {
+ lookup.close();
+ }
+ }
+
+ /**
+ * By hook or by crook return a single IV for this resolution.
Processes
+ * bestLabels, so you'll have to call fillBestLabels before calling
+ * this. Options:
+ * <ul>
+ * <li>If there is a single label it returns it.
+ * <li>If there isn't a label it uses bestEffortLabel to mock up a
label
+ * <li>If there are multiple labels it uses joinLabels to smoosh them
+ * into a comma separated list.
+ * </ul>
+ */
+ private IV pickOrBuildBestLabel() {
+ switch (bestLabels.size()) {
+ case 1:
+ // Found a single label so we can just return it.
+ // This is probably the most common case.
+ return bestLabels.get(0);
+ case 0:
+ // Didn't find a real label so lets fake one up
+ return bestEffortLabel();
+ default:
+ return joinLabels();
+ }
+ }
+
+ /**
+ * Build a mock IV from a literal.
+ */
+ private IV mock(Literal literal) {
+ TermId mock = TermId.mockIV(VTE.LITERAL);
+ mock.setValue(lexiconRelation.getValueFactory().asValue(literal));
+ return mock;
+ }
+
+ /**
+ * Returns the IV to which expression is bound in the current context
or
+ * throws an error if it isn't bound.
+ */
+ private IV resolveToIvOrError(IValueExpression expression, String
nameOfExpression) {
+ Object resolved = expression.get(binding);
+ if (resolved == null) {
+ throw new RuntimeException(String.format(Locale.ROOT,
+ "Refusing to lookup labels for unknown %s (%s). That'd
be way way way inefficient.",
+ nameOfExpression, expression));
+ }
+ try {
+ return (IV) resolved;
+ } catch (ClassCastException e) {
+ throw new RuntimeException(String.format(Locale.ROOT,
+ "Expected %s (%s) to be bound to an IV but it
wasn't.", nameOfExpression, expression));
+ }
+ }
+
+ /**
+ * The IV the represents rdfs:label. Its built lazily when needed and
+ * cached.
+ */
+ public IV rdfsLabelIv() {
+ if (rdfsLabelIv == null) {
+ rdfsLabelIv = tripleStore.getVocabulary().get(RDFS.LABEL);
+ }
+ return rdfsLabelIv;
+ }
+
+ /**
+ * The WikibaseUris to use in this context.
+ */
+ private WikibaseUris uris() {
+ // TODO lookup wikibase host and default to wikidata
+ return WikibaseUris.WIKIDATA;
+ }
+
+ /**
+ * Build a label for something without a label. If the
resolvedLabelType
+ * is actually rdfs:label you'll get a nice Q1324 style label but if it
+ * isn't you'll get an empty string.
+ */
+ private IV bestEffortLabel() {
+ // Only rdfs:label gets the entity ID as the label
+ if (!rdfsLabelIv().equals(resolvedLabelType)) {
+ // Everything else gets the empty string
+ return null;
+ }
+ BigdataValue value = lexiconRelation.getTerm(resolvedSubject);
+ String bestEffortLabel = value.stringValue();
+ if (bestEffortLabel.startsWith(uris().entity())) {
+ bestEffortLabel =
bestEffortLabel.substring(uris().entity().length());
+ }
+ return mock(new LiteralImpl(bestEffortLabel));
+ }
+
+ /**
+ * Smoosh bestLabels into a comma separated list.
+ */
+ private IV joinLabels() {
+ // Found lots of labels so we should merge them into one.
+ // This is going to be common for alt labels
+ StringBuilder b = new StringBuilder();
+ String language = null;
+ boolean first = true;
+ for (IV label : bestLabels) {
+ Literal literal = (Literal) lexiconRelation.getTerm(label);
+ if (!first) {
+ b.append(", ");
+ } else {
+ first = false;
+ }
+ b.append(literal.stringValue());
+ if (language == null) {
+ language = literal.getLanguage();
+ }
+ }
+ return mock(new LiteralImpl(b.toString(), language));
+ }
+ }
+}
diff --git
a/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphStorageTestCase.java
b/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphStorageTestCase.java
new file mode 100644
index 0000000..567e536
--- /dev/null
+++
b/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphStorageTestCase.java
@@ -0,0 +1,116 @@
+package org.wikidata.query.rdf.blazegraph;
+
+import static
com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope.SUITE;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.runner.RunWith;
+
+import com.bigdata.bop.engine.QueryEngine;
+import com.bigdata.bop.fed.QueryEngineFactory;
+import com.bigdata.cache.SynchronizedHardReferenceQueueWithTimeout;
+import com.bigdata.journal.TemporaryStore;
+import com.bigdata.rdf.store.AbstractTripleStore;
+import com.bigdata.rdf.store.TempTripleStore;
+import com.carrotsearch.randomizedtesting.RandomizedRunner;
+import com.carrotsearch.randomizedtesting.RandomizedTest;
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
+
+/**
+ * Randomized test that creates a triple store.
+ *
+ * <p>
+ * We have to take a number of actions to make RandomizedRunner compatible with
+ * Blazegraph:
+ * <ul>
+ * <li>Switch the ThreadLeakScope to SUITE because there are threads that
+ * survive across tests
+ * <li>Create a temporary store that is shared for all test methods that holds
+ * multiple triple stores
+ * <li>Create a new triple store per test method (lazily)
+ * </ul>
+ */
+@RunWith(RandomizedRunner.class)
+@ThreadLeakScope(SUITE)
+public class AbstractRandomizedBlazegraphStorageTestCase extends
RandomizedTest {
+
+ /**
+ * Holds all the triples stores. Initialized once per test class.
+ */
+ private static TemporaryStore temporaryStore;
+ /**
+ * Triple store for the current test method. Lazily initialized.
+ */
+ private AbstractTripleStore store;
+
+ /**
+ * Get a TemporaryStore. Lazily initialized once per test class.
+ */
+ private static TemporaryStore temporaryStore() {
+ if (temporaryStore != null) {
+ return temporaryStore;
+ }
+ /*
+ * Initializing the temporary store replaces RandomizedRunner's
+ * painstakingly applied UncaughtExceptionHandler. That is bad so we
+ * replace it.
+ */
+ UncaughtExceptionHandler uncaughtExceptionHandler =
Thread.getDefaultUncaughtExceptionHandler();
+ temporaryStore = new TemporaryStore();
+ Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+ return temporaryStore;
+ }
+
+ /**
+ * Get a triple store. Lazily initialized once per test method.
+ */
+ protected AbstractTripleStore store() {
+ if (store != null) {
+ return store;
+ }
+ Properties properties = new Properties();
+
properties.setProperty("com.bigdata.rdf.store.AbstractTripleStore.vocabularyClass",
+ WikibaseVocabulary.V001.class.getName());
+
properties.setProperty("com.bigdata.rdf.store.AbstractTripleStore.inlineURIFactory",
+ WikibaseInlineUriFactory.class.getName());
+ store = new TempTripleStore(temporaryStore(), properties, null);
+ return store;
+ }
+
+ /**
+ * Close the temporary store used by this test.
+ * @throws InterruptedException if the executor service fails to await
termination
+ */
+ @AfterClass
+ public static void closeTemporaryStore() throws InterruptedException {
+ if (temporaryStore == null) {
+ return;
+ }
+ ExecutorService executorService = temporaryStore.getExecutorService();
+ temporaryStore.close();
+ QueryEngine queryEngine =
QueryEngineFactory.getExistingQueryController(temporaryStore);
+ if (queryEngine != null) {
+ queryEngine.shutdownNow();
+ }
+ SynchronizedHardReferenceQueueWithTimeout.stopStaleReferenceCleaner();
+ executorService.awaitTermination(20, TimeUnit.SECONDS);
+ temporaryStore = null;
+ }
+
+ /**
+ * Close the triple store used by the test that just finished.
+ */
+ @After
+ public void closeStore() {
+ if (store == null) {
+ return;
+ }
+ store.close();
+ store = null;
+ }
+}
diff --git
a/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphTestBase.java
b/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphTestBase.java
index 86b168b..18b7a4a 100644
---
a/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphTestBase.java
+++
b/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/AbstractRandomizedBlazegraphTestBase.java
@@ -1,62 +1,36 @@
package org.wikidata.query.rdf.blazegraph;
-import static
com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope.SUITE;
-
-import java.lang.Thread.UncaughtExceptionHandler;
import java.math.BigInteger;
-import java.util.Properties;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.runner.RunWith;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.IntegerLiteralImpl;
import org.openrdf.model.impl.URIImpl;
+import org.openrdf.query.MalformedQueryException;
+import org.openrdf.query.QueryEvaluationException;
+import org.openrdf.query.TupleQueryResult;
+import org.openrdf.query.algebra.evaluation.QueryBindingSet;
+import org.wikidata.query.rdf.common.uri.Ontology;
import org.wikidata.query.rdf.common.uri.WikibaseUris;
import org.wikidata.query.rdf.common.uri.WikibaseUris.PropertyType;
-import com.bigdata.cache.SynchronizedHardReferenceQueueWithTimeout;
-import com.bigdata.journal.TemporaryStore;
import com.bigdata.rdf.model.BigdataStatement;
-import com.bigdata.rdf.store.ITripleStore;
-import com.bigdata.rdf.store.TempTripleStore;
-import com.carrotsearch.randomizedtesting.RandomizedRunner;
-import com.carrotsearch.randomizedtesting.RandomizedTest;
-import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
+import com.bigdata.rdf.sail.sparql.Bigdata2ASTSPARQLParser;
+import com.bigdata.rdf.sparql.ast.ASTContainer;
+import com.bigdata.rdf.sparql.ast.eval.ASTEvalHelper;
/**
- * Randomized test that can create a triple store.
- *
- * <p>
- * We have to take a number of actions to make RandomizedRunner compatible with
- * Blazegraph:
- * <ul>
- * <li>Wwitch the ThreadLeakScope to SUITE because there are threads that
- * survive across tests
- * <li>Create a temporary store that is shared for all test methods that holds
- * multiple triple stores
- * <li>Create a triple store per test method
- * </ul>
+ * Base class for tests that need to interact with a temporary triple store.
All
+ * the triple store creation logic lives in the parent class. This class just
+ * has convenient utilities to help with things like adding data and running
+ * queries.
*/
-@RunWith(RandomizedRunner.class)
-@ThreadLeakScope(SUITE)
-public class AbstractRandomizedBlazegraphTestBase extends RandomizedTest {
- /**
- * Holds all the triples stores. Initialized once per test class.
- */
- private static TemporaryStore temporaryStore;
+public class AbstractRandomizedBlazegraphTestBase extends
AbstractRandomizedBlazegraphStorageTestCase {
/**
* Which uris this test uses.
*/
private WikibaseUris uris = WikibaseUris.WIKIDATA;
- /**
- * Triple store for the current test method. Lazily initialized.
- */
- private ITripleStore store;
/**
* The uris this test uses.
@@ -66,19 +40,23 @@
}
/**
- * Get a triple store. Lazily initialized once per test method.
+ * Run a query.
*/
- protected ITripleStore store() {
- if (store != null) {
- return store;
+ protected TupleQueryResult query(String query) {
+ try {
+ ASTContainer astContainer = new
Bigdata2ASTSPARQLParser(store()).parseQuery2(query, null);
+
+ return ASTEvalHelper.evaluateTupleQuery(store(), astContainer, new
QueryBindingSet());
+ } catch (MalformedQueryException | QueryEvaluationException e) {
+ throw new RuntimeException(e);
}
- Properties properties = new Properties();
-
properties.setProperty("com.bigdata.rdf.store.AbstractTripleStore.vocabularyClass",
- WikibaseVocabulary.V001.class.getName());
-
properties.setProperty("com.bigdata.rdf.store.AbstractTripleStore.inlineURIFactory",
- WikibaseInlineUriFactory.class.getName());
- store = new TempTripleStore(temporaryStore(), properties, null);
- return store;
+ }
+
+ /**
+ * Add a triple to the store.
+ */
+ protected void add(Object s, Object p, Object o) {
+ store().addStatement((Resource) convert(s), (URI) convert(p),
convert(o), null);
}
/**
@@ -105,12 +83,13 @@
}
if (o instanceof String) {
String s = (String) o;
+ s = s.replaceFirst("^ontology:", Ontology.NAMESPACE);
s = s.replaceFirst("^wdata:", uris.entityData());
s = s.replaceFirst("^wd:", uris.entity());
s = s.replaceFirst("^wds:", uris.statement());
s = s.replaceFirst("^wdv:", uris.value());
s = s.replaceFirst("^wdref:", uris.reference());
- for (PropertyType p: PropertyType.values()) {
+ for (PropertyType p : PropertyType.values()) {
s = s.replaceFirst("^" + p.prefix() + ":", uris.property(p));
}
return new URIImpl(s);
@@ -121,49 +100,12 @@
throw new RuntimeException("No idea how to convert " + o + " to a
value. Its a " + o.getClass() + ".");
}
- /**
- * Get a TemporaryStore. Lazily initialized once per test class.
+ /*
+ * Initialize the Wikibase services including shutting off remote SERVICE
+ * calls and turning on label service calls.
*/
- private static TemporaryStore temporaryStore() {
- if (temporaryStore != null) {
- return temporaryStore;
- }
- /*
- * Initializing the temporary store replaces RandomizedRunner's
- * painstakingly applied UncaughtExceptionHandler. That is bad so we
- * replace it.
- */
- UncaughtExceptionHandler uncaughtExceptionHandler =
Thread.getDefaultUncaughtExceptionHandler();
- temporaryStore = new TemporaryStore();
- Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
- return temporaryStore;
- }
-
- /**
- * Close the triple store used by the test that just finished.
- */
- @After
- public void closeStore() {
- if (store == null) {
- return;
- }
- store.close();
- store = null;
- }
-
- /**
- * Close the temporary store used by this test.
- * @throws InterruptedException if the executor service fails to await
termination
- */
- @AfterClass
- public static void closeTemporaryStore() throws InterruptedException {
- if (temporaryStore == null) {
- return;
- }
- ExecutorService executorService = temporaryStore.getExecutorService();
- temporaryStore.close();
- SynchronizedHardReferenceQueueWithTimeout.stopStaleReferenceCleaner();
- executorService.awaitTermination(20, TimeUnit.SECONDS);
- temporaryStore = null;
+ static {
+ WikibaseContextListener.initializeServices();
+ System.setProperty("ASTOptimizerClass",
WikibaseOptimizers.class.getName());
}
}
diff --git
a/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/label/LabelServiceUnitTest.java
b/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/label/LabelServiceUnitTest.java
new file mode 100644
index 0000000..fbd4441
--- /dev/null
+++
b/blazegraph/src/test/java/org/wikidata/query/rdf/blazegraph/label/LabelServiceUnitTest.java
@@ -0,0 +1,176 @@
+package org.wikidata.query.rdf.blazegraph.label;
+
+import static org.hamcrest.Matchers.both;
+import static org.hamcrest.Matchers.containsString;
+import static org.wikidata.query.rdf.test.Matchers.assertResult;
+import static org.wikidata.query.rdf.test.Matchers.binds;
+
+import java.util.Locale;
+
+import org.apache.log4j.Logger;
+import org.junit.Test;
+import org.openrdf.model.impl.LiteralImpl;
+import org.openrdf.query.QueryEvaluationException;
+import org.openrdf.query.TupleQueryResult;
+import org.wikidata.query.rdf.blazegraph.AbstractRandomizedBlazegraphTestBase;
+import org.wikidata.query.rdf.common.uri.Ontology;
+import org.wikidata.query.rdf.common.uri.RDFS;
+import org.wikidata.query.rdf.common.uri.SKOS;
+import org.wikidata.query.rdf.common.uri.SchemaDotOrg;
+
+public class LabelServiceUnitTest extends AbstractRandomizedBlazegraphTestBase
{
+ private static final Logger log =
Logger.getLogger(LabelServiceUnitTest.class);
+
+ @Test
+ public void labelOverConstant() throws QueryEvaluationException {
+ simpleLabelLookupTestCase(null, "wd:Q123");
+ }
+
+ @Test
+ public void labelOverVariable() throws QueryEvaluationException {
+ add("ontology:dummy", "ontology:dummy", "wd:Q123");
+ simpleLabelLookupTestCase("ontology:dummy ontology:dummy ?o.", "?o");
+ }
+
+ @Test
+ public void chain() throws QueryEvaluationException {
+ add("ontology:dummy", "ontology:dummy", "wd:Q1");
+ add("wd:Q1", "ontology:dummy", "wd:Q2");
+ add("wd:Q2", "ontology:dummy", "wd:Q3");
+ add("wd:Q3", "ontology:dummy", "wd:Q4");
+ add("wd:Q4", "ontology:dummy", "wd:Q123");
+ simpleLabelLookupTestCase(
+ "ontology:dummy
ontology:dummy/ontology:dummy/ontology:dummy/ontology:dummy/ontology:dummy
?o.", "?o");
+ }
+
+ @Test
+ public void many() throws QueryEvaluationException {
+ for (int i = 1; i <= 10; i++) {
+ addSimpleLabels("Q" + i);
+ add("ontology:dummy", "ontology:dummy", "wd:Q" + i);
+ }
+ TupleQueryResult result = lookupLabel("ontology:dummy ontology:dummy
?o", "en", "?o", "rdfs:label");
+ for (int i = 1; i <= 10; i++) {
+ assertTrue(result.hasNext());
+ assertThat(result.next(), binds("oLabel", new LiteralImpl("in en",
"en")));
+ }
+ assertFalse(result.hasNext());
+ }
+
+ @Test
+ public void labelOverUnboundSubjectIsError() {
+ try {
+ lookupLabel(null, "en", "?s", "rdfs:label").next();
+ fail();
+ } catch (QueryEvaluationException e) {
+ assertThat(
+ e.getMessage(),
+ containsString("Refusing to lookup labels for unknown
subject (s). That'd be way way way inefficient."));
+ }
+ }
+
+ @Test
+ public void noDotIsOkErrorMessage() {
+ try {
+ StringBuilder query = Ontology.prefix(new StringBuilder());
+ query.append("SELECT *\n");
+ query.append("WHERE {\n");
+ query.append(" SERVICE ontology:label {}\n");
+ query.append("}\n");
+ query(query.toString());
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("must provide the label
service a list of languages"));
+ }
+ }
+
+ @Test
+ public void deeperServiceCall() {
+ add("ontology:dummy", "ontology:dummy", "wd:Q1");
+ add("wd:Q1", "ontology:dummy", "wd:Q123");
+ addSimpleLabels("Q123");
+ StringBuilder query = uris().prefixes(Ontology.prefix(new
StringBuilder()));
+ query.append("SELECT ?pLabel\n");
+ query.append("WHERE {\n");
+ query.append(" ontology:dummy ontology:dummy ?s .\n");
+ query.append(" {\n");
+ query.append(" ?s ontology:dummy ?p .\n");
+ query.append(" SERVICE ontology:label.en {}\n");
+ query.append(" }\n");
+ query.append("}\n");
+ assertResult(query(query.toString()), binds("pLabel", "in en", "en"));
+ }
+
+ private void simpleLabelLookupTestCase(String extraQuery, String
subjectInQuery) throws QueryEvaluationException {
+ addSimpleLabels("Q123");
+ slltcp(extraQuery, subjectInQuery, "en", "in en", "en", "alt label in
en, alt label in en2", "en");
+ slltcp(extraQuery, subjectInQuery, "ru", "in ru", "ru", null, null);
+ slltcp(extraQuery, subjectInQuery, "dummy", "Q123", null, null, null);
+ slltcp(extraQuery, subjectInQuery, "dummy.en", "in en", "en", "alt
label in en, alt label in en2", "en");
+ slltcp(extraQuery, subjectInQuery, "en.ru", "in en", "en", "alt label
in en, alt label in en2", "en");
+ slltcp(extraQuery, subjectInQuery, "ru.de", "in ru", "ru", "alt label
in de", "de");
+ }
+
+ private void slltcp(String extraQuery, String subjectInQuery, String
language, String labelText,
+ String labelLanguage, String altLabelText, String
altLabelLanguage) throws QueryEvaluationException {
+ assertResult(
+ lookupLabel(extraQuery, language, subjectInQuery,
"rdfs:label", "skos:altLabel"),
+ both(binds(labelName(subjectInQuery, "rdfs:label"), labelText,
labelLanguage)).and(
+ binds(labelName(subjectInQuery, "skos:altLabel"),
altLabelText, altLabelLanguage)));
+
+ }
+
+ private TupleQueryResult lookupLabel(String otherQuery, String
inLanguages, String subject, String... labelTypes)
+ throws QueryEvaluationException {
+ if (inLanguages.indexOf(' ') >= 0) {
+ throw new IllegalArgumentException("Languages cannot contain a
space or that'd make an invalid query.");
+ }
+ StringBuilder query = uris().prefixes(
+
SchemaDotOrg.prefix(SKOS.prefix(RDFS.prefix(Ontology.prefix(new
StringBuilder())))));
+ query.append("SELECT");
+ for (String labelType : labelTypes) {
+ query.append(" ?").append(labelName(subject, labelType));
+ }
+ query.append('\n');
+ query.append("WHERE {\n");
+ if (otherQuery != null) {
+ query.append(otherQuery).append("\n");
+ }
+ query.append(" SERVICE ontology:label.").append(inLanguages).append("
{\n");
+ if (subject.contains(":") || rarely()) {
+ // We rarely explicitly specify the labels to load
+ for (String labelType : labelTypes) {
+ query.append(" ").append(subject).append("
").append(labelType).append(" ?")
+ .append(labelName(subject, labelType)).append(" .\n");
+ }
+ }
+ query.append(" }\n");
+ query.append("}\n");
+ if (log.isDebugEnabled()) {
+ log.debug("Query: " + query);
+ }
+ return query(query.toString());
+ }
+
+ private void addSimpleLabels(String entity) {
+ for (String language : new String[] {"en", "de", "ru"}) {
+ add("wd:" + entity, RDFS.LABEL, new LiteralImpl("in " + language,
language));
+ }
+ add("wd:" + entity, SKOS.ALT_LABEL, new LiteralImpl("alt label in en",
"en"));
+ add("wd:" + entity, SKOS.ALT_LABEL, new LiteralImpl("alt label in
en2", "en"));
+ add("wd:" + entity, SKOS.ALT_LABEL, new LiteralImpl("alt label in de",
"de"));
+ for (String language : new String[] {"en", "de", "ru"}) {
+ add("wd:" + entity, SchemaDotOrg.DESCRIPTION, new
LiteralImpl("description in " + language, language));
+ }
+ }
+
+ private String labelName(String subjectName, String labelType) {
+ int start = labelType.indexOf(':') + 1;
+ if (subjectName.contains(":")) {
+ return labelType.substring(start);
+ }
+ return subjectName.substring(1) + labelType.substring(start, start +
1).toUpperCase(Locale.ROOT)
+ + labelType.substring(start + 1);
+ }
+
+}
diff --git
a/common/src/main/java/org/wikidata/query/rdf/common/uri/Ontology.java
b/common/src/main/java/org/wikidata/query/rdf/common/uri/Ontology.java
index 2b20ea6..a0fae60 100644
--- a/common/src/main/java/org/wikidata/query/rdf/common/uri/Ontology.java
+++ b/common/src/main/java/org/wikidata/query/rdf/common/uri/Ontology.java
@@ -72,6 +72,11 @@
* them.
*/
public static final String DEPRECATED_RANK = NAMESPACE + "DeprecatedRank";
+ /**
+ * Prefix for the label service. Not used in that data - just for SPARQL
+ * queries.
+ */
+ public static final String LABEL = NAMESPACE + "label";
/**
* Predicates used to describe a time.
diff --git a/common/src/main/java/org/wikidata/query/rdf/common/uri/RDFS.java
b/common/src/main/java/org/wikidata/query/rdf/common/uri/RDFS.java
index 9302dc9..021f7c4 100644
--- a/common/src/main/java/org/wikidata/query/rdf/common/uri/RDFS.java
+++ b/common/src/main/java/org/wikidata/query/rdf/common/uri/RDFS.java
@@ -18,7 +18,7 @@
/**
* Add the rdfs: prefix to the query.
*/
- public static StringBuilder prefixes(StringBuilder query) {
+ public static StringBuilder prefix(StringBuilder query) {
return query.append("PREFIX rdfs: <").append(NAMESPACE).append(">\n");
}
diff --git a/common/src/main/java/org/wikidata/query/rdf/common/uri/SKOS.java
b/common/src/main/java/org/wikidata/query/rdf/common/uri/SKOS.java
index 0795f4c..997b8b8 100644
--- a/common/src/main/java/org/wikidata/query/rdf/common/uri/SKOS.java
+++ b/common/src/main/java/org/wikidata/query/rdf/common/uri/SKOS.java
@@ -19,6 +19,13 @@
public static final String ALT_LABEL = NAMESPACE + "altLabel";
/**
+ * Adds the skos: prefix to the query.
+ */
+ public static StringBuilder prefix(StringBuilder query) {
+ return query.append("PREFIX skos: <").append(NAMESPACE).append(">\n");
+ }
+
+ /**
* Utility class uncallable constructor.
*/
private SKOS() {
diff --git a/dist/pom.xml b/dist/pom.xml
index ad277e9..d046346 100644
--- a/dist/pom.xml
+++ b/dist/pom.xml
@@ -29,7 +29,6 @@
<dependency>
<groupId>org.wikidata.query.rdf</groupId>
<artifactId>common</artifactId>
- <version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.wikidata.query.rdf</groupId>
diff --git a/dist/src/config/web.xml b/dist/src/config/web.xml
index 222c477..7d0febd 100644
--- a/dist/src/config/web.xml
+++ b/dist/src/config/web.xml
@@ -55,11 +55,14 @@
<param-name>queryTimeout</param-name>
<param-value>30000</param-value>
</context-param>
-- <context-param>
+- <!-- We can't use the builtin whitelist because it breaks label relation.
But we enable our own whitelist so its all good. -->
+ <!--
+ <context-param>
<description>List of allowed services.</description>
<param-name>serviceWhitelist</param-name>
<param-value>http://www.bigdata.com/rdf#describe</param-value>
</context-param>
+ -->
<listener>
<listener-class>org.wikidata.query.rdf.blazegraph.WikibaseContextListener</listener-class>
</listener>
diff --git a/dist/src/script/runBlazegraph.sh b/dist/src/script/runBlazegraph.sh
index cb33766..6a64b5a 100755
--- a/dist/src/script/runBlazegraph.sh
+++ b/dist/src/script/runBlazegraph.sh
@@ -27,6 +27,7 @@
-Dorg.eclipse.jetty.server.Request.maxFormContentSize=20000000 \
-Dcom.bigdata.rdf.sparql.ast.QueryHints.analytic=true \
-Dcom.bigdata.rdf.sparql.ast.QueryHints.analyticMaxMemoryPerQuery=1073741824 \
+ -DASTOptimizerClass=org.wikidata.query.rdf.blazegraph.WikibaseOptimizers \
-jar jetty-runner*.jar \
--port $PORT \
--path /$CONTEXT \
diff --git a/pom.xml b/pom.xml
index 1a6a72a..49237a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
<packaging>pom</packaging>
<modules>
+ <module>testTools</module>
<module>common</module>
<module>blazegraph</module>
<module>tools</module>
@@ -61,9 +62,9 @@
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
- <id>ossrh</id>
- <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
- </repository>
+ <id>ossrh</id>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
</distributionManagement>
<build>
@@ -190,20 +191,19 @@
</excludes>
<!-- Use as many JVMs as the test runner wants to use. -->
<parallelism>auto</parallelism>
- <!-- The listeners and balancers elements do three things:
- 1. Control how output is formatted during tests
- 2. Dump the report to the right spot
- 3. Log an "execution-hints" file that is used to balance the
load when tests are forked. -->
+ <!-- The listeners and balancers elements do three things: 1.
Control how output is formatted during tests
+ 2. Dump the report to the right spot 3. Log an
"execution-hints" file that is used to balance the load when tests are forked.
-->
<listeners>
<report-ant-xml mavenExtensions="true"
dir="${project.build.directory}/surefire-reports" />
<report-text showThrowable="true" showStackTraces="true"
showOutput="onerror"
showStatusOk="false" showStatusError="true"
showStatusFailure="true" showStatusIgnored="true"
showSuiteSummary="true" timestamps="true" />
- <report-execution-times historyLength="20"
file="${basedir}/.execution-hints-${project.version}.log" />
+ <report-execution-times historyLength="20"
+ file="${basedir}/.execution-hints-${project.version}.log"
/>
</listeners>
<balancers>
<execution-times>
- <fileset dir="${basedir}"
includes="${basedir}/.execution-hints-${project.version}.log"/>
+ <fileset dir="${basedir}"
includes="${basedir}/.execution-hints-${project.version}.log" />
</execution-times>
</balancers>
</configuration>
@@ -258,7 +258,7 @@
</goals>
<configuration>
<quiet>true</quiet>
- <!-- We don't want Java 8's strict javadoc checking so we use
this trick borrowed from dropwizard to turn
+ <!-- We don't want Java 8's strict javadoc checking so we use
this trick borrowed from dropwizard to turn
it off -->
<additionalparam>${javadoc.doclint.none}</additionalparam>
</configuration>
@@ -327,7 +327,7 @@
<signaturesFile>${project.parent.basedir}/src/build/forbidden/core.txt</signaturesFile>
</signaturesFiles>
<excludes>
- <!-- Some portions of the project need access to System.out
and things so we contain them in inner classes
+ <!-- Some portions of the project need access to System.out
and things so we contain them in inner classes
of this form. -->
<exclude>**/*$ForbiddenOk**.class</exclude>
</excludes>
@@ -351,36 +351,63 @@
</plugins>
</build>
+ <dependencyManagement>
+ <!-- Decalare the default parameters for some dependencies. -->
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>18.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wikidata.query.rdf</groupId>
+ <artifactId>common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.openrdf.sesame</groupId>
+ <artifactId>sesame-query</artifactId>
+ <version>${sesame.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.bigdata</groupId>
+ <artifactId>bigdata</artifactId>
+ <version>${blazegraph.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.carrotsearch.randomizedtesting</groupId>
+ <artifactId>randomizedtesting-runner</artifactId>
+ <version>2.1.13</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.9.5</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
<dependencies>
- <dependency>
- <!-- Everyone needs Guava! -->
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>18.0</version>
- </dependency>
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-all</artifactId>
- <version>1.3</version>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.12</version>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>com.carrotsearch.randomizedtesting</groupId>
<artifactId>randomizedtesting-runner</artifactId>
- <version>2.1.13</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-all</artifactId>
- <version>1.9.5</version>
- <scope>test</scope>
</dependency>
</dependencies>
@@ -492,25 +519,25 @@
</build>
</profile>
<profile>
- <id>release</id>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-gpg-plugin</artifactId>
- <version>1.5</version>
- <executions>
- <execution>
- <id>sign-artifacts</id>
- <phase>verify</phase>
- <goals>
- <goal>sign</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
+ <id>release</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.5</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</profile>
</profiles>
</project>
diff --git a/src/build/forbidden/all.txt b/src/build/forbidden/all.txt
index 24f4d6b..c2a4111 100644
--- a/src/build/forbidden/all.txt
+++ b/src/build/forbidden/all.txt
@@ -3,7 +3,3 @@
@defaultMessage Convert to URI
java.net.URL#getPath()
java.net.URL#getFile()
-
-@defaultMessage These throw out the stack trace so are almost always wrong
-java.lang.Throwable#getMessage()
-java.lang.Throwable#toString()
diff --git a/src/build/forbidden/core.txt b/src/build/forbidden/core.txt
index 568c3c0..8f4926b 100644
--- a/src/build/forbidden/core.txt
+++ b/src/build/forbidden/core.txt
@@ -33,8 +33,9 @@
@defaultMessage Please do not try to stop the world
java.lang.System#gc()
-@defaultMessage Use Long.compare instead we are on Java7
-com.google.common.primitives.Longs#compare(long,long)
+# Disabled for a bit because it causes compilation to fail on things that
don't depend on guava
+#@defaultMessage Use Long.compare instead we are on Java7
+#com.google.common.primitives.Longs#compare(long,long)
@defaultMessage Use Channels.* methods to write to channels. Do not write
directly.
java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer)
@@ -45,3 +46,7 @@
java.nio.channels.ScatteringByteChannel#read(java.nio.ByteBuffer[])
java.nio.channels.ScatteringByteChannel#read(java.nio.ByteBuffer[], int, int)
java.nio.channels.FileChannel#read(java.nio.ByteBuffer, long)
+
+@defaultMessage These throw out the stack trace so are almost always wrong
+java.lang.Throwable#getMessage()
+java.lang.Throwable#toString()
diff --git a/testTools/pom.xml b/testTools/pom.xml
new file mode 100644
index 0000000..870c78f
--- /dev/null
+++ b/testTools/pom.xml
@@ -0,0 +1,81 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.wikidata.query.rdf</groupId>
+ <artifactId>parent</artifactId>
+ <version>0.0.3-SNAPSHOT</version>
+ </parent>
+ <artifactId>testTools</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Wikidata Query RDF Testing Tools</name>
+ <description>Tools for testing the rest of the project.</description>
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.wikidata.query.rdf</groupId>
+ <artifactId>common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.openrdf.sesame</groupId>
+ <artifactId>sesame-query</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.carrotsearch.randomizedtesting</groupId>
+ <artifactId>randomizedtesting-runner</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>com.carrotsearch.randomizedtesting</groupId>
+ <artifactId>junit4-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/testTools/src/main/java/org/wikidata/query/rdf/test/Matchers.java
b/testTools/src/main/java/org/wikidata/query/rdf/test/Matchers.java
new file mode 100644
index 0000000..fc3f466
--- /dev/null
+++ b/testTools/src/main/java/org/wikidata/query/rdf/test/Matchers.java
@@ -0,0 +1,189 @@
+package org.wikidata.query.rdf.test;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.wikidata.query.rdf.test.StatementHelper.uri;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.openrdf.model.Literal;
+import org.openrdf.model.Statement;
+import org.openrdf.model.URI;
+import org.openrdf.model.impl.LiteralImpl;
+import org.openrdf.query.Binding;
+import org.openrdf.query.BindingSet;
+import org.openrdf.query.QueryEvaluationException;
+import org.openrdf.query.TupleQueryResult;
+import org.wikidata.query.rdf.common.uri.WikibaseUris;
+import org.wikidata.query.rdf.common.uri.WikibaseUris.PropertyType;
+
+/**
+ * Useful matchers for RDF.
+ */
+public final class Matchers {
+ /**
+ * Check a binding to a uri.
+ */
+ public static Matcher<BindingSet> binds(String name, String value) {
+ if (value.startsWith("P")) {
+ value = WikibaseUris.WIKIDATA.property(PropertyType.CLAIM) + value;
+ }
+ return new BindsMatcher<URI>(name, equalTo(uri(value)));
+ }
+
+ /**
+ * Check binding to specific class.
+ */
+ public static Matcher<BindingSet> binds(String name, Class<?> value) {
+ return new BindsMatcher<Object>(name, instanceOf(value));
+ }
+
+ /**
+ * Check a binding to a value.
+ */
+ public static <V> Matcher<BindingSet> binds(String name, V value) {
+ return new BindsMatcher<V>(name, equalTo(value));
+ }
+
+ /**
+ * Check a binding to a language string.
+ */
+ public static Matcher<BindingSet> binds(String name, String str, String
language) {
+ if (str == null && language == null) {
+ return notBinds(name);
+ }
+ return new BindsMatcher<Literal>(name, equalTo((Literal) new
LiteralImpl(str, language)));
+ }
+
+ /**
+ * Check that a binding isn't bound.
+ */
+ public static Matcher<BindingSet> notBinds(String name) {
+ return new NotBindsMatcher(name);
+ }
+
+ /**
+ * Construct subject, predicate, and object matchers for a bunch of
+ * statements.
+ */
+ @SuppressWarnings("unchecked")
+ public static Matcher<BindingSet>[]
subjectPredicateObjectMatchers(Iterable<Statement> statements) {
+ List<Matcher<? super BindingSet>> matchers = new ArrayList<>();
+ for (Statement statement : statements) {
+ matchers.add(allOf(//
+ binds("s", statement.getSubject()), //
+ binds("p", statement.getPredicate()), //
+ binds("o", statement.getObject()) //
+ ));
+ }
+ return (Matcher<BindingSet>[]) matchers.toArray(new
Matcher<?>[matchers.size()]);
+ }
+
+ /**
+ * Assert that a result set contains some result bindings. Not a matcher
+ * because it modifies the result by iterating it.
+ */
+ @SafeVarargs
+ public static void assertResult(TupleQueryResult result,
Matcher<BindingSet>... bindingMatchers) {
+ try {
+ int position = 0;
+ for (Matcher<BindingSet> bindingMatcher : bindingMatchers) {
+ assertTrue("There should be at least " + position + "
results", result.hasNext());
+ assertThat(result.next(), bindingMatcher);
+ position++;
+ }
+ assertFalse("There should be no more than " + position + "
result", result.hasNext());
+ result.close();
+ } catch (QueryEvaluationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Checks bindings.
+ *
+ * @param <V> type of the binding result
+ */
+ private static class BindsMatcher<V> extends TypeSafeMatcher<BindingSet> {
+ /**
+ * Name of the binding to check.
+ */
+ private final String name;
+ /**
+ * Delegate matcher for the bound value.
+ */
+ private final Matcher<V> valueMatcher;
+
+ public BindsMatcher(String name, Matcher<V> valueMatcher) {
+ this.name = name;
+ this.valueMatcher = valueMatcher;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("contains a binding
from").appendValue(name).appendText("to")
+ .appendDescriptionOf(valueMatcher);
+ }
+
+ @Override
+ protected void describeMismatchSafely(BindingSet item, Description
mismatchDescription) {
+ Binding binding = item.getBinding(name);
+ if (binding == null) {
+ mismatchDescription.appendText("but did not contain such a
binding");
+ return;
+ }
+ mismatchDescription.appendText("instead it was bound
to").appendValue(binding.getValue());
+ }
+
+ @Override
+ protected boolean matchesSafely(BindingSet item) {
+ Binding binding = item.getBinding(name);
+ if (binding == null) {
+ return false;
+ }
+ return valueMatcher.matches(binding.getValue());
+ }
+ }
+
+ /**
+ * Checks that a name isn't bound.
+ */
+ private static class NotBindsMatcher extends TypeSafeMatcher<BindingSet> {
+ /**
+ * Name of the binding to check.
+ */
+ private final String name;
+
+ public NotBindsMatcher(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("does not contain a binding
for").appendValue(name);
+ }
+
+ @Override
+ protected void describeMismatchSafely(BindingSet item, Description
mismatchDescription) {
+ Binding binding = item.getBinding(name);
+ mismatchDescription.appendText("instead it was bound
to").appendValue(binding.getValue());
+ }
+
+ @Override
+ protected boolean matchesSafely(BindingSet item) {
+ return item.getBinding(name) == null;
+ }
+ }
+
+ private Matchers() {
+ // Utility constructor
+ }
+}
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/StatementHelper.java
b/testTools/src/main/java/org/wikidata/query/rdf/test/StatementHelper.java
similarity index 98%
rename from tools/src/test/java/org/wikidata/query/rdf/tool/StatementHelper.java
rename to
testTools/src/main/java/org/wikidata/query/rdf/test/StatementHelper.java
index fb44b4c..af81124 100644
--- a/tools/src/test/java/org/wikidata/query/rdf/tool/StatementHelper.java
+++ b/testTools/src/main/java/org/wikidata/query/rdf/test/StatementHelper.java
@@ -1,4 +1,4 @@
-package org.wikidata.query.rdf.tool;
+package org.wikidata.query.rdf.test;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomInt;
import static
com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
diff --git a/tools/pom.xml b/tools/pom.xml
index f71395c..1804527 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -21,9 +21,12 @@
<dependencies>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.wikidata.query.rdf</groupId>
<artifactId>common</artifactId>
- <version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.lexicalscope.jewelcli</groupId>
@@ -68,7 +71,6 @@
<dependency>
<groupId>org.openrdf.sesame</groupId>
<artifactId>sesame-query</artifactId>
- <version>${sesame.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
@@ -82,9 +84,15 @@
<version>2.1.1</version>
</dependency>
<dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.4</version>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.4</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wikidata.query.rdf</groupId>
+ <artifactId>testTools</artifactId>
+ <version>${project.parent.version}</version>
+ <scope>test</scope>
</dependency>
</dependencies>
@@ -183,6 +191,10 @@
<name>org.eclipse.jetty.server.Request.maxFormContentSize</name>
<value>20000000</value>
</property>
+ <property>
+ <name>ASTOptimizerClass</name>
+
<value>org.wikidata.query.rdf.blazegraph.WikibaseOptimizers</value>
+ </property>
</properties>
</configuration>
</execution>
diff --git
a/tools/src/main/java/org/wikidata/query/rdf/tool/wikibase/WikibaseRepository.java
b/tools/src/main/java/org/wikidata/query/rdf/tool/wikibase/WikibaseRepository.java
index 9ab23f3..5e25b06 100644
---
a/tools/src/main/java/org/wikidata/query/rdf/tool/wikibase/WikibaseRepository.java
+++
b/tools/src/main/java/org/wikidata/query/rdf/tool/wikibase/WikibaseRepository.java
@@ -1,7 +1,5 @@
package org.wikidata.query.rdf.tool.wikibase;
-import static
org.wikidata.query.rdf.tool.wikibase.WikibaseRepository.outputDateFormat;
-
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/ExpandedStatementBuilder.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/ExpandedStatementBuilder.java
index a62184e..21f00a8 100644
---
a/tools/src/test/java/org/wikidata/query/rdf/tool/ExpandedStatementBuilder.java
+++
b/tools/src/test/java/org/wikidata/query/rdf/tool/ExpandedStatementBuilder.java
@@ -2,7 +2,7 @@
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
-import static org.wikidata.query.rdf.tool.StatementHelper.statement;
+import static org.wikidata.query.rdf.test.StatementHelper.statement;
import java.util.ArrayList;
import java.util.Collections;
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/IOBlastingIntegrationTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/IOBlastingIntegrationTest.java
index c93a855..6047ccc 100644
---
a/tools/src/test/java/org/wikidata/query/rdf/tool/IOBlastingIntegrationTest.java
+++
b/tools/src/test/java/org/wikidata/query/rdf/tool/IOBlastingIntegrationTest.java
@@ -1,8 +1,8 @@
package org.wikidata.query.rdf.tool;
import static org.hamcrest.Matchers.hasItems;
-import static
org.wikidata.query.rdf.tool.Matchers.subjectPredicateObjectMatchers;
-import static
org.wikidata.query.rdf.tool.StatementHelper.randomStatementsAbout;
+import static
org.wikidata.query.rdf.test.Matchers.subjectPredicateObjectMatchers;
+import static
org.wikidata.query.rdf.test.StatementHelper.randomStatementsAbout;
import static org.wikidata.query.rdf.tool.TupleQueryResultHelper.toIterable;
import java.util.ArrayList;
@@ -67,8 +67,8 @@
private final Matcher<BindingSet>[] matchers;
IOBlasterResults(Iterable<BindingSet> first, Matcher<BindingSet>[]
second) {
- this.results = first;
- this.matchers = second;
+ results = first;
+ matchers = second;
}
Iterable<BindingSet> results() {
@@ -90,7 +90,7 @@
private final RdfRepositoryForTesting rdfRepository;
IOBlaster(String namespace) {
- this.rdfRepository = new RdfRepositoryForTesting(namespace);
+ rdfRepository = new RdfRepositoryForTesting(namespace);
}
/**
diff --git a/tools/src/test/java/org/wikidata/query/rdf/tool/Matchers.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/Matchers.java
deleted file mode 100644
index 9212622..0000000
--- a/tools/src/test/java/org/wikidata/query/rdf/tool/Matchers.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.wikidata.query.rdf.tool;
-
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.wikidata.query.rdf.tool.StatementHelper.uri;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeMatcher;
-import org.openrdf.model.Statement;
-import org.openrdf.model.URI;
-import org.openrdf.query.Binding;
-import org.openrdf.query.BindingSet;
-import org.wikidata.query.rdf.common.uri.WikibaseUris;
-import org.wikidata.query.rdf.common.uri.WikibaseUris.PropertyType;
-
-/**
- * Useful matchers for RDF.
- */
-public final class Matchers {
- /**
- * Check a binding to a uri.
- */
- public static Matcher<BindingSet> binds(String name, String value) {
- if (value.startsWith("P")) {
- value = WikibaseUris.WIKIDATA.property(PropertyType.CLAIM) + value;
- }
- return new BindsMatcher<URI>(name, equalTo(uri(value)));
- }
-
- /**
- * Check binding to specific class.
- */
- public static Matcher<BindingSet> binds(String name, Class<?> value) {
- return new BindsMatcher<Object>(name, instanceOf(value));
- }
-
- /**
- * Check a binding to a value.
- */
- public static <V> Matcher<BindingSet> binds(String name, V value) {
- return new BindsMatcher<V>(name, equalTo(value));
- }
-
-
- /**
- * Checks bindings.
- *
- * @param <V> type of the binding result
- */
- private static class BindsMatcher<V> extends TypeSafeMatcher<BindingSet> {
- /**
- * Name of the binding to check.
- */
- private final String name;
- /**
- * Delegate matcher for the bound value.
- */
- private final Matcher<V> valueMatcher;
-
- public BindsMatcher(String name, Matcher<V> valueMatcher) {
- this.name = name;
- this.valueMatcher = valueMatcher;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("contains a binding
from").appendValue(name).appendText("to")
- .appendDescriptionOf(valueMatcher);
- }
-
- @Override
- protected void describeMismatchSafely(BindingSet item, Description
mismatchDescription) {
- Binding binding = item.getBinding(name);
- if (binding == null) {
- mismatchDescription.appendText("but did not contain such a
binding");
- return;
- }
- mismatchDescription.appendText("instead it was bound
to").appendValue(binding.getValue());
- }
-
- @Override
- protected boolean matchesSafely(BindingSet item) {
- Binding binding = item.getBinding(name);
- if (binding == null) {
- return false;
- }
- return valueMatcher.matches(binding.getValue());
- }
- }
-
- /**
- * Construct subject, predicate, and object matchers for a bunch of
- * statements.
- */
- @SuppressWarnings("unchecked")
- public static Matcher<BindingSet>[]
subjectPredicateObjectMatchers(Iterable<Statement> statements) {
- List<Matcher<? super BindingSet>> matchers = new ArrayList<>();
- for (Statement statement : statements) {
- matchers.add(allOf(//
- binds("s", statement.getSubject()), //
- binds("p", statement.getPredicate()), //
- binds("o", statement.getObject()) //
- ));
- }
- return (Matcher<BindingSet>[]) matchers.toArray(new
Matcher<?>[matchers.size()]);
- }
-
- private Matchers() {
- // Utility constructor
- }
-}
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/MultipleResultsQueryIntegrationTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/MultipleResultsQueryIntegrationTest.java
index d6b29af..652d024 100644
---
a/tools/src/test/java/org/wikidata/query/rdf/tool/MultipleResultsQueryIntegrationTest.java
+++
b/tools/src/test/java/org/wikidata/query/rdf/tool/MultipleResultsQueryIntegrationTest.java
@@ -3,8 +3,8 @@
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
-import static
org.wikidata.query.rdf.tool.Matchers.subjectPredicateObjectMatchers;
-import static
org.wikidata.query.rdf.tool.StatementHelper.randomStatementsAbout;
+import static
org.wikidata.query.rdf.test.Matchers.subjectPredicateObjectMatchers;
+import static
org.wikidata.query.rdf.test.StatementHelper.randomStatementsAbout;
import static org.wikidata.query.rdf.tool.TupleQueryResultHelper.toIterable;
import java.util.ArrayList;
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/MungeIntegrationTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/MungeIntegrationTest.java
index 7bf806f..0e96c27 100644
--- a/tools/src/test/java/org/wikidata/query/rdf/tool/MungeIntegrationTest.java
+++ b/tools/src/test/java/org/wikidata/query/rdf/tool/MungeIntegrationTest.java
@@ -75,7 +75,7 @@
}
}
assertTrue(rdfRepository().ask(
- RDFS.prefixes(uris().prefixes(new StringBuilder()))
+ RDFS.prefix(uris().prefixes(new StringBuilder()))
.append("ASK { wd:Q10 rdfs:label \"Wikidata\"@en
}").toString()));
assertTrue(rdfRepository().ask(
SchemaDotOrg.prefix(Ontology.prefix(new StringBuilder()))
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/WikibaseDateExtensionIntegrationTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/WikibaseDateExtensionIntegrationTest.java
index 64a885f..a976c2a 100644
---
a/tools/src/test/java/org/wikidata/query/rdf/tool/WikibaseDateExtensionIntegrationTest.java
+++
b/tools/src/test/java/org/wikidata/query/rdf/tool/WikibaseDateExtensionIntegrationTest.java
@@ -1,7 +1,7 @@
package org.wikidata.query.rdf.tool;
-import static org.wikidata.query.rdf.tool.Matchers.binds;
-import static org.wikidata.query.rdf.tool.StatementHelper.statement;
+import static org.wikidata.query.rdf.test.Matchers.binds;
+import static org.wikidata.query.rdf.test.StatementHelper.statement;
import java.util.ArrayList;
import java.util.List;
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/MungerUnitTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/MungerUnitTest.java
index b639c04..bcdaeb5 100644
--- a/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/MungerUnitTest.java
+++ b/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/MungerUnitTest.java
@@ -3,8 +3,8 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
-import static org.wikidata.query.rdf.tool.StatementHelper.siteLink;
-import static org.wikidata.query.rdf.tool.StatementHelper.statement;
+import static org.wikidata.query.rdf.test.StatementHelper.siteLink;
+import static org.wikidata.query.rdf.test.StatementHelper.statement;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -27,7 +27,7 @@
import org.wikidata.query.rdf.common.uri.SchemaDotOrg;
import org.wikidata.query.rdf.common.uri.WikibaseUris;
import org.wikidata.query.rdf.common.uri.WikibaseUris.PropertyType;
-import org.wikidata.query.rdf.tool.StatementHelper;
+import org.wikidata.query.rdf.test.StatementHelper;
import org.wikidata.query.rdf.tool.rdf.Munger.BadSubjectException;
import com.carrotsearch.randomizedtesting.RandomizedRunner;
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/NormalizingRdfHandlerUnitTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/NormalizingRdfHandlerUnitTest.java
index 6c60b48..47d5a10 100644
---
a/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/NormalizingRdfHandlerUnitTest.java
+++
b/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/NormalizingRdfHandlerUnitTest.java
@@ -10,7 +10,7 @@
import org.wikidata.query.rdf.common.uri.RDF;
import static org.junit.Assert.assertEquals;
-import static org.wikidata.query.rdf.tool.StatementHelper.statement;
+import static org.wikidata.query.rdf.test.StatementHelper.statement;
public class NormalizingRdfHandlerUnitTest {
diff --git
a/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/RdfRepositoryIntegrationTest.java
b/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/RdfRepositoryIntegrationTest.java
index bb84936..7d132e8 100644
---
a/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/RdfRepositoryIntegrationTest.java
+++
b/tools/src/test/java/org/wikidata/query/rdf/tool/rdf/RdfRepositoryIntegrationTest.java
@@ -1,9 +1,9 @@
package org.wikidata.query.rdf.tool.rdf;
import static org.hamcrest.Matchers.allOf;
-import static org.wikidata.query.rdf.tool.Matchers.binds;
-import static org.wikidata.query.rdf.tool.StatementHelper.siteLink;
-import static org.wikidata.query.rdf.tool.StatementHelper.statement;
+import static org.wikidata.query.rdf.test.Matchers.binds;
+import static org.wikidata.query.rdf.test.StatementHelper.siteLink;
+import static org.wikidata.query.rdf.test.StatementHelper.statement;
import java.math.BigInteger;
import java.util.ArrayList;
--
To view, visit https://gerrit.wikimedia.org/r/206396
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I37df29ad442692d350fdd35cd17badb05170b416
Gerrit-PatchSet: 8
Gerrit-Project: wikidata/query/rdf
Gerrit-Branch: master
Gerrit-Owner: Manybubbles <[email protected]>
Gerrit-Reviewer: Jdouglas <[email protected]>
Gerrit-Reviewer: Manybubbles <[email protected]>
Gerrit-Reviewer: Smalyshev <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits