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 | |
40 | Soprano::ODBC::QueryResult::QueryResult() |
41 | : d( new QueryResultPrivate() ) |
42 | { |
43 | } |
44 | |
45 | |
46 | Soprano::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 | |
55 | QStringList 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 | |
94 | bool 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 | |
111 | Soprano::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 | |
308 | bool 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 | |
316 | bool 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 | |