1/*
2 * This file is part of Soprano Project
3 *
4 * Copyright (C) 2008-2012 Sebastian Trueg <trueg@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "virtuosomodel.h"
23#include "virtuosomodel_p.h"
24#include "virtuosoqueryresultiteratorbackend.h"
25#include "virtuosoqueryresultiteratorbackend_p.h"
26#include "virtuosobackend.h"
27#include "soprano.h"
28#include "odbcenvironment.h"
29#include "odbcconnection.h"
30#include "odbcconnectionpool.h"
31#include "virtuosotools.h"
32
33#include <QtCore/QDebug>
34#include <QtCore/QDir>
35
36#include <stdlib.h>
37
38
39namespace {
40 QString nodeToN3( const Soprano::Node& node ) {
41 if ( node.isBlank() ) {
42 // looks like Virtuoso needs a special syntax here, at least that is what the redland bindings do.
43 return '<' + node.toN3() + '>';
44 }
45 else {
46 return node.toN3();
47 }
48 }
49
50 // there is still a bug in Virtuoso which makes the define prefix unusable: if a query defines the
51 // graph the result will be empty if the exclude graph is specified.
52 const char* s_queryPrefix =
53 "sparql ";
54// "define input:default-graph-exclude <http://www.openlinksw.com/schemas/virtrdf#> "
55// "define input:named-graph-exclude <http://www.openlinksw.com/schemas/virtrdf#>";
56}
57
58QString Soprano::VirtuosoModelPrivate::statementToConstructGraphPattern( const Soprano::Statement& s,
59 bool withContext,
60 bool parameterized) const
61{
62 QString query;
63
64 if ( withContext ) {
65 query += QLatin1String( "graph " );
66 if ( s.context().isValid() ) {
67 if (parameterized && !s.context().isBlank())
68 query += QLatin1String("bif:__rdf_long_from_batch_params(\?\?,\?\?,\?\?)");
69 else
70 query += nodeToN3( s.context() );
71 }
72 else {
73 query += QLatin1String( "?g" );
74 }
75 query += QLatin1String( " { " );
76 }
77
78 if ( s.subject().isValid() ) {
79 if (parameterized && !s.subject().isBlank())
80 query += QLatin1String("`bif:__rdf_long_from_batch_params(\?\?,\?\?,\?\?)` ");
81 else
82 query += nodeToN3( s.subject() ) + ' ';
83 }
84 else {
85 query += QLatin1String( "?s " );
86 }
87
88 if ( s.predicate().isValid() ) {
89 if (parameterized)
90 query += QLatin1String("`bif:__rdf_long_from_batch_params(\?\?,\?\?,\?\?)` ");
91 else
92 query += nodeToN3( s.predicate() ) + ' ';
93 }
94 else {
95 query += QLatin1String( "?p " );
96 }
97
98 if ( s.object().isValid() ) {
99 if (parameterized && !s.object().isBlank()) {
100 query += QLatin1String("`bif:__rdf_long_from_batch_params(\?\?,\?\?,\?\?)`");
101 }
102 else {
103 if ( m_fakeBooleans && s.object().literal().isBool() )
104 query += Soprano::Node( Soprano::LiteralValue::fromString( s.object().literal().toBool() ? QString( QLatin1String( "true" ) ) : QLatin1String("false"),
105 Soprano::Virtuoso::fakeBooleanType() ) ).toN3();
106 else if ( s.object().literal().isByteArray() )
107 query += Soprano::Node( Soprano::LiteralValue::fromString( s.object().literal().toString(),
108 Soprano::Virtuoso::fakeBase64BinaryType() ) ).toN3();
109 else
110 query += nodeToN3( s.object() );
111 }
112 }
113 else {
114 query += QLatin1String( "?o" );
115 }
116
117 if ( withContext ) {
118 query += QLatin1String( " . }" );
119 }
120
121 return query;
122}
123
124
125Soprano::QueryResultIterator Soprano::VirtuosoModelPrivate::sqlQuery( const QString& query )
126{
127 if ( ODBC::Connection* conn = connectionPool->connection() ) {
128 ODBC::QueryResult* result = conn->executeQuery( query );
129 if ( result ) {
130 q->clearError();
131 Virtuoso::QueryResultIteratorBackend* backend = new Virtuoso::QueryResultIteratorBackend( this, result );
132 return backend;
133 }
134 else {
135 qDebug() << "Query failed:" << query;
136 q->setError( conn->lastError() );
137 return 0;
138 }
139 }
140 else {
141 q->setError( connectionPool->lastError() );
142 return 0;
143 }
144}
145
146Soprano::QueryResultIterator Soprano::VirtuosoModelPrivate::sparqlQuery( const QString& query )
147{
148 return sqlQuery( QLatin1String( s_queryPrefix ) + query );
149}
150
151
152QString Soprano::VirtuosoModelPrivate::replaceFakeTypesInQuery( const QString& query )
153{
154 if( !m_fakeBooleans )
155 return query;
156
157 QMutexLocker lock( &m_fakeBooleanRegExpMutex );
158 return QString(query).replace( m_fakeBooleanRegExp, QString::fromLatin1("'\\2'^^<%1>").arg( Virtuoso::fakeBooleanTypeString() ) );
159}
160
161
162Soprano::VirtuosoModel::VirtuosoModel( const QString &virtuosoVersion, ODBC::ConnectionPool* connectionPool,
163 bool supportFakeBooleans, bool emptyGraphs, const Backend* b )
164 : StorageModel(b),
165 d( new VirtuosoModelPrivate() )
166{
167 d->q = this;
168 d->m_virtuosoVersion = virtuosoVersion;
169 d->connectionPool = connectionPool;
170 d->m_fakeBooleans = supportFakeBooleans;
171 d->m_supportEmptyGraphs = emptyGraphs;
172}
173
174
175Soprano::VirtuosoModel::~VirtuosoModel()
176{
177 d->closeAllIterators();
178 delete d->connectionPool;
179 delete d;
180}
181
182
183Soprano::Error::ErrorCode Soprano::VirtuosoModel::addStatement( const Statement& statement )
184{
185// qDebug() << Q_FUNC_INFO << statement;
186
187 if( !statement.isValid() ) {
188 qDebug() << Q_FUNC_INFO << "Cannot add invalid statement:" << statement;
189 setError( "Cannot add invalid statement.", Error::ErrorInvalidArgument );
190 return Error::ErrorInvalidArgument;
191 }
192
193 Statement s( statement );
194 if( !s.context().isValid() ) {
195 if ( d->m_supportEmptyGraphs ) {
196 s.setContext( Virtuoso::defaultGraph() );
197 }
198 else {
199 qDebug() << Q_FUNC_INFO << "Cannot add invalid statement:" << statement;
200 setError( "Cannot add statement with invalid context", Error::ErrorInvalidArgument );
201 return Error::ErrorInvalidArgument;
202 }
203 }
204
205 // for adding statements we use ODBC parameters which are way more efficient than plain query strings, especially for long values
206 QString insert = QLatin1String("sparql insert into ") + d->statementToConstructGraphPattern( s, true, true );
207 QList<Node> paramNodes;
208 if(statement.context().isValid() && !statement.context().isBlank())
209 paramNodes << statement.context();
210 else
211 paramNodes << Virtuoso::defaultGraph();
212 if(statement.subject().isValid() && !statement.subject().isBlank())
213 paramNodes << statement.subject();
214 if(statement.predicate().isValid())
215 paramNodes << statement.predicate();
216 if(statement.object().isValid() && !statement.object().isBlank())
217 paramNodes << statement.object();
218
219 if ( ODBC::Connection* conn = d->connectionPool->connection() ) {
220
221 if ( conn->executeCommand( insert, paramNodes ) == Error::ErrorNone ) {
222 clearError();
223
224 if(!d->m_noStatementSignals) {
225 emit statementAdded( statement );
226 emit statementsAdded();
227 }
228
229 return Error::ErrorNone;
230 }
231 else {
232 setError( conn->lastError() );
233 }
234 }
235 else {
236 setError( d->connectionPool->lastError() );
237 }
238 return Error::convertErrorCode( lastError().code() );
239}
240
241
242// TODO: use "select GRAPH_IRI from DB.DBA.SPARQL_SELECT_KNOWN_GRAPHS_T"
243Soprano::NodeIterator Soprano::VirtuosoModel::listContexts() const
244{
245// qDebug() << Q_FUNC_INFO;
246
247 return d->sparqlQuery( QString::fromLatin1( "select distinct ?g where { "
248 "graph ?g { ?s ?p ?o . } . "
249 "FILTER(?g != <%1> && ?g != <%2>) . }" )
250 .arg( QLatin1String( Virtuoso::defaultGraphString() ),
251 QLatin1String( Virtuoso::openlinkVirtualGraphString() ) ) )
252 .iterateBindings( 0 );
253}
254
255
256bool Soprano::VirtuosoModel::containsStatement( const Statement& statement ) const
257{
258// qDebug() << Q_FUNC_INFO << statement;
259
260 // fail on invalid statements
261 if ( !statement.isValid() ) {
262 setError( "Cannot call containsStatement on invalid statements", Error::ErrorInvalidArgument );
263 return false;
264 }
265
266 Statement s( statement );
267 if( !statement.context().isValid() ) {
268 if ( d->m_supportEmptyGraphs ) {
269 s.setContext( Virtuoso::defaultGraph() );
270 } else {
271 qDebug() << Q_FUNC_INFO << "Invalid Context " << statement;
272 setError( "Found invalid context", Error::ErrorInvalidArgument );
273 return Error::ErrorInvalidArgument;
274 }
275 }
276
277 return containsAnyStatement( s );
278}
279
280
281bool Soprano::VirtuosoModel::containsAnyStatement( const Statement& statement ) const
282{
283// qDebug() << Q_FUNC_INFO << statement;
284
285 QString query;
286 if ( statement.context().isValid() )
287 query = QString::fromLatin1( "ask from %1 where { %2 . }" ).arg( statement.context().toN3(), d->statementToConstructGraphPattern( statement, false ) );
288 else
289 query = QString::fromLatin1( "ask where { %1 . }" ).arg( d->statementToConstructGraphPattern( statement, true ) );
290// if ( VirtuosoStatementHandler* sh = d->connection.execute( "sparql " + query ) ) {
291// bool b = sh->fetchScroll();
292// delete sh;
293// return b;
294// }
295// return false;
296 return d->sparqlQuery( query ).boolValue();
297}
298
299
300Soprano::StatementIterator Soprano::VirtuosoModel::listStatements( const Statement& partial ) const
301{
302// qDebug() << Q_FUNC_INFO << partial;
303
304 // we cannot use a construct query due to missing graph support
305 QString query;
306 if ( partial.context().isValid() )
307 query = QString::fromLatin1( "select * from %1 where { %2 . }" )
308 .arg( partial.context().toN3(), d->statementToConstructGraphPattern( partial, false ) );
309 else
310 query = QString::fromLatin1( "select * where { %1 . FILTER(?g != <%2>) . }" )
311 .arg( d->statementToConstructGraphPattern( partial, true ),
312 QLatin1String( Virtuoso::openlinkVirtualGraphString() ) );
313// qDebug() << "List Statements Query" << query;
314 return d->sparqlQuery( query )
315 .iterateStatementsFromBindings( partial.subject().isValid() ? QString() : QString( 's' ),
316 partial.predicate().isValid() ? QString() : QString( 'p' ),
317 partial.object().isValid() ? QString() : QString( 'o' ),
318 partial.context().isValid() ? QString() : QString( 'g' ),
319 partial );
320}
321
322
323Soprano::Error::ErrorCode Soprano::VirtuosoModel::removeStatement( const Statement& statement )
324{
325// qDebug() << Q_FUNC_INFO << statement;
326
327 if ( !statement.isValid() ) {
328 setError( "Cannot remove invalid statement.", Error::ErrorInvalidArgument );
329 return Error::ErrorInvalidArgument;
330 }
331
332 Statement s( statement );
333 if( !s.context().isValid() ) {
334 if ( d->m_supportEmptyGraphs ) {
335 s.setContext( Virtuoso::defaultGraph() );
336 } else {
337 qDebug() << Q_FUNC_INFO << "Cannot remove invalid statement:" << statement;
338 setError( "Cannot remove statement with invalid context", Error::ErrorInvalidArgument );
339 return Error::ErrorInvalidArgument;
340 }
341 }
342 else if ( s.context().uri() == Virtuoso::openlinkVirtualGraph() ) {
343 setError( "Cannot remove statements from the virtual openlink graph. Virtuoso would not like that.", Error::ErrorInvalidArgument );
344 return Error::ErrorInvalidArgument;
345 }
346
347 QString query = QString::fromLatin1( "delete from %1" )
348 .arg( d->statementToConstructGraphPattern( s, true ) );
349// qDebug() << "removeStatement query:" << query;
350 if ( ODBC::Connection* conn = d->connectionPool->connection() ) {
351 if ( conn->executeCommand( QLatin1String( "sparql " ) + query ) == Error::ErrorNone ) {
352 if(!d->m_noStatementSignals) {
353 // FIXME: can this be done with SQL/RDF views?
354 emit statementRemoved( statement );
355 emit statementsRemoved();
356 }
357 }
358 setError( conn->lastError() );
359 }
360 else {
361 setError( d->connectionPool->lastError() );
362 }
363 return Error::convertErrorCode( lastError().code() );
364}
365
366
367Soprano::Error::ErrorCode Soprano::VirtuosoModel::removeAllStatements( const Statement& statement )
368{
369// qDebug() << Q_FUNC_INFO << statement;
370
371 QString query;
372 if ( statement.context().isValid() ) {
373 if ( statement.context().uri() == Virtuoso::openlinkVirtualGraph() ) {
374 setError( "Cannot remove statements from the virtual openlink graph. Virtuoso would not like that.", Error::ErrorInvalidArgument );
375 return Error::ErrorInvalidArgument;
376 }
377
378 if ( statement.context().isValid() &&
379 !statement.subject().isValid() &&
380 !statement.predicate().isValid() &&
381 !statement.object().isValid() ) {
382 // Virtuoso docu says this might be faster
383 query = QString::fromLatin1( "clear graph %1" ).arg( statement.context().toN3() );
384 }
385 else {
386 query = QString::fromLatin1( "delete from %1 { %2 } where { %3 }" )
387 .arg( statement.context().isValid() ? statement.context().toN3() : QString( "?g" ),
388 d->statementToConstructGraphPattern( statement, false ),
389 d->statementToConstructGraphPattern( statement, true ) );
390
391 }
392 }
393 else {
394 //
395 // Starting with version 6.1.5 Virtuoso now supports the SPARQL 1.1 delete variant which allows to omit
396 // the graph from the delete pattern.
397 // For versions before we need to use the old hacky method which requires iterating all graph candidates.
398 //
399 if( d->m_virtuosoVersion >= QLatin1String("6.1.5") ) {
400 query = QString::fromLatin1("delete { %1 } where { %1 }").arg( d->statementToConstructGraphPattern(statement, true) );
401 }
402 else {
403 QList<Node> allContexts = d->sparqlQuery( QString::fromLatin1( "select distinct ?g where { %1 . FILTER(?g != <%2>) . }" )
404 .arg( d->statementToConstructGraphPattern( statement, true ),
405 QLatin1String( Virtuoso::openlinkVirtualGraphString() ) ) )
406 .iterateBindings( 0 ).allNodes();
407 foreach( const Node& node, allContexts ) {
408 Statement s( statement );
409 if ( node.isValid() )
410 s.setContext( node );
411 else {
412 if( d->m_supportEmptyGraphs ) {
413 s.setContext( Virtuoso::defaultGraph() );
414 } else {
415 qDebug() << Q_FUNC_INFO << "Cannot remove invalid statement:" << statement;
416 setError( "Cannot remove statement with invalid context", Error::ErrorInvalidArgument );
417 return Error::ErrorInvalidArgument;
418 }
419 }
420 Error::ErrorCode c = removeAllStatements( s );
421 if ( c != Error::ErrorNone )
422 return c;
423 }
424 return Error::ErrorNone;
425 }
426 }
427
428 if ( ODBC::Connection* conn = d->connectionPool->connection() ) {
429 if ( conn->executeCommand( "sparql " + query ) == Error::ErrorNone ) {
430 if(!d->m_noStatementSignals) {
431 // FIXME: can this be done with SQL/RDF views?
432 emit statementsRemoved();
433 Statement signalStatement( statement );
434 if( signalStatement.context() == Virtuoso::defaultGraph() ) {
435 if( d->m_supportEmptyGraphs ) {
436 signalStatement.setContext( Node() );
437 } else {
438 qDebug() << Q_FUNC_INFO << "Cannot remove invalid statement:" << statement;
439 setError( "Cannot remove statement with invalid context", Error::ErrorInvalidArgument );
440 return Error::ErrorInvalidArgument;
441 }
442 }
443
444 emit statementRemoved( signalStatement );
445 }
446 }
447 setError( conn->lastError() );
448 }
449 else {
450 setError( d->connectionPool->lastError() );
451 }
452 return Error::convertErrorCode( lastError().code() );
453}
454
455
456int Soprano::VirtuosoModel::statementCount() const
457{
458// qDebug() << Q_FUNC_INFO;
459
460 QueryResultIterator it = d->sparqlQuery( QString::fromLatin1( "select count(*) where { "
461 "graph ?g { ?s ?p ?o . } . "
462 "FILTER(?g != <%1>) . }" )
463 .arg( QLatin1String( Virtuoso::openlinkVirtualGraphString() ) ) );
464 if ( it.isValid() && it.next() ) {
465 return it.binding( 0 ).literal().toInt();
466 }
467 else {
468 return -1;
469 }
470}
471
472
473Soprano::Node Soprano::VirtuosoModel::createBlankNode()
474{
475 setError( "createBlankNode not supported by the Virtuoso backend", Error::ErrorNotSupported );
476 return Node();
477}
478
479
480Soprano::QueryResultIterator Soprano::VirtuosoModel::executeQuery( const QString& query,
481 Query::QueryLanguage language,
482 const QString& userQueryLanguage ) const
483{
484 if ( language == Soprano::Query::QueryLanguageSparql ) {
485 return d->sparqlQuery( d->replaceFakeTypesInQuery( query ) );
486 }
487
488 else if( language == Soprano::Query::QueryLanguageUser &&
489 userQueryLanguage.toLower() == QLatin1String("sql") ) {
490 return d->sqlQuery( d->replaceFakeTypesInQuery( query ) );
491 }
492
493 else {
494 setError( Error::Error( QString::fromLatin1( "Unsupported query language %1." )
495 .arg( Query::queryLanguageToString( language, userQueryLanguage ) ) ) );
496 return QueryResultIterator();
497 }
498}
499
500
501void Soprano::VirtuosoModel::slotVirtuosoStopped(VirtuosoController::ExitStatus status)
502{
503 // inform clients about a non-scheduled exit of the server so they can act accordingly
504 // typically this would mean to re-create the model from the backend
505 // We do this async in case clients react by directly deleting us
506 QMetaObject::invokeMethod(this,
507 "virtuosoStopped",
508 Qt::QueuedConnection,
509 Q_ARG(bool, (!(status == VirtuosoController::CrashExit ||
510 status == VirtuosoController::ThirdPartyExit))));
511}
512
513