1/*
2 * This file is part of Soprano Project
3 *
4 * Copyright (C) 2009 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 "odbcqueryresult.h"
23#include "odbcqueryresult_p.h"
24#include "odbcconnection_p.h"
25#include "virtuosoodbcext.h"
26#include "virtuosotools.h"
27
28#include "node.h"
29#include "literalvalue.h"
30#include "xsd.h"
31
32#include <QtCore/QMutex>
33#include <QtCore/QMutexLocker>
34#include <QtCore/QVariant>
35#include <QtCore/QStringList>
36#include <QtCore/QScopedPointer>
37#include <QtCore/QDebug>
38
39
40Soprano::ODBC::QueryResult::QueryResult()
41 : d( new QueryResultPrivate() )
42{
43}
44
45
46Soprano::ODBC::QueryResult::~QueryResult()
47{
48 d->m_conn->m_openResults.removeAll( this );
49 SQLCloseCursor( d->m_hstmt );
50 SQLFreeHandle( SQL_HANDLE_STMT, d->m_hstmt );
51 delete d;
52}
53
54
55QStringList Soprano::ODBC::QueryResult::resultColumns()
56{
57 if ( d->m_columns.isEmpty() ) {
58 SQLSMALLINT numCols = -1;
59 if ( SQLNumResultCols( d->m_hstmt, &numCols ) != SQL_SUCCESS ) {
60 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt ) );
61 }
62 else {
63 clearError();
64 d->m_columns.reserve( numCols );
65 d->m_columTypes.reserve( numCols );
66 for ( int col = 1; col <= numCols; ++col ) {
67 SQLTCHAR colName[51];
68 colName[50] = 0;
69 SQLSMALLINT colType;
70 if ( SQLDescribeCol( d->m_hstmt,
71 col,
72 (SQLTCHAR *) colName,
73 50,
74 0,
75 &colType,
76 0,
77 0,
78 0) == SQL_SUCCESS ) {
79 d->m_columns.append( QString::fromLatin1( ( const char* )colName ) );
80 d->m_columTypes.append( colType );
81 }
82 else {
83 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt, QLatin1String( "SQLDescribeCol failed" ) ) );
84 break;
85 }
86 }
87 }
88 }
89
90 return d->m_columns;
91}
92
93
94bool Soprano::ODBC::QueryResult::fetchRow()
95{
96 int sts = SQLFetch( d->m_hstmt );
97 if ( sts == SQL_NO_DATA_FOUND ) {
98 clearError();
99 return false;
100 }
101 else if( sts != SQL_SUCCESS ) {
102 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt, QLatin1String( "SQLFetch failed" ) ) );
103 return false;
104 }
105 else {
106 return true;
107 }
108}
109
110
111Soprano::Node Soprano::ODBC::QueryResult::getData( int colNum )
112{
113 SQLCHAR* data = 0;
114 SQLLEN length = 0;
115 if ( getCharData( colNum, &data, &length ) ) {
116 SQLHDESC hdesc = 0;
117 int dvtype = 0;
118
119 // easy mem cleanup: never care about data again below
120 QScopedPointer<SQLCHAR, QScopedPointerArrayDeleter<SQLCHAR> > dap( data );
121
122 //
123 // Before we can retrieve the column meta data using SQLGetDescField,
124 // we first needs to retrieve the correct descriptor handle attached to the statement handle
125 //
126 if ( !SQL_SUCCEEDED( SQLGetStmtAttr( d->m_hstmt, SQL_ATTR_IMP_ROW_DESC, &hdesc, SQL_IS_POINTER, 0 ) ) ) {
127 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt, QLatin1String( "SQLGetStmtAttr failed" ) ) );
128 return Node();
129 }
130
131 //
132 // Retrieve the datatype of a field
133 // Will yield one of the VIRTUOSO_DV_* defined in virtuosoodbcext.h
134 //
135 else if ( !SQL_SUCCEEDED( SQLGetDescField( hdesc, colNum, SQL_DESC_COL_DV_TYPE, &dvtype, SQL_IS_INTEGER, 0 ) ) ) {
136 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt, QLatin1String( "SQLGetDescField SQL_DESC_COL_DV_TYPE failed" ) ) );
137 return Node();
138 }
139
140 // The node we will construct below
141 Soprano::Node node;
142
143 switch (dvtype) {
144 case VIRTUOSO_DV_STRING: {
145 //
146 // Retrieve the flags associated with the field:
147 // 0 - field contains a normal string
148 // 1 - field contains an IRI string
149 // 2 - field contains a UTF-8 string
150 //
151 int boxFlags = 0;
152 if ( !SQL_SUCCEEDED( SQLGetDescField( hdesc, colNum, SQL_DESC_COL_BOX_FLAGS, &boxFlags, SQL_IS_INTEGER, 0 ) ) ) {
153 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt, QLatin1String( "SQLGetDescField failed" ) ) );
154 return Node();
155 }
156
157 if ( boxFlags & VIRTUOSO_BF_IRI ) {
158 if ( data && strncmp( (char*)data, "_:", 2 ) == 0 ) {
159 node = Node( QString::fromUtf8( reinterpret_cast<const char*>( data )+2 ) );
160 }
161 else {
162 node = Node( QUrl::fromEncoded( reinterpret_cast<const char*>( data ), QUrl::StrictMode ) );
163 }
164 }
165 else {
166 if ( data && strncmp( (char*)data, "nodeID://", 9 ) == 0 ) {
167 node = Node( QString::fromLatin1( reinterpret_cast<const char*>( data )+9 ) );
168 }
169 else if ( boxFlags & VIRTUOSO_BF_UTF8 ) {
170 node = Node( LiteralValue::createPlainLiteral( QString::fromUtf8( reinterpret_cast<const char*>( data ) ) ) );
171 }
172 else {
173 node = Node( LiteralValue::createPlainLiteral( QString::fromLatin1( reinterpret_cast<const char*>( data ) ) ) );
174 }
175 }
176 break;
177 }
178
179 case VIRTUOSO_DV_RDF: {
180 //
181 // Retrieve lang and type strings which are cached in the server for faster lookups
182 //
183 SQLCHAR typeBuf[100];
184 SQLINTEGER typeBufLen = 0;
185
186 bool fetchTypeSucceded = SQL_SUCCEEDED( SQLGetDescField( hdesc, colNum,
187 SQL_DESC_COL_LITERAL_TYPE,
188 typeBuf, sizeof( typeBuf ), &typeBufLen ) );
189
190 const char* str = reinterpret_cast<const char*>( data );
191
192 if( fetchTypeSucceded ) {
193 const char* typeStr = reinterpret_cast<const char*>( typeBuf );
194
195 if ( !qstrncmp( typeStr, Virtuoso::fakeBooleanTypeString(), typeBufLen ) ) {
196 node = Node( LiteralValue( !qstrcmp( "true", str ) ) );
197 }
198 else {
199 QUrl type;
200 // FIXME: Disable these checks based on the backend settings!
201 if ( !qstrncmp( typeStr, Virtuoso::fakeBase64BinaryTypeString(), typeBufLen ) )
202 type = Soprano::Vocabulary::XMLSchema::base64Binary();
203 else
204 type = QUrl::fromEncoded( QByteArray::fromRawData( typeStr, typeBufLen ), QUrl::StrictMode );
205 node = Node( LiteralValue::fromString( QString::fromUtf8( str ), type ) );
206 }
207 }
208 else {
209 SQLCHAR langBuf[100];
210 SQLINTEGER langBufLen = 0;
211
212 bool fetchLangSucceded = SQL_SUCCEEDED( SQLGetDescField( hdesc, colNum,
213 SQL_DESC_COL_LITERAL_LANG,
214 langBuf, sizeof( langBuf ), &langBufLen ) );
215
216 if( fetchLangSucceded ) {
217 QString lang = QString::fromLatin1( reinterpret_cast<const char*>( langBuf ), langBufLen );
218 node = Node( LiteralValue::createPlainLiteral( QString::fromUtf8( str ), lang ) );
219 }
220 else {
221 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt,
222 QLatin1String( "SQLGetDescField SQL_DESC_COL_LITERAL_* failed" ) ) );
223 return Node();
224 }
225 }
226 break;
227 }
228
229 case VIRTUOSO_DV_LONG_INT:
230 node = LiteralValue::fromString( QString::fromUtf8( reinterpret_cast<const char*>( data ) ), QVariant::Int );
231 break;
232
233 case VIRTUOSO_DV_SINGLE_FLOAT:
234 node = LiteralValue::fromString( QString::fromUtf8( reinterpret_cast<const char*>( data ) ), Vocabulary::XMLSchema::xsdFloat() );
235 break;
236
237 case VIRTUOSO_DV_DOUBLE_FLOAT:
238 node = LiteralValue::fromString( QString::fromUtf8( reinterpret_cast<const char*>( data ) ), QVariant::Double );
239 break;
240
241 case VIRTUOSO_DV_NUMERIC:
242 node = LiteralValue::fromString( QString::fromUtf8( reinterpret_cast<const char*>( data ) ), Vocabulary::XMLSchema::decimal() );
243 break;
244
245 case VIRTUOSO_DV_TIMESTAMP:
246 case VIRTUOSO_DV_DATE:
247 case VIRTUOSO_DV_TIME:
248 case VIRTUOSO_DV_DATETIME: {
249 //
250 // Retrieve the date subtype
251 // Will yield one of the VIRTUOSO_DT_TYPE_* defined in virtuosoodbcext.h
252 //
253 int dv_dt_type = 0;
254 if ( !SQL_SUCCEEDED( SQLGetDescField( hdesc, colNum, SQL_DESC_COL_DT_DT_TYPE, &dv_dt_type, SQL_IS_INTEGER, 0 ) ) ) {
255 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt, QLatin1String( "SQLGetDescField SQL_DESC_COL_DT_DT_TYPE failed" ) ) );
256 return Node();
257 }
258 QVariant::Type type;
259 switch( dv_dt_type ) {
260 case VIRTUOSO_DT_TYPE_DATE:
261 type = QVariant::Date;
262 break;
263 case VIRTUOSO_DT_TYPE_TIME:
264 type = QVariant::Time;
265 break;
266 default:
267 type = QVariant::DateTime;
268 break;
269 }
270 QString dts = QString::fromUtf8( reinterpret_cast<const char*>( data ) );
271 // Virtuoso returns datetime values with a space instead of a T: "2009-04-07 13:33:19.790"
272 dts.replace( ' ', 'T' );
273 node = LiteralValue::fromString( dts, type );
274 break;
275 }
276
277 case VIRTUOSO_DV_IRI_ID:
278 //
279 // node is an IRI ID
280 //
281 // It needs to be translated into a URIusing the
282 // ID_TO_IRI() function as the value is database specific.
283 //
284 // For now, we simply pass it on as a string literal
285 //
286 node = LiteralValue(QString::fromLatin1(reinterpret_cast<const char*>(data), length));
287 break;
288
289 case 204:
290 // VIRTUOSO_DV_DB_NULL
291 // a null node -> empty
292 break;
293
294 default:
295 qDebug("*unexpected result type %d*", dvtype);
296 setError( QString( "Internal Error: Unknown result type %1" ).arg( dvtype ) );
297 break;
298 }
299
300 return node;
301 }
302 else {
303 return Node();
304 }
305}
306
307
308bool Soprano::ODBC::QueryResult::isBlob( int colNum )
309{
310 return ( d->m_columTypes[colNum-1] == SQL_LONGVARCHAR ||
311 d->m_columTypes[colNum-1] == SQL_LONGVARBINARY ||
312 d->m_columTypes[colNum-1] == SQL_WLONGVARCHAR );
313}
314
315
316bool Soprano::ODBC::QueryResult::getCharData( int colNum, SQLCHAR** buffer, SQLLEN* length )
317{
318 // We pre alocate a buffer which can hold most of the values that we get.
319 // If it cannot, only then do we allocate the proper size
320 // This way we avoid the extra SQLGetData call
321 const static int bufSize = 100;
322 *buffer = new SQLCHAR[ bufSize ];
323
324 int r = SQLGetData( d->m_hstmt, colNum, SQL_C_CHAR, *buffer, bufSize, length );
325
326 if ( SQL_SUCCEEDED( r ) ) {
327 //
328 // Treat a 0 length and null data as an empty node
329 //
330 if ( *length == SQL_NULL_DATA || *length == 0 ) {
331 delete [] *buffer;
332 *buffer = 0;
333 *length = 0;
334 clearError();
335 return true;
336 }
337
338 // The -1 is because it is a null terminated string
339 if( *length > bufSize-1 ) {
340 SQLCHAR* oldBuffer = *buffer;
341
342 *buffer = new SQLCHAR[ *length + 4 ]; // FIXME: Why the +4 (I got this from the redland plugin)
343 memcpy( *buffer, oldBuffer, bufSize );
344 delete [] oldBuffer;
345
346 // The -1 is cause SQLGetData returns a null terminated string
347 SQLCHAR* newBuffer = (*buffer) + bufSize - 1;
348 int len = *length - ( bufSize - 1 ) + 1; // The +1 is for the null char
349
350 int r = SQLGetData( d->m_hstmt, colNum, SQL_C_CHAR, newBuffer, len, length );
351 if( SQL_SUCCEEDED( r ) ) {
352 clearError();
353 return true;
354 }
355 else {
356 delete [] *buffer;
357 *buffer = 0;
358 *length = 0;
359 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt,
360 QLatin1String( "SQLGetData failed" ) ) );
361 return false;
362 }
363 }
364 else {
365 clearError();
366 return true;
367 }
368 }
369 else {
370 delete [] *buffer;
371 *buffer = 0;
372 *length = 0;
373 setError( Virtuoso::convertSqlError( SQL_HANDLE_STMT, d->m_hstmt,
374 QLatin1String( "SQLGetData failed" ) ) );
375 return false;
376 }
377}
378