Faster implementation of TPF for SDB (#54)
* Initial performance improvements to TPF * Add faster counts for TPF * Correct end count * Bug fixes, use hashes for filtering * Initial LDF SPARQL client * Handle blank nodes correctly
This commit is contained in:
parent
0191ae6dbb
commit
b5da11c2be
17 changed files with 1446 additions and 112 deletions
|
@ -8,6 +8,7 @@ import java.util.List;
|
|||
|
||||
import org.apache.jena.rdf.model.Model;
|
||||
import org.apache.jena.rdf.model.ModelChangedListener;
|
||||
import org.apache.jena.rdf.model.RDFNode;
|
||||
|
||||
/**
|
||||
* Interface for API to write, read, and update Vitro's RDF store, with support
|
||||
|
@ -247,7 +248,11 @@ public interface RDFService {
|
|||
*
|
||||
* @return ChangeSet an empty ChangeSet object
|
||||
*/
|
||||
public ChangeSet manufactureChangeSet();
|
||||
public ChangeSet manufactureChangeSet();
|
||||
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException;
|
||||
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException;
|
||||
|
||||
/**
|
||||
* Frees any resources held by this RDFService object
|
||||
|
|
|
@ -468,6 +468,16 @@ public class LanguageFilteringRDFService implements RDFService {
|
|||
return s.manufactureChangeSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
|
||||
return s.countTriples(subject, predicate, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
|
||||
return s.getTriples(subject, predicate, object, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
s.close();
|
||||
|
|
|
@ -16,6 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
|||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
|
||||
import org.apache.jena.rdf.model.RDFNode;
|
||||
|
||||
/**
|
||||
* An RDFServiceFactory that always returns the same RDFService object
|
||||
|
@ -192,6 +193,16 @@ public class RDFServiceFactorySingle implements RDFServiceFactory {
|
|||
return s.manufactureChangeSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
|
||||
return s.countTriples(subject, predicate, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
|
||||
return s.getTriples(subject, predicate, object, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Don't close s. It's being used by everybody.
|
||||
|
|
|
@ -12,18 +12,24 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.apache.jena.atlas.io.StringWriterI;
|
||||
import org.apache.jena.graph.Node;
|
||||
import org.apache.jena.graph.NodeFactory;
|
||||
import org.apache.jena.graph.Triple;
|
||||
import org.apache.jena.query.Query;
|
||||
import org.apache.jena.query.QueryFactory;
|
||||
import org.apache.jena.query.QueryParseException;
|
||||
import org.apache.jena.query.QuerySolution;
|
||||
import org.apache.jena.query.Syntax;
|
||||
import org.apache.jena.rdf.model.Literal;
|
||||
import org.apache.jena.rdf.model.Model;
|
||||
import org.apache.jena.rdf.model.ModelChangedListener;
|
||||
import org.apache.jena.rdf.model.ModelFactory;
|
||||
import org.apache.jena.rdf.model.RDFNode;
|
||||
import org.apache.jena.rdf.model.Statement;
|
||||
import org.apache.jena.rdf.model.StmtIterator;
|
||||
import org.apache.jena.riot.out.NodeFormatter;
|
||||
import org.apache.jena.riot.out.NodeFormatterTTL;
|
||||
import org.apache.jena.vocabulary.RDF;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
|
||||
|
@ -34,6 +40,7 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
|||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
|
||||
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceBasedRequestProcessorForTPFs;
|
||||
|
||||
public abstract class RDFServiceImpl implements RDFService {
|
||||
|
||||
|
@ -340,5 +347,121 @@ public abstract class RDFServiceImpl implements RDFService {
|
|||
public String toString() {
|
||||
return ToString.simpleName(this) + "[" + ToString.hashHex(this) + "]";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
|
||||
if ( subject != null ) {
|
||||
appendNode(whereClause.append(' '), subject);
|
||||
} else {
|
||||
whereClause.append(" ?s");
|
||||
orderBy.append(" ?s");
|
||||
}
|
||||
|
||||
if ( predicate != null ) {
|
||||
appendNode(whereClause.append(' '), predicate);
|
||||
} else {
|
||||
whereClause.append(" ?p");
|
||||
orderBy.append(" ?p");
|
||||
}
|
||||
|
||||
if ( object != null ) {
|
||||
appendNode(whereClause.append(' '), object);
|
||||
} else {
|
||||
whereClause.append(" ?o");
|
||||
orderBy.append(" ?o");
|
||||
}
|
||||
|
||||
long estimate = -1;
|
||||
|
||||
StringBuilder count = new StringBuilder();
|
||||
count.append("SELECT (COUNT(*) AS ?count) WHERE { ");
|
||||
count.append(whereClause.toString());
|
||||
count.append(" . ");
|
||||
count.append(" }");
|
||||
CountConsumer countConsumer = new CountConsumer();
|
||||
this.sparqlSelectQuery(count.toString(), countConsumer);
|
||||
return countConsumer.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
|
||||
if ( subject != null ) {
|
||||
appendNode(whereClause.append(' '), subject);
|
||||
} else {
|
||||
whereClause.append(" ?s");
|
||||
orderBy.append(" ?s");
|
||||
}
|
||||
|
||||
if ( predicate != null ) {
|
||||
appendNode(whereClause.append(' '), predicate);
|
||||
} else {
|
||||
whereClause.append(" ?p");
|
||||
orderBy.append(" ?p");
|
||||
}
|
||||
|
||||
if ( object != null ) {
|
||||
appendNode(whereClause.append(' '), object);
|
||||
} else {
|
||||
whereClause.append(" ?o");
|
||||
orderBy.append(" ?o");
|
||||
}
|
||||
|
||||
StringBuilder constructQuery = new StringBuilder();
|
||||
|
||||
constructQuery.append("CONSTRUCT { ");
|
||||
constructQuery.append(whereClause.toString());
|
||||
constructQuery.append(" } WHERE { ");
|
||||
constructQuery.append(whereClause.toString()).append(" . ");
|
||||
constructQuery.append(" }");
|
||||
|
||||
if (orderBy.length() > 0) {
|
||||
constructQuery.append(" ORDER BY").append(orderBy.toString());
|
||||
}
|
||||
|
||||
if (limit > 0) {
|
||||
constructQuery.append(" LIMIT ").append(limit);
|
||||
}
|
||||
|
||||
if (offset > 0) {
|
||||
constructQuery.append(" OFFSET ").append(offset);
|
||||
}
|
||||
|
||||
Model triples = ModelFactory.createDefaultModel();
|
||||
this.sparqlConstructQuery(constructQuery.toString(), triples);
|
||||
|
||||
return triples;
|
||||
}
|
||||
|
||||
private void appendNode(StringBuilder builder, RDFNode node) {
|
||||
if (node.isLiteral()) {
|
||||
builder.append(literalToString(node.asLiteral()));
|
||||
} else if (node.isURIResource()) {
|
||||
builder.append('<' + node.asResource().getURI() + '>');
|
||||
}
|
||||
}
|
||||
|
||||
private String literalToString(Literal l) {
|
||||
StringWriterI sw = new StringWriterI();
|
||||
NodeFormatter fmt = new NodeFormatterTTL(null, null);
|
||||
fmt.formatLiteral(sw, l.asNode());
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
class CountConsumer extends ResultSetConsumer {
|
||||
public long count = -1;
|
||||
|
||||
@Override
|
||||
protected void processQuerySolution(QuerySolution qs) {
|
||||
if (count == -1) {
|
||||
Literal literal = qs.getLiteral("count");
|
||||
count = literal.getLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.jena.query.QuerySolutionMap;
|
||||
import org.apache.jena.query.Syntax;
|
||||
import org.apache.jena.rdf.model.Literal;
|
||||
import org.apache.jena.riot.RDFDataMgr;
|
||||
import org.apache.log4j.lf5.util.StreamUtils;
|
||||
|
||||
|
@ -612,6 +615,71 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
|
|||
return graph.isIsomorphicWith(fromTripleStoreModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
|
||||
Query countQuery = QueryFactory.create("SELECT (COUNT(?s) AS ?count) WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o", Syntax.syntaxSPARQL_11);
|
||||
QuerySolutionMap map = new QuerySolutionMap();
|
||||
if ( subject != null ) {
|
||||
map.add("s", subject);
|
||||
}
|
||||
if ( predicate != null ) {
|
||||
map.add("p", predicate);
|
||||
}
|
||||
if ( object != null ) {
|
||||
map.add("o", object);
|
||||
}
|
||||
|
||||
DatasetWrapper dw = getDatasetWrapper();
|
||||
try {
|
||||
Dataset d = dw.getDataset();
|
||||
try (QueryExecution qexec = QueryExecutionFactory.create(countQuery, d, map)) {
|
||||
ResultSet results = qexec.execSelect();
|
||||
if (results.hasNext()) {
|
||||
QuerySolution soln = results.nextSolution() ;
|
||||
Literal literal = soln.getLiteral("count");
|
||||
return literal.getLong();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
dw.close();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
|
||||
Query query = QueryFactory.create("CONSTRUCT WHERE { ?s ?p ?o }", Syntax.syntaxSPARQL_11);
|
||||
QuerySolutionMap map = new QuerySolutionMap();
|
||||
if ( subject != null ) {
|
||||
map.add("s", subject);
|
||||
}
|
||||
if ( predicate != null ) {
|
||||
map.add("p", predicate);
|
||||
}
|
||||
if ( object != null ) {
|
||||
map.add("o", object);
|
||||
}
|
||||
|
||||
query.setOffset(offset);
|
||||
query.setLimit(limit);
|
||||
|
||||
Model triples = ModelFactory.createDefaultModel();
|
||||
|
||||
DatasetWrapper dw = getDatasetWrapper();
|
||||
try {
|
||||
Dataset d = dw.getDataset();
|
||||
try (QueryExecution qexec = QueryExecutionFactory.create(query, d, map)) {
|
||||
qexec.execConstruct(triples);
|
||||
}
|
||||
|
||||
return triples;
|
||||
} finally {
|
||||
dw.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing
|
||||
|
@ -620,5 +688,4 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
|
|||
protected QueryExecution createQueryExecution(String queryString, Query q, Dataset d) {
|
||||
return QueryExecutionFactory.create(q, d);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,20 +4,34 @@ package edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.sdb;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.apache.jena.datatypes.RDFDatatype;
|
||||
import org.apache.jena.datatypes.TypeMapper;
|
||||
import org.apache.jena.datatypes.xsd.XSDDatatype;
|
||||
import org.apache.jena.graph.Node;
|
||||
import org.apache.jena.graph.NodeFactory;
|
||||
import org.apache.jena.graph.Triple;
|
||||
import org.apache.jena.query.Dataset;
|
||||
import org.apache.jena.query.Query;
|
||||
import org.apache.jena.query.QueryExecution;
|
||||
import org.apache.jena.query.QueryExecutionFactory;
|
||||
import org.apache.jena.rdf.model.Model;
|
||||
import org.apache.jena.rdf.model.ModelFactory;
|
||||
import org.apache.jena.rdf.model.RDFNode;
|
||||
import org.apache.jena.sdb.SDBFactory;
|
||||
import org.apache.jena.sdb.Store;
|
||||
import org.apache.jena.sdb.StoreDesc;
|
||||
import org.apache.jena.sdb.layout2.NodeLayout2;
|
||||
import org.apache.jena.sdb.layout2.ValueType;
|
||||
import org.apache.jena.sdb.sql.SDBConnection;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.dao.jena.DatasetWrapper;
|
||||
|
@ -28,6 +42,8 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
|
|||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.RDFServiceJena;
|
||||
import org.apache.jena.sdb.store.DatabaseType;
|
||||
import org.apache.jena.sdb.store.LayoutType;
|
||||
|
||||
public class RDFServiceSDB extends RDFServiceJena implements RDFService {
|
||||
|
||||
|
@ -147,8 +163,118 @@ public class RDFServiceSDB extends RDFServiceJena implements RDFService {
|
|||
// This used to execute against the default model if the query included an OPTIONAL
|
||||
// However, in recent Jena this turns out to be much slower than executing against the dataset directly
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
|
||||
if (LayoutType.LayoutTripleNodesHash.equals(storeDesc.getLayout())) {
|
||||
if (DatabaseType.MySQL.equals(storeDesc.getDbType()) ||
|
||||
DatabaseType.PostgreSQL.equals(storeDesc.getDbType())) {
|
||||
SDBConnection sdbConn = getSDBConnection();
|
||||
try {
|
||||
String whereClause = makeWhereClause(subject, predicate, object);
|
||||
Statement stmt = sdbConn.getSqlConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT count(DISTINCT s,p,o) AS tcount FROM Quads" + (StringUtils.isEmpty(whereClause) ? "" : " WHERE " + whereClause));
|
||||
try {
|
||||
while (rs.next()) {
|
||||
return rs.getLong("tcount");
|
||||
}
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
} catch (SQLException sqle) {
|
||||
throw new RDFServiceException("Unable to retrieve triples", sqle);
|
||||
} finally {
|
||||
close(sdbConn);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return super.countTriples(subject, predicate, object);
|
||||
}
|
||||
|
||||
return super.countTriples(subject, predicate, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
|
||||
if (LayoutType.LayoutTripleNodesHash.equals(storeDesc.getLayout())) {
|
||||
if (DatabaseType.MySQL.equals(storeDesc.getDbType()) ||
|
||||
DatabaseType.PostgreSQL.equals(storeDesc.getDbType())) {
|
||||
Model triples = ModelFactory.createDefaultModel();
|
||||
|
||||
SDBConnection sdbConn = getSDBConnection();
|
||||
try {
|
||||
String whereClause = makeWhereClause(subject, predicate, object);
|
||||
Statement stmt = sdbConn.getSqlConnection().createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT \n" +
|
||||
"N1.lex AS s_lex,\n" +
|
||||
"N1.lang AS s_lang,\n" +
|
||||
"N1.datatype AS s_datatype,\n" +
|
||||
"N1.type AS s_type,\n" +
|
||||
"N2.lex AS p_lex,\n" +
|
||||
"N2.lang AS p_lang,\n" +
|
||||
"N2.datatype AS p_datatype,\n" +
|
||||
"N2.type AS p_type,\n" +
|
||||
"N3.lex AS o_lex,\n" +
|
||||
"N3.lang AS o_lang,\n" +
|
||||
"N3.datatype AS o_datatype,\n" +
|
||||
"N3.type AS o_type\n" +
|
||||
"FROM\n" +
|
||||
"(SELECT DISTINCT s,p,o FROM Quads" +
|
||||
(StringUtils.isEmpty(whereClause) ? "" : " WHERE " + whereClause) +
|
||||
" ORDER BY s,p,o " +
|
||||
(limit > 0 ? "LIMIT " + limit : "") +
|
||||
(offset > 0 ? " OFFSET " + offset : "") + ") Q\n" +
|
||||
"LEFT OUTER JOIN\n" +
|
||||
"\tNodes AS N1\n" +
|
||||
"ON ( Q.s = N1.hash )\n" +
|
||||
"LEFT OUTER JOIN\n" +
|
||||
"\tNodes AS N2\n" +
|
||||
"ON ( Q.p = N2.hash )\n" +
|
||||
"LEFT OUTER JOIN\n" +
|
||||
"\tNodes AS N3\n" +
|
||||
"ON ( Q.o = N3.hash )");
|
||||
|
||||
try {
|
||||
while (rs.next()) {
|
||||
Node subjectNode = makeNode(
|
||||
rs.getString("s_lex"),
|
||||
rs.getString("s_datatype"),
|
||||
rs.getString("s_lang"),
|
||||
ValueType.lookup(rs.getInt("s_type")));
|
||||
|
||||
Node predicateNode = makeNode(
|
||||
rs.getString("p_lex"),
|
||||
rs.getString("p_datatype"),
|
||||
rs.getString("p_lang"),
|
||||
ValueType.lookup(rs.getInt("p_type")));
|
||||
|
||||
Node objectNode = makeNode(
|
||||
rs.getString("o_lex"),
|
||||
rs.getString("o_datatype"),
|
||||
rs.getString("o_lang"),
|
||||
ValueType.lookup(rs.getInt("o_type")));
|
||||
|
||||
triples.add(
|
||||
triples.asStatement(Triple.create(subjectNode, predicateNode, objectNode))
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
} catch (SQLException sqle) {
|
||||
throw new RDFServiceException("Unable to retrieve triples", sqle);
|
||||
} finally {
|
||||
close(sdbConn);
|
||||
}
|
||||
|
||||
return triples;
|
||||
}
|
||||
}
|
||||
|
||||
return super.getTriples(subject, predicate, object, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (conn != null) {
|
||||
try {
|
||||
|
@ -159,4 +285,55 @@ public class RDFServiceSDB extends RDFServiceJena implements RDFService {
|
|||
}
|
||||
}
|
||||
|
||||
// Copied from Jena SQLBridge2
|
||||
private static Node makeNode(String lex, String datatype, String lang, ValueType vType) {
|
||||
switch(vType) {
|
||||
case BNODE:
|
||||
return NodeFactory.createBlankNode(lex);
|
||||
case URI:
|
||||
return NodeFactory.createURI(lex);
|
||||
case STRING:
|
||||
return NodeFactory.createLiteral(lex, lang);
|
||||
case XSDSTRING:
|
||||
return NodeFactory.createLiteral(lex, XSDDatatype.XSDstring);
|
||||
case INTEGER:
|
||||
return NodeFactory.createLiteral(lex, XSDDatatype.XSDinteger);
|
||||
case DOUBLE:
|
||||
return NodeFactory.createLiteral(lex, XSDDatatype.XSDdouble);
|
||||
case DATETIME:
|
||||
return NodeFactory.createLiteral(lex, XSDDatatype.XSDdateTime);
|
||||
case OTHER:
|
||||
RDFDatatype dt = TypeMapper.getInstance().getSafeTypeByName(datatype);
|
||||
return NodeFactory.createLiteral(lex, dt);
|
||||
default:
|
||||
log.warn("Unrecognized: (" + lex + ", " + lang + ", " + vType + ")");
|
||||
return NodeFactory.createLiteral("UNRECOGNIZED");
|
||||
}
|
||||
}
|
||||
|
||||
private String makeWhereClause(RDFNode subject, RDFNode predicate, RDFNode object) {
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
if (subject != null) {
|
||||
if (whereClause.length() > 0) {
|
||||
whereClause.append(" AND ");
|
||||
}
|
||||
whereClause.append("s=").append(NodeLayout2.hash(subject.asNode()));
|
||||
}
|
||||
|
||||
if (predicate != null) {
|
||||
if (whereClause.length() > 0) {
|
||||
whereClause.append(" AND ");
|
||||
}
|
||||
whereClause.append("p=").append(NodeLayout2.hash(predicate.asNode()));
|
||||
}
|
||||
|
||||
if (object != null) {
|
||||
if (whereClause.length() > 0) {
|
||||
whereClause.append(" AND ");
|
||||
}
|
||||
whereClause.append("o=").append(NodeLayout2.hash(object.asNode()));
|
||||
}
|
||||
|
||||
return whereClause.length() > 0 ? whereClause.toString() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
|
|||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
|
||||
import org.apache.jena.rdf.model.RDFNode;
|
||||
|
||||
/**
|
||||
* This RDFService wrapper adds instrumentation to the time-consuming methods of
|
||||
|
@ -182,6 +183,16 @@ public class LoggingRDFService implements RDFService {
|
|||
return innerService.manufactureChangeSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
|
||||
return innerService.countTriples(subject, predicate, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
|
||||
return innerService.getTriples(subject, predicate, object, limit, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
innerService.close();
|
||||
|
|
|
@ -106,7 +106,7 @@ public class HtmlTriplePatternFragmentWriterImpl extends TriplePatternFragmentWr
|
|||
// Calculate start and end triple number
|
||||
Long start = ((tpfRequest.getPageNumber() - 1) * fragment.getMaxPageSize()) + 1;
|
||||
data.put("start", start);
|
||||
data.put("end", start + (triples.size() < fragment.getMaxPageSize() ? triples.size() : fragment.getMaxPageSize()));
|
||||
data.put("end", (start - 1) + (triples.size() < fragment.getMaxPageSize() ? triples.size() : fragment.getMaxPageSize()));
|
||||
|
||||
// Compose query object
|
||||
Map query = new HashMap();
|
||||
|
|
|
@ -6,6 +6,9 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
|||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
|
||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
|
||||
import org.apache.jena.atlas.io.StringWriterI;
|
||||
import org.apache.jena.graph.Node;
|
||||
import org.apache.jena.graph.NodeFactory;
|
||||
import org.apache.jena.graph.Triple;
|
||||
import org.apache.jena.query.Dataset;
|
||||
import org.apache.jena.query.Query;
|
||||
import org.apache.jena.query.QueryExecution;
|
||||
|
@ -19,6 +22,8 @@ import org.apache.jena.rdf.model.Literal;
|
|||
import org.apache.jena.rdf.model.Model;
|
||||
import org.apache.jena.rdf.model.ModelFactory;
|
||||
import org.apache.jena.rdf.model.RDFNode;
|
||||
import org.apache.jena.rdf.model.Statement;
|
||||
import org.apache.jena.rdf.model.StmtIterator;
|
||||
import org.apache.jena.riot.out.NodeFormatter;
|
||||
import org.apache.jena.riot.out.NodeFormatterTTL;
|
||||
import org.apache.jena.tdb.TDBFactory;
|
||||
|
@ -74,6 +79,32 @@ public class RDFServiceBasedRequestProcessorForTPFs
|
|||
return sw.toString();
|
||||
}
|
||||
|
||||
private Node skolemize(Node node) {
|
||||
if (node != null && node.isBlank()) {
|
||||
return NodeFactory.createURI("bnode://" + node.getBlankNodeLabel());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private RDFNode deskolemize(RDFNode node) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.isResource()) {
|
||||
String uri = node.asResource().getURI();
|
||||
if (uri != null && uri.startsWith("bnode://")) {
|
||||
String bnodeId = uri.substring(8);
|
||||
return ModelFactory.createDefaultModel().asRDFNode(
|
||||
NodeFactory.createBlankNode(bnodeId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ILinkedDataFragment createFragment(
|
||||
final ITriplePatternElement<RDFNode,String,String> subject,
|
||||
|
@ -82,100 +113,51 @@ public class RDFServiceBasedRequestProcessorForTPFs
|
|||
final long offset,
|
||||
final long limit )
|
||||
{
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
StringBuilder filter = new StringBuilder();
|
||||
StringBuilder orderBy = new StringBuilder();
|
||||
|
||||
if ( ! subject.isVariable() ) {
|
||||
appendNode(whereClause.append(' '), subject.asConstantTerm());
|
||||
} else {
|
||||
whereClause.append(" ?s");
|
||||
if (filter.length() > 0) { filter.append(" && "); }
|
||||
filter.append("!isBlank(?s)");
|
||||
orderBy.append(" ?s");
|
||||
}
|
||||
|
||||
if ( ! predicate.isVariable() ) {
|
||||
appendNode(whereClause.append(' '), predicate.asConstantTerm());
|
||||
} else {
|
||||
whereClause.append(" ?p");
|
||||
if (filter.length() > 0) { filter.append(" && "); }
|
||||
filter.append("!isBlank(?p)");
|
||||
orderBy.append(" ?p");
|
||||
}
|
||||
|
||||
if ( ! object.isVariable() ) {
|
||||
appendNode(whereClause.append(' '), object.asConstantTerm());
|
||||
} else {
|
||||
whereClause.append(" ?o");
|
||||
if (filter.length() > 0) { filter.append(" && "); }
|
||||
filter.append("!isBlank(?o)");
|
||||
orderBy.append(" ?o");
|
||||
}
|
||||
|
||||
StringBuilder constructQuery = new StringBuilder();
|
||||
|
||||
constructQuery.append("CONSTRUCT { ");
|
||||
constructQuery.append(whereClause.toString());
|
||||
constructQuery.append(" } WHERE { ");
|
||||
constructQuery.append(whereClause.toString()).append(" . ");
|
||||
if (filter.length() > 0) {
|
||||
constructQuery.append(" FILTER(").append(filter.toString()).append(")");
|
||||
}
|
||||
constructQuery.append(" }");
|
||||
|
||||
if (orderBy.length() > 0) {
|
||||
constructQuery.append(" ORDER BY").append(orderBy.toString());
|
||||
}
|
||||
|
||||
if (limit > 0) {
|
||||
constructQuery.append(" LIMIT ").append(limit);
|
||||
}
|
||||
|
||||
if (offset > 0) {
|
||||
constructQuery.append(" OFFSET ").append(offset);
|
||||
}
|
||||
|
||||
Model triples = ModelFactory.createDefaultModel();
|
||||
|
||||
try {
|
||||
rdfService.sparqlConstructQuery(constructQuery.toString(), triples);
|
||||
RDFNode nSubject = subject.isVariable() ? null : deskolemize(subject.asConstantTerm());
|
||||
RDFNode nPredicate = predicate.isVariable() ? null : deskolemize(predicate.asConstantTerm());
|
||||
RDFNode nObject = object.isVariable() ? null : deskolemize(object.asConstantTerm());
|
||||
|
||||
Model triples = rdfService.getTriples(nSubject, nPredicate, nObject, limit, offset);
|
||||
if (triples == null || triples.isEmpty()) {
|
||||
return createEmptyTriplePatternFragment();
|
||||
}
|
||||
|
||||
if (triples.size() > 0) {
|
||||
Model replacedBlankNodes = ModelFactory.createDefaultModel();
|
||||
StmtIterator iter = triples.listStatements();
|
||||
while (iter.hasNext()) {
|
||||
Statement oldStmt = iter.next();
|
||||
Triple t = oldStmt.asTriple();
|
||||
replacedBlankNodes.add(
|
||||
replacedBlankNodes.asStatement(
|
||||
new Triple(
|
||||
skolemize(t.getSubject()),
|
||||
skolemize(t.getPredicate()),
|
||||
skolemize(t.getObject())
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
triples = replacedBlankNodes;
|
||||
}
|
||||
|
||||
long size = triples.size();
|
||||
long estimate = -1;
|
||||
estimate = rdfService.countTriples(nSubject, nPredicate, nObject);
|
||||
|
||||
// No estimate or incorrect
|
||||
if (estimate < offset + size) {
|
||||
estimate = (size == limit) ? offset + size + 1 : offset + size;
|
||||
}
|
||||
|
||||
// create the fragment
|
||||
final boolean isLastPage = ( estimate < offset + limit );
|
||||
return createTriplePatternFragment( triples, estimate, isLastPage );
|
||||
} catch (RDFServiceException e) {
|
||||
return createEmptyTriplePatternFragment();
|
||||
}
|
||||
|
||||
if (triples.isEmpty()) {
|
||||
return createEmptyTriplePatternFragment();
|
||||
}
|
||||
|
||||
// Try to get an estimate
|
||||
long size = triples.size();
|
||||
long estimate = -1;
|
||||
|
||||
StringBuilder count = new StringBuilder();
|
||||
count.append("SELECT (COUNT(*) AS ?count) WHERE { ");
|
||||
count.append(whereClause.toString());
|
||||
count.append(" . ");
|
||||
if (filter.length() > 0) {
|
||||
count.append(" FILTER(").append(filter.toString()).append(") ");
|
||||
}
|
||||
count.append(" }");
|
||||
try {
|
||||
CountConsumer countConsumer = new CountConsumer();
|
||||
rdfService.sparqlSelectQuery(count.toString(), countConsumer);
|
||||
estimate = countConsumer.estimate;
|
||||
} catch (RDFServiceException e) {
|
||||
return createEmptyTriplePatternFragment();
|
||||
}
|
||||
|
||||
// No estimate or incorrect
|
||||
if (estimate < offset + size) {
|
||||
estimate = (size == limit) ? offset + size + 1 : offset + size;
|
||||
}
|
||||
|
||||
// create the fragment
|
||||
final boolean isLastPage = ( estimate < offset + limit );
|
||||
return createTriplePatternFragment( triples, estimate, isLastPage );
|
||||
}
|
||||
|
||||
} // end of class Worker
|
||||
|
@ -186,16 +168,4 @@ public class RDFServiceBasedRequestProcessorForTPFs
|
|||
*/
|
||||
public RDFServiceBasedRequestProcessorForTPFs() {
|
||||
}
|
||||
|
||||
class CountConsumer extends ResultSetConsumer {
|
||||
public long estimate = -1;
|
||||
|
||||
@Override
|
||||
protected void processQuerySolution(QuerySolution qs) {
|
||||
if (estimate == -1) {
|
||||
Literal literal = qs.getLiteral("count");
|
||||
estimate = literal.getLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public class HtmlTriplePatternFragmentWriterImpl extends TriplePatternFragmentWr
|
|||
// Calculate start and end triple number
|
||||
Long start = ((tpfRequest.getPageNumber() - 1) * fragment.getMaxPageSize()) + 1;
|
||||
data.put("start", start);
|
||||
data.put("end", start + (triples.size() < fragment.getMaxPageSize() ? triples.size() : fragment.getMaxPageSize()));
|
||||
data.put("end", (start - 1) + (triples.size() < fragment.getMaxPageSize() ? triples.size() : fragment.getMaxPageSize()));
|
||||
|
||||
// Compose query object
|
||||
Map query = new HashMap();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue