001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 2008.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.sail.rdbms;
007:
008: import java.sql.Connection;
009: import java.sql.PreparedStatement;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.Collections;
015: import java.util.LinkedList;
016: import java.util.List;
017: import java.util.concurrent.locks.Lock;
018:
019: import org.openrdf.model.Resource;
020: import org.openrdf.model.URI;
021: import org.openrdf.model.Value;
022: import org.openrdf.sail.SailException;
023: import org.openrdf.sail.helpers.DefaultSailChangedEvent;
024: import org.openrdf.sail.rdbms.evaluation.QueryBuilderFactory;
025: import org.openrdf.sail.rdbms.evaluation.SqlBracketBuilder;
026: import org.openrdf.sail.rdbms.evaluation.SqlJoinBuilder;
027: import org.openrdf.sail.rdbms.evaluation.SqlQueryBuilder;
028: import org.openrdf.sail.rdbms.exceptions.RdbmsException;
029: import org.openrdf.sail.rdbms.iteration.EmptyRdbmsResourceIteration;
030: import org.openrdf.sail.rdbms.iteration.EmptyRdbmsStatementIteration;
031: import org.openrdf.sail.rdbms.iteration.RdbmsResourceIteration;
032: import org.openrdf.sail.rdbms.iteration.RdbmsStatementIteration;
033: import org.openrdf.sail.rdbms.managers.TransTableManager;
034: import org.openrdf.sail.rdbms.managers.TripleManager;
035: import org.openrdf.sail.rdbms.model.RdbmsResource;
036: import org.openrdf.sail.rdbms.model.RdbmsStatement;
037: import org.openrdf.sail.rdbms.model.RdbmsURI;
038: import org.openrdf.sail.rdbms.model.RdbmsValue;
039: import org.openrdf.sail.rdbms.schema.BNodeTable;
040: import org.openrdf.sail.rdbms.schema.IdSequence;
041: import org.openrdf.sail.rdbms.schema.LiteralTable;
042: import org.openrdf.sail.rdbms.schema.URITable;
043: import org.openrdf.sail.rdbms.schema.ValueTable;
044:
045: /**
046: * Facade to {@link TransTableManager}, {@link ResourceTable}, and
047: * {@link LiteralTable} for adding, removing, and retrieving statements from the
048: * database.
049: *
050: * @author James Leigh
051: *
052: */
053: public class RdbmsTripleRepository {
054: public static int STMT_BUFFER = 32;
055: private Connection conn;
056: private RdbmsValueFactory vf;
057: private TransTableManager statements;
058: private QueryBuilderFactory factory;
059: private BNodeTable bnodes;
060: private URITable uris;
061: private LiteralTable literals;
062: private Lock readLock;
063: private DefaultSailChangedEvent sailChangedEvent;
064: private TripleManager manager;
065: private LinkedList<RdbmsStatement> queue = new LinkedList<RdbmsStatement>();
066: private IdSequence ids;
067:
068: public Connection getConnection() {
069: return conn;
070: }
071:
072: public void setConnection(Connection conn) {
073: this .conn = conn;
074: }
075:
076: public void setIdSequence(IdSequence ids) {
077: this .ids = ids;
078: }
079:
080: public RdbmsValueFactory getValueFactory() {
081: return vf;
082: }
083:
084: public void setValueFactory(RdbmsValueFactory vf) {
085: this .vf = vf;
086: }
087:
088: public DefaultSailChangedEvent getSailChangedEvent() {
089: return sailChangedEvent;
090: }
091:
092: public void setSailChangedEvent(
093: DefaultSailChangedEvent sailChangedEvent) {
094: this .sailChangedEvent = sailChangedEvent;
095: }
096:
097: public void setQueryBuilderFactory(QueryBuilderFactory factory) {
098: this .factory = factory;
099: }
100:
101: public void setBNodeTable(BNodeTable bnodes) {
102: this .bnodes = bnodes;
103: }
104:
105: public void setURITable(URITable uris) {
106: this .uris = uris;
107: }
108:
109: public void setLiteralTable(LiteralTable literals) {
110: this .literals = literals;
111: }
112:
113: public void setTransaction(TransTableManager temporary) {
114: this .statements = temporary;
115: }
116:
117: public void setTripleManager(TripleManager tripleManager) {
118: this .manager = tripleManager;
119: }
120:
121: public void flush() throws RdbmsException {
122: try {
123: synchronized (queue) {
124: while (!queue.isEmpty()) {
125: insert(queue.removeFirst());
126: }
127: }
128: vf.flush();
129: manager.flush();
130: } catch (SQLException e) {
131: throw new RdbmsException(e);
132: } catch (InterruptedException e) {
133: throw new RdbmsException(e);
134: }
135: }
136:
137: public synchronized void begin() throws SQLException {
138: conn.setAutoCommit(false);
139: }
140:
141: public synchronized void close() throws SQLException {
142: manager.close();
143: if (!conn.getAutoCommit()) {
144: conn.rollback();
145: }
146: conn.setAutoCommit(true);
147: conn.close();
148: }
149:
150: public synchronized void commit() throws SQLException,
151: RdbmsException, InterruptedException {
152: synchronized (queue) {
153: while (!queue.isEmpty()) {
154: insert(queue.removeFirst());
155: }
156: }
157: manager.flush();
158: conn.commit();
159: conn.setAutoCommit(true);
160: if (readLock != null) {
161: readLock.unlock();
162: readLock = null;
163: }
164: Lock writeLock = vf.getIdWriteLock();
165: boolean locked = writeLock.tryLock();
166: try {
167: vf.flush();
168: statements.committed(locked);
169: } finally {
170: if (locked) {
171: writeLock.unlock();
172: }
173: }
174: }
175:
176: public void rollback() throws SQLException, SailException {
177: synchronized (queue) {
178: queue.clear();
179: }
180: manager.clear();
181: if (!conn.getAutoCommit()) {
182: conn.rollback();
183: conn.setAutoCommit(true);
184: }
185: if (readLock != null) {
186: readLock.unlock();
187: readLock = null;
188: }
189: }
190:
191: public void add(RdbmsStatement st) throws SailException,
192: SQLException, InterruptedException {
193: if (readLock == null) {
194: readLock = vf.getIdReadLock();
195: readLock.lock();
196: }
197: synchronized (queue) {
198: queue.add(st);
199: if (queue.size() > getMaxQueueSize()) {
200: insert(queue.removeFirst());
201: }
202: }
203: }
204:
205: public RdbmsStatementIteration find(Resource subj, URI pred,
206: Value obj, Resource... ctxs) throws RdbmsException {
207: try {
208: RdbmsResource s = vf.asRdbmsResource(subj);
209: RdbmsURI p = vf.asRdbmsURI(pred);
210: RdbmsValue o = vf.asRdbmsValue(obj);
211: RdbmsResource[] c = vf.asRdbmsResource(ctxs);
212: flush();
213: SqlQueryBuilder query = buildSelectQuery(s, p, o, c);
214: if (query == null)
215: return new EmptyRdbmsStatementIteration();
216: List<?> parameters = query
217: .findParameters(new ArrayList<Object>());
218: PreparedStatement stmt = conn.prepareStatement(query
219: .toString());
220: try {
221: for (int i = 0, n = parameters.size(); i < n; i++) {
222: stmt.setObject(i + 1, parameters.get(i));
223: }
224: return new RdbmsStatementIteration(vf, stmt, ids);
225: } catch (SQLException e) {
226: stmt.close();
227: throw e;
228: }
229: } catch (SQLException e) {
230: throw new RdbmsException(e);
231: }
232:
233: }
234:
235: public RdbmsResourceIteration findContexts() throws SQLException,
236: RdbmsException {
237: flush();
238: String qry = buildContextQuery();
239: if (qry == null)
240: return new EmptyRdbmsResourceIteration();
241: PreparedStatement stmt = conn.prepareStatement(qry);
242: try {
243: return new RdbmsResourceIteration(vf, stmt);
244: } catch (SQLException e) {
245: stmt.close();
246: throw e;
247: }
248: }
249:
250: public boolean isClosed() throws SQLException {
251: return conn.isClosed();
252: }
253:
254: public int remove(Resource subj, URI pred, Value obj,
255: Resource... ctxs) throws RdbmsException {
256: RdbmsResource s = vf.asRdbmsResource(subj);
257: RdbmsURI p = vf.asRdbmsURI(pred);
258: RdbmsValue o = vf.asRdbmsValue(obj);
259: RdbmsResource[] c = vf.asRdbmsResource(ctxs);
260: flush();
261: try {
262: Collection<Number> predicates;
263: if (p == null) {
264: predicates = statements.getPredicateIds();
265: } else {
266: predicates = Collections.singleton(vf.getInternalId(p));
267: }
268: int total = 0;
269: for (Number id : predicates) {
270: String tableName = statements.findTableName(id);
271: if (!statements.isPredColumnPresent(id)) {
272: p = null;
273: }
274: String query = buildDeleteQuery(tableName, s, p, o, c);
275: PreparedStatement stmt = conn.prepareStatement(query);
276: try {
277: setSelectQuery(stmt, s, p, o, c);
278: int count = stmt.executeUpdate();
279: statements.removed(id, count);
280: total += count;
281: } finally {
282: stmt.close();
283: }
284: }
285: if (total > 0) {
286: sailChangedEvent.setStatementsRemoved(true);
287: }
288: return total;
289: } catch (SQLException e) {
290: throw new RdbmsException(e);
291: }
292: }
293:
294: public long size(RdbmsResource... ctxs) throws SQLException,
295: SailException {
296: flush();
297: String qry = buildCountQuery(ctxs);
298: if (qry == null)
299: return 0;
300: PreparedStatement stmt = conn.prepareStatement(qry);
301: try {
302: setCountQuery(stmt, ctxs);
303: ResultSet rs = stmt.executeQuery();
304: try {
305: if (rs.next())
306: return rs.getLong(1);
307: throw new RdbmsException("Could not determine size");
308: } finally {
309: rs.close();
310: }
311: } finally {
312: stmt.close();
313: }
314: }
315:
316: protected int getMaxQueueSize() {
317: return STMT_BUFFER;
318: }
319:
320: private String buildContextQuery() throws SQLException {
321: if (statements.isEmpty())
322: return null;
323: String tableName = statements.getCombinedTableName();
324: SqlQueryBuilder query = factory.createSqlQueryBuilder();
325: query.select().column("t", "ctx");
326: query
327: .select()
328: .append(
329: "CASE WHEN MIN(u.value) IS NOT NULL THEN MIN(u.value) ELSE MIN(b.value) END");
330: SqlJoinBuilder join = query.from(tableName, "t");
331: join.leftjoin(bnodes.getName(), "b").on("id", "t.ctx");
332: join.leftjoin(uris.getShortTableName(), "u").on("id", "t.ctx");
333: SqlBracketBuilder open = query.filter().and().open();
334: open.column("u", "value").isNotNull();
335: open.or();
336: open.column("b", "value").isNotNull();
337: open.close();
338: query.groupBy("t.ctx");
339: return query.toString();
340: }
341:
342: private String buildCountQuery(RdbmsResource... ctxs)
343: throws SQLException {
344: String tableName = statements.getCombinedTableName();
345: StringBuilder sb = new StringBuilder();
346: sb.append("SELECT COUNT(*) FROM ");
347: sb.append(tableName).append(" t");
348: if (ctxs != null && ctxs.length > 0) {
349: sb.append("\nWHERE ");
350: for (int i = 0; i < ctxs.length; i++) {
351: sb.append("t.ctx = ?");
352: if (i < ctxs.length - 1) {
353: sb.append(" OR ");
354: }
355: }
356: }
357: return sb.toString();
358: }
359:
360: private String buildDeleteQuery(String tableName,
361: RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
362: RdbmsResource... ctxs) throws RdbmsException, SQLException {
363: StringBuilder sb = new StringBuilder();
364: sb.append("DELETE FROM ").append(tableName);
365: return buildWhere(sb, subj, pred, obj, ctxs);
366: }
367:
368: private SqlQueryBuilder buildSelectQuery(RdbmsResource subj,
369: RdbmsURI pred, RdbmsValue obj, RdbmsResource... ctxs)
370: throws RdbmsException, SQLException {
371: String tableName = statements.getTableName(vf
372: .getInternalId(pred));
373: SqlQueryBuilder query = factory.createSqlQueryBuilder();
374: query.select().column("t", "ctx");
375: query
376: .select()
377: .append(
378: "CASE WHEN cu.value IS NOT NULL THEN cu.value WHEN clu.value IS NOT NULL THEN clu.value ELSE cb.value END");
379: query.select().column("t", "subj");
380: query
381: .select()
382: .append(
383: "CASE WHEN su.value IS NOT NULL THEN su.value WHEN slu.value IS NOT NULL THEN slu.value ELSE sb.value END");
384: query.select().column("pu", "id");
385: query.select().column("pu", "value");
386: query.select().column("t", "obj");
387: query
388: .select()
389: .append(
390: "CASE WHEN ou.value IS NOT NULL THEN ou.value"
391: + " WHEN olu.value IS NOT NULL THEN olu.value"
392: + " WHEN ob.value IS NOT NULL THEN ob.value"
393: + " WHEN ol.value IS NOT NULL THEN ol.value ELSE oll.value END");
394: query.select().column("od", "value");
395: query.select().column("og", "value");
396: SqlJoinBuilder join;
397: if (pred != null) {
398: join = query.from(uris.getShortTableName(), "pu");
399: // TODO what about long predicate URIs?
400: join = join.join(tableName, "t");
401: } else {
402: join = query.from(tableName, "t");
403: }
404: if (pred == null) {
405: join.join(uris.getShortTableName(), "pu")
406: .on("id", "t.pred");
407: }
408: join.leftjoin(uris.getShortTableName(), "cu").on("id", "t.ctx");
409: join.leftjoin(uris.getLongTableName(), "clu").on("id", "t.ctx");
410: join.leftjoin(bnodes.getName(), "cb").on("id", "t.ctx");
411: join.leftjoin(uris.getShortTableName(), "su")
412: .on("id", "t.subj");
413: join.leftjoin(uris.getLongTableName(), "slu")
414: .on("id", "t.subj");
415: join.leftjoin(bnodes.getName(), "sb").on("id", "t.subj");
416: join.leftjoin(uris.getShortTableName(), "ou").on("id", "t.obj");
417: join.leftjoin(uris.getLongTableName(), "olu").on("id", "t.obj");
418: join.leftjoin(bnodes.getName(), "ob").on("id", "t.obj");
419: join.leftjoin(literals.getLabelTable().getName(), "ol").on(
420: "id", "t.obj");
421: join.leftjoin(literals.getLongLabelTable().getName(), "oll")
422: .on("id", "t.obj");
423: join.leftjoin(literals.getLanguageTable().getName(), "og").on(
424: "id", "t.obj");
425: join.leftjoin(literals.getDatatypeTable().getName(), "od").on(
426: "id", "t.obj");
427: if (ctxs != null && ctxs.length > 0) {
428: Number[] ids = new Number[ctxs.length];
429: for (int i = 0; i < ids.length; i++) {
430: ids[i] = vf.getInternalId(ctxs[i]);
431: }
432: query.filter().and().columnIn("t", "ctx", ids);
433: }
434: if (subj != null) {
435: Number id = vf.getInternalId(subj);
436: query.filter().and().columnEquals("t", "subj", id);
437: }
438: if (pred != null) {
439: Number id = vf.getInternalId(pred);
440: query.filter().and().columnEquals("pu", "id", id);
441: if (statements.isPredColumnPresent(id)) {
442: query.filter().and().columnEquals("t", "pred", id);
443: }
444: }
445: if (obj != null) {
446: Number id = vf.getInternalId(obj);
447: query.filter().and().columnEquals("t", "obj", id);
448: }
449: return query;
450: }
451:
452: private String buildWhere(StringBuilder sb, RdbmsResource subj,
453: RdbmsURI pred, RdbmsValue obj, RdbmsResource... ctxs) {
454: sb.append("\nWHERE 1=1");
455: if (ctxs != null && ctxs.length > 0) {
456: sb.append(" AND (");
457: for (int i = 0; i < ctxs.length; i++) {
458: sb.append("ctx = ?");
459: if (i < ctxs.length - 1) {
460: sb.append(" OR ");
461: }
462: }
463: sb.append(")");
464: }
465: if (subj != null) {
466: sb.append(" AND subj = ?");
467: }
468: if (pred != null) {
469: sb.append(" AND pred = ?");
470: }
471: if (obj != null) {
472: sb.append(" AND obj = ?");
473: }
474: return sb.toString();
475: }
476:
477: private void insert(RdbmsStatement st) throws RdbmsException,
478: SQLException, InterruptedException {
479: Number ctx = vf.getInternalId(st.getContext());
480: Number subj = vf.getInternalId(st.getSubject());
481: Number pred = vf.getPredicateId(st.getPredicate());
482: Number obj = vf.getInternalId(st.getObject());
483: manager.insert(ctx, subj, pred, obj);
484: }
485:
486: private void setCountQuery(PreparedStatement stmt,
487: RdbmsResource... ctxs) throws SQLException, RdbmsException {
488: if (ctxs != null && ctxs.length > 0) {
489: for (int i = 0; i < ctxs.length; i++) {
490: stmt.setObject(i + 1, vf.getInternalId(ctxs[i]));
491: }
492: }
493: }
494:
495: private void setSelectQuery(PreparedStatement stmt,
496: RdbmsResource subj, RdbmsURI pred, RdbmsValue obj,
497: RdbmsResource... ctxs) throws SQLException, RdbmsException {
498: int p = 0;
499: if (ctxs != null && ctxs.length > 0) {
500: for (int i = 0; i < ctxs.length; i++) {
501: if (ctxs[i] == null) {
502: stmt.setLong(++p, ValueTable.NIL_ID);
503: } else {
504: stmt.setObject(++p, vf.getInternalId(ctxs[i]));
505: }
506: }
507: }
508: if (subj != null) {
509: stmt.setObject(++p, vf.getInternalId(subj));
510: }
511: if (pred != null) {
512: stmt.setObject(++p, vf.getInternalId(pred));
513: }
514: if (obj != null) {
515: stmt.setObject(++p, vf.getInternalId(obj));
516: }
517: }
518:
519: }
|