/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 2008.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.rdbms;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import info.aduna.concurrent.locks.Lock;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.sail.helpers.DefaultSailChangedEvent;
import org.openrdf.sail.rdbms.cursor.EmptyRdbmsResourceCursor;
import org.openrdf.sail.rdbms.cursor.EmptyRdbmsStatementCursor;
import org.openrdf.sail.rdbms.cursor.RdbmsResourceCursor;
import org.openrdf.sail.rdbms.cursor.RdbmsStatementCursor;
import org.openrdf.sail.rdbms.evaluation.QueryBuilderFactory;
import org.openrdf.sail.rdbms.evaluation.SqlBracketBuilder;
import org.openrdf.sail.rdbms.evaluation.SqlJoinBuilder;
import org.openrdf.sail.rdbms.evaluation.SqlQueryBuilder;
import org.openrdf.sail.rdbms.exceptions.RdbmsException;
import org.openrdf.sail.rdbms.managers.TransTableManager;
import org.openrdf.sail.rdbms.managers.TripleManager;
import org.openrdf.sail.rdbms.model.RdbmsResource;
import org.openrdf.sail.rdbms.model.RdbmsStatement;
import org.openrdf.sail.rdbms.model.RdbmsURI;
import org.openrdf.sail.rdbms.model.RdbmsValue;
import org.openrdf.sail.rdbms.schema.BNodeTable;
import org.openrdf.sail.rdbms.schema.IdSequence;
import org.openrdf.sail.rdbms.schema.LiteralTable;
import org.openrdf.sail.rdbms.schema.URITable;
import org.openrdf.sail.rdbms.schema.ValueTable;
import org.openrdf.store.StoreException;
/**
* Facade to {@link TransTableManager}, {@link URITable}, {@link BNodeTable} and
* {@link LiteralTable} for adding, removing, and retrieving statements from the
* database.
*
* @author James Leigh
*/
public class RdbmsTripleRepository {
public static int STMT_BUFFER = 32;
private Connection conn;
private RdbmsValueFactory vf;
private TransTableManager statements;
private QueryBuilderFactory factory;
private BNodeTable bnodes;
private URITable uris;
private LiteralTable literals;
private Lock readLock;
private DefaultSailChangedEvent sailChangedEvent;
private TripleManager manager;
private LinkedList<RdbmsStatement> queue = new LinkedList<RdbmsStatement>();
private IdSequence ids;
public Connection getConnection() {
return conn;
}
public void setConnection(Connection conn) {
this.conn = conn;
}
public void setIdSequence(IdSequence ids) {
this.ids = ids;
}
public RdbmsValueFactory getValueFactory() {
return vf;
}
public void setValueFactory(RdbmsValueFactory vf) {
this.vf = vf;
}
public DefaultSailChangedEvent getSailChangedEvent() {
return sailChangedEvent;
}
public void setSailChangedEvent(DefaultSailChangedEvent sailChangedEvent) {
this.sailChangedEvent = sailChangedEvent;
}
public void setQueryBuilderFactory(QueryBuilderFactory factory) {
this.factory = factory;
}
public void setBNodeTable(BNodeTable bnodes) {
this.bnodes = bnodes;
}
public void setURITable(URITable uris) {
this.uris = uris;
}
public void setLiteralTable(LiteralTable literals) {
this.literals = literals;
}
public void setTransaction(TransTableManager temporary) {
this.statements = temporary;
}
public void setTripleManager(TripleManager tripleManager) {
this.manager = tripleManager;
}
public void flush()
throws RdbmsException
{
try {
synchronized (queue) {
while (!queue.isEmpty()) {
insert(queue.removeFirst());
}
}
vf.flush();
manager.flush();
}
catch (SQLException e) {
throw new RdbmsException(e);
}
catch (InterruptedException e) {
throw new RdbmsException(e);
}
}
public synchronized void begin()
throws SQLException
{
conn.setAutoCommit(false);
}
public synchronized void close()
throws SQLException
{
manager.close();
if (!conn.getAutoCommit()) {
conn.rollback();
}
conn.setAutoCommit(true);
conn.close();
releaseLock();
}
public synchronized void commit()
throws SQLException, RdbmsException, InterruptedException
{
synchronized (queue) {
while (!queue.isEmpty()) {
insert(queue.removeFirst());
}
}
manager.flush();
conn.commit();
conn.setAutoCommit(true);
releaseLock();
Lock writeLock = vf.tryIdWriteLock();
try {
vf.flush();
statements.committed(writeLock != null);
}
finally {
if (writeLock != null) {
writeLock.release();
}
}
}
public void rollback()
throws SQLException, StoreException
{
synchronized (queue) {
queue.clear();
}
manager.clear();
if (!conn.getAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
}
releaseLock();
}
@Override
protected void finalize()
throws Throwable
{
releaseLock();
super.finalize();
}
public void add(RdbmsStatement st)
throws StoreException, SQLException, InterruptedException
{
acquireLock();
synchronized (queue) {
queue.add(st);
if (queue.size() > getMaxQueueSize()) {
insert(queue.removeFirst());
}
}
}
public RdbmsStatementCursor find(Resource subj, URI pred, Value obj, Resource... ctxs)
throws RdbmsException
{
try {
RdbmsResource s = vf.asRdbmsResource(subj);
RdbmsURI p = vf.asRdbmsURI(pred);
RdbmsValue o = vf.asRdbmsValue(obj);
RdbmsResource[] c = vf.asRdbmsResource(ctxs);
flush();
SqlQueryBuilder query = buildSelectQuery(s, p, o, c);
if (query == null) {
return new EmptyRdbmsStatementCursor();
}
List<?> parameters = query.findParameters(new ArrayList<Object>());
PreparedStatement stmt = conn.prepareStatement(query.toString());
try {
for (int i = 0, n = parameters.size(); i < n; i++) {
stmt.setObject(i + 1, parameters.get(i));
}
return new RdbmsStatementCursor(vf, stmt, ids);
}
catch (SQLException e) {
stmt.close();
throw e;
}
}
catch (SQLException e) {
throw new RdbmsException(e);
}
}
public RdbmsResourceCursor findContexts()
throws SQLException, RdbmsException
{
flush();
String qry = buildContextQuery();
if (qry == null) {
return new EmptyRdbmsResourceCursor();
}
PreparedStatement stmt = conn.prepareStatement(qry);
try {
return new RdbmsResourceCursor(vf, stmt);
}
catch (SQLException e) {
stmt.close();
throw e;
}
}
public boolean isClosed()
throws SQLException
{
return conn.isClosed();
}
public int remove(Resource subj, URI pred, Value obj, Resource... ctxs)
throws RdbmsException
{
RdbmsResource s = vf.asRdbmsResource(subj);
RdbmsURI p = vf.asRdbmsURI(pred);
RdbmsValue o = vf.asRdbmsValue(obj);
RdbmsResource[] c = vf.asRdbmsResource(ctxs);
flush();
try {
Collection<Number> predicates;
if (p == null) {
predicates = statements.getPredicateIds();
}
else {
predicates = Collections.singleton(vf.getInternalId(p));
}
int total = 0;
for (Number id : predicates) {
String tableName = statements.findTableName(id);
if (!statements.isPredColumnPresent(id)) {
p = null;
}
String query = buildDeleteQuery(tableName, s, p, o, c);
PreparedStatement stmt = conn.prepareStatement(query);
try {
setSelectQuery(stmt, s, p, o, c);
int count = stmt.executeUpdate();
statements.removed(id, count);
total += count;
}
finally {
stmt.close();
}
}
if (total > 0) {
sailChangedEvent.setStatementsRemoved(true);
}
return total;
}
catch (SQLException e) {
throw new RdbmsException(e);
}
}
public long size(RdbmsResource subj, RdbmsURI pred, RdbmsValue obj, RdbmsResource... ctxs)
throws SQLException, StoreException
{
flush();
SqlQueryBuilder qry = buildCountQuery(subj, pred, obj, ctxs);
if (qry == null) {
return 0;
}
List<?> parameters = qry.findParameters(new ArrayList<Object>());
PreparedStatement stmt = conn.prepareStatement(qry.toString());
try {
for (int i = 0, n = parameters.size(); i < n; i++) {
stmt.setObject(i + 1, parameters.get(i));
}
ResultSet rs = stmt.executeQuery();
try {
if (rs.next()) {
return rs.getLong(1);
}
throw new RdbmsException("Could not determine size");
}
finally {
rs.close();
}
}
finally {
stmt.close();
}
}
protected int getMaxQueueSize() {
return STMT_BUFFER;
}
private synchronized void acquireLock()
throws InterruptedException
{
if (readLock == null) {
readLock = vf.getIdReadLock();
}
}
private synchronized void releaseLock() {
if (readLock != null) {
readLock.release();
readLock = null;
}
}
private String buildContextQuery()
throws SQLException
{
if (statements.isEmpty()) {
return null;
}
String tableName = statements.getCombinedTableName();
SqlQueryBuilder query = factory.createSqlQueryBuilder();
query.select().column("t", "ctx");
query.select().append("CASE WHEN MIN(u.value) IS NOT NULL THEN MIN(u.value) ELSE MIN(b.value) END");
SqlJoinBuilder join = query.from(tableName, "t");
join.leftjoin(bnodes.getName(), "b").on("id", "t.ctx");
join.leftjoin(uris.getShortTableName(), "u").on("id", "t.ctx");
SqlBracketBuilder open = query.filter().and().open();
open.column("u", "value").isNotNull();
open.or();
open.column("b", "value").isNotNull();
open.close();
query.groupBy("t.ctx");
return query.toString();
}
private SqlQueryBuilder buildCountQuery(RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
RdbmsResource... ctxs)
throws RdbmsException, SQLException
{
String tableName = statements.getTableName(vf.getInternalId(pred));
SqlQueryBuilder query = factory.createSqlQueryBuilder();
query.select().append("COUNT(*)");
query.from(tableName, "t");
if (ctxs != null && ctxs.length > 0) {
Number[] ids = new Number[ctxs.length];
for (int i = 0; i < ids.length; i++) {
ids[i] = vf.getInternalId(ctxs[i]);
}
query.filter().and().columnIn("t", "ctx", ids);
}
if (subj != null) {
Number id = vf.getInternalId(subj);
query.filter().and().columnEquals("t", "subj", id);
}
if (pred != null) {
Number id = vf.getInternalId(pred);
if (statements.isPredColumnPresent(id)) {
query.filter().and().columnEquals("t", "pred", id);
}
}
if (obj != null) {
Number id = vf.getInternalId(obj);
query.filter().and().columnEquals("t", "obj", id);
}
return query;
}
private String buildDeleteQuery(String tableName, RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
RdbmsResource... ctxs)
throws RdbmsException, SQLException
{
StringBuilder sb = new StringBuilder();
sb.append("DELETE FROM ").append(tableName);
return buildWhere(sb, subj, pred, obj, ctxs);
}
private SqlQueryBuilder buildSelectQuery(RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
RdbmsResource... ctxs)
throws RdbmsException, SQLException
{
String tableName = statements.getTableName(vf.getInternalId(pred));
SqlQueryBuilder query = factory.createSqlQueryBuilder();
query.select().column("t", "ctx");
query.select().append(
"CASE WHEN cu.value IS NOT NULL THEN cu.value WHEN clu.value IS NOT NULL THEN clu.value ELSE cb.value END");
query.select().column("t", "subj");
query.select().append(
"CASE WHEN su.value IS NOT NULL THEN su.value WHEN slu.value IS NOT NULL THEN slu.value ELSE sb.value END");
query.select().column("pu", "id");
query.select().column("pu", "value");
query.select().column("t", "obj");
query.select().append(
"CASE WHEN ou.value IS NOT NULL THEN ou.value" + " WHEN olu.value IS NOT NULL THEN olu.value"
+ " WHEN ob.value IS NOT NULL THEN ob.value"
+ " WHEN ol.value IS NOT NULL THEN ol.value ELSE oll.value END");
query.select().column("od", "value");
query.select().column("og", "value");
SqlJoinBuilder join;
if (pred != null) {
join = query.from(uris.getShortTableName(), "pu");
// TODO what about long predicate URIs?
join = join.join(tableName, "t");
}
else {
join = query.from(tableName, "t");
}
if (pred == null) {
join.join(uris.getShortTableName(), "pu").on("id", "t.pred");
}
join.leftjoin(uris.getShortTableName(), "cu").on("id", "t.ctx");
join.leftjoin(uris.getLongTableName(), "clu").on("id", "t.ctx");
join.leftjoin(bnodes.getName(), "cb").on("id", "t.ctx");
join.leftjoin(uris.getShortTableName(), "su").on("id", "t.subj");
join.leftjoin(uris.getLongTableName(), "slu").on("id", "t.subj");
join.leftjoin(bnodes.getName(), "sb").on("id", "t.subj");
join.leftjoin(uris.getShortTableName(), "ou").on("id", "t.obj");
join.leftjoin(uris.getLongTableName(), "olu").on("id", "t.obj");
join.leftjoin(bnodes.getName(), "ob").on("id", "t.obj");
join.leftjoin(literals.getLabelTable().getName(), "ol").on("id", "t.obj");
join.leftjoin(literals.getLongLabelTable().getName(), "oll").on("id", "t.obj");
join.leftjoin(literals.getLanguageTable().getName(), "og").on("id", "t.obj");
join.leftjoin(literals.getDatatypeTable().getName(), "od").on("id", "t.obj");
if (ctxs != null && ctxs.length > 0) {
Number[] ids = new Number[ctxs.length];
for (int i = 0; i < ids.length; i++) {
ids[i] = vf.getInternalId(ctxs[i]);
}
query.filter().and().columnIn("t", "ctx", ids);
}
if (subj != null) {
Number id = vf.getInternalId(subj);
query.filter().and().columnEquals("t", "subj", id);
}
if (pred != null) {
Number id = vf.getInternalId(pred);
query.filter().and().columnEquals("pu", "id", id);
if (statements.isPredColumnPresent(id)) {
query.filter().and().columnEquals("t", "pred", id);
}
}
if (obj != null) {
Number id = vf.getInternalId(obj);
query.filter().and().columnEquals("t", "obj", id);
}
return query;
}
private String buildWhere(StringBuilder sb, RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
RdbmsResource... ctxs)
{
sb.append("\nWHERE 1=1");
if (ctxs != null && ctxs.length > 0) {
sb.append(" AND (");
for (int i = 0; i < ctxs.length; i++) {
sb.append("ctx = ?");
if (i < ctxs.length - 1) {
sb.append(" OR ");
}
}
sb.append(")");
}
if (subj != null) {
sb.append(" AND subj = ?");
}
if (pred != null) {
sb.append(" AND pred = ?");
}
if (obj != null) {
sb.append(" AND obj = ?");
}
return sb.toString();
}
private void insert(RdbmsStatement st)
throws RdbmsException, SQLException, InterruptedException
{
Number ctx = vf.getInternalId(st.getContext());
Number subj = vf.getInternalId(st.getSubject());
Number pred = vf.getPredicateId(st.getPredicate());
Number obj = vf.getInternalId(st.getObject());
manager.insert(ctx, subj, pred, obj);
}
private void setSelectQuery(PreparedStatement stmt, RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
RdbmsResource... ctxs)
throws SQLException, RdbmsException
{
int p = 0;
if (ctxs != null && ctxs.length > 0) {
for (int i = 0; i < ctxs.length; i++) {
if (ctxs[i] == null) {
stmt.setLong(++p, ValueTable.NIL_ID);
}
else {
stmt.setObject(++p, vf.getInternalId(ctxs[i]));
}
}
}
if (subj != null) {
stmt.setObject(++p, vf.getInternalId(subj));
}
if (pred != null) {
stmt.setObject(++p, vf.getInternalId(pred));
}
if (obj != null) {
stmt.setObject(++p, vf.getInternalId(obj));
}
}
}