1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include "ResultSet.hxx"
21 #include "ResultSetMetaData.hxx"
22 #include "Util.hxx"
23 
24 #include <comphelper/sequence.hxx>
25 #include <cppuhelper/typeprovider.hxx>
26 #include <cppuhelper/supportsservice.hxx>
27 #include <connectivity/dbexception.hxx>
28 #include <propertyids.hxx>
29 #include <rtl/string.hxx>
30 #include <rtl/ustrbuf.hxx>
31 #include <sal/log.hxx>
32 #include <time.h>
33 #include <TConnection.hxx>
34 
35 #include <com/sun/star/beans/PropertyAttribute.hpp>
36 #include <com/sun/star/lang/DisposedException.hpp>
37 #include <com/sun/star/sdbc/DataType.hpp>
38 #include <com/sun/star/sdbcx/CompareBookmark.hpp>
39 
40 using namespace ::comphelper;
41 using namespace ::connectivity;
42 using namespace ::connectivity::firebird;
43 using namespace ::cppu;
44 using namespace ::dbtools;
45 using namespace ::osl;
46 
47 using namespace ::com::sun::star;
48 using namespace ::com::sun::star::uno;
49 using namespace ::com::sun::star::lang;
50 using namespace ::com::sun::star::beans;
51 using namespace ::com::sun::star::sdbc;
52 using namespace ::com::sun::star::sdbcx;
53 using namespace ::com::sun::star::container;
54 using namespace ::com::sun::star::io;
55 using namespace ::com::sun::star::util;
56 
57 OResultSet::OResultSet(Connection* pConnection,
58                        ::osl::Mutex& rMutex,
59                        const uno::Reference< XInterface >& xStatement,
60                        isc_stmt_handle aStatementHandle,
61                        XSQLDA* pSqlda )
62     : OResultSet_BASE(rMutex)
63     , OPropertyContainer(OResultSet_BASE::rBHelper)
64     , m_bIsBookmarkable(false)
65     , m_nFetchSize(1)
66     , m_nResultSetType(css::sdbc::ResultSetType::FORWARD_ONLY)
67     , m_nFetchDirection(css::sdbc::FetchDirection::FORWARD)
68     , m_nResultSetConcurrency(css::sdbc::ResultSetConcurrency::READ_ONLY)
69     , m_pConnection(pConnection)
70     , m_rMutex(rMutex)
71     , m_xStatement(xStatement)
72     , m_pSqlda(pSqlda)
73     , m_statementHandle(aStatementHandle)
74     , m_bWasNull(false)
75     , m_currentRow(0)
76     , m_bIsAfterLastRow(false)
77     , m_fieldCount(pSqlda? pSqlda->sqld : 0)
78 {
79     SAL_INFO("connectivity.firebird", "OResultSet().");
80     registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISBOOKMARKABLE),
81                      PROPERTY_ID_ISBOOKMARKABLE,
82                      PropertyAttribute::READONLY,
83                      &m_bIsBookmarkable,
84                      cppu::UnoType<decltype(m_bIsBookmarkable)>::get());
85     registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHSIZE),
86                      PROPERTY_ID_FETCHSIZE,
87                      PropertyAttribute::READONLY,
88                      &m_nFetchSize,
89                      cppu::UnoType<decltype(m_nFetchSize)>::get());
90     registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETTYPE),
91                      PROPERTY_ID_RESULTSETTYPE,
92                      PropertyAttribute::READONLY,
93                      &m_nResultSetType,
94                      cppu::UnoType<decltype(m_nResultSetType)>::get());
95     registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHDIRECTION),
96                      PROPERTY_ID_FETCHDIRECTION,
97                      PropertyAttribute::READONLY,
98                      &m_nFetchDirection,
99                      cppu::UnoType<decltype(m_nFetchDirection)>::get());
100     registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETCONCURRENCY),
101                      PROPERTY_ID_RESULTSETCONCURRENCY,
102                      PropertyAttribute::READONLY,
103                      &m_nResultSetConcurrency,
104                      cppu::UnoType<decltype(m_nResultSetConcurrency)>::get());
105 
106     if (!pSqlda)
107         return; // TODO: what?
108 
109 }
110 
111 OResultSet::~OResultSet()
112 {
113 }
114 
115 // ---- XResultSet -- Row retrieval methods ------------------------------------
116 sal_Int32 SAL_CALL OResultSet::getRow()
117 {
118     MutexGuard aGuard(m_rMutex);
119     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
120 
121     return m_currentRow;
122 }
123 
124 sal_Bool SAL_CALL OResultSet::next()
125 {
126     MutexGuard aGuard(m_rMutex);
127     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
128 
129     m_currentRow++;
130 
131     ISC_STATUS fetchStat = isc_dsql_fetch(m_statusVector,
132                                &m_statementHandle,
133                                1,
134                                m_pSqlda);
135     if (fetchStat == 0)         // SUCCESSFUL
136     {
137         return true;
138     }
139     else if (fetchStat == 100) // END OF DATASET
140     {
141         m_bIsAfterLastRow = true;
142         return false;
143     }
144     else
145     {
146         SAL_WARN("connectivity.firebird", "Error when fetching data");
147         // Throws sql exception as appropriate
148         evaluateStatusVector(m_statusVector, "isc_dsql_fetch", *this);
149         return false;
150     }
151 }
152 
153 sal_Bool SAL_CALL OResultSet::previous()
154 {
155     ::dbtools::throwFunctionNotSupportedSQLException("previous not supported in firebird",
156                                                   *this);
157     return false;
158 }
159 
160 sal_Bool SAL_CALL OResultSet::isLast()
161 {
162     ::dbtools::throwFunctionNotSupportedSQLException("isLast not supported in firebird",
163                                                   *this);
164     return false;
165 }
166 
167 sal_Bool SAL_CALL OResultSet::isBeforeFirst()
168 {
169     MutexGuard aGuard(m_rMutex);
170     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
171 
172     return m_currentRow == 0;
173 }
174 
175 sal_Bool SAL_CALL OResultSet::isAfterLast()
176 {
177     MutexGuard aGuard(m_rMutex);
178     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
179 
180     return m_bIsAfterLastRow;
181 }
182 
183 sal_Bool SAL_CALL OResultSet::isFirst()
184 {
185     MutexGuard aGuard(m_rMutex);
186     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
187 
188     return m_currentRow == 1 && !m_bIsAfterLastRow;
189 }
190 
191 void SAL_CALL OResultSet::beforeFirst()
192 {
193     MutexGuard aGuard(m_rMutex);
194     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
195 
196     if (m_currentRow != 0)
197         ::dbtools::throwFunctionNotSupportedSQLException("beforeFirst not supported in firebird",
198                                                       *this);
199 }
200 
201 void SAL_CALL OResultSet::afterLast()
202 {
203     MutexGuard aGuard(m_rMutex);
204     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
205 
206     if (!m_bIsAfterLastRow)
207         ::dbtools::throwFunctionNotSupportedSQLException("afterLast not supported in firebird",
208                                                       *this);
209 }
210 
211 sal_Bool SAL_CALL OResultSet::first()
212 {
213     MutexGuard aGuard(m_rMutex);
214     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
215 
216     if (m_currentRow == 0)
217     {
218         return next();
219     }
220     else if (m_currentRow == 1 && !m_bIsAfterLastRow)
221     {
222         return true;
223     }
224     else
225     {
226         ::dbtools::throwFunctionNotSupportedSQLException("first not supported in firebird",
227                                                       *this);
228         return false;
229     }
230 }
231 
232 sal_Bool SAL_CALL OResultSet::last()
233 {
234     // We need to iterate past the last row to know when we've passed the last
235     // row, hence we can't actually move to last.
236     ::dbtools::throwFunctionNotSupportedSQLException("last not supported in firebird",
237                                                   *this);
238     return false;
239 }
240 
241 sal_Bool SAL_CALL OResultSet::absolute(sal_Int32 aRow)
242 {
243     MutexGuard aGuard(m_rMutex);
244     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
245 
246     if (aRow > m_currentRow)
247     {
248         sal_Int32 aIterations = aRow - m_currentRow;
249         return relative(aIterations);
250     }
251     else
252     {
253         ::dbtools::throwFunctionNotSupportedSQLException("absolute not supported in firebird",
254                                                       *this);
255         return false;
256     }
257 }
258 
259 sal_Bool SAL_CALL OResultSet::relative(sal_Int32 row)
260 {
261     MutexGuard aGuard(m_rMutex);
262     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
263 
264     if (row > 0)
265     {
266         while (row--)
267         {
268             if (!next())
269                 return false;
270         }
271         return true;
272     }
273     else
274     {
275         ::dbtools::throwFunctionNotSupportedSQLException("relative not supported in firebird",
276                                                       *this);
277         return false;
278     }
279 }
280 
281 void OResultSet::checkColumnIndex(sal_Int32 nIndex)
282 {
283     MutexGuard aGuard(m_rMutex);
284     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
285 
286     if( nIndex < 1 || nIndex > m_fieldCount )
287     {
288         ::dbtools::throwSQLException(
289             "No column " + OUString::number(nIndex),
290             ::dbtools::StandardSQLState::COLUMN_NOT_FOUND,
291             *this);
292     }
293 }
294 
295 void OResultSet::checkRowIndex()
296 {
297     MutexGuard aGuard(m_rMutex);
298     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
299 
300     if((m_currentRow < 1) || m_bIsAfterLastRow)
301     {
302         ::dbtools::throwSQLException(
303             "Invalid Row",
304             ::dbtools::StandardSQLState::INVALID_CURSOR_POSITION,
305             *this);
306     }
307 }
308 
309 Any SAL_CALL OResultSet::queryInterface( const Type & rType )
310 {
311     Any aRet = OPropertySetHelper::queryInterface(rType);
312     return aRet.hasValue() ? aRet : OResultSet_BASE::queryInterface(rType);
313 }
314 
315  Sequence<  Type > SAL_CALL OResultSet::getTypes()
316 {
317     return concatSequences(OPropertySetHelper::getTypes(), OResultSet_BASE::getTypes());
318 }
319 // ---- XColumnLocate ---------------------------------------------------------
320 sal_Int32 SAL_CALL OResultSet::findColumn(const OUString& rColumnName)
321 {
322     MutexGuard aGuard(m_rMutex);
323     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
324 
325     uno::Reference< XResultSetMetaData > xMeta = getMetaData();
326     sal_Int32 nLen = xMeta->getColumnCount();
327     sal_Int32 i;
328 
329     for(i = 1; i<=nLen; ++i)
330     {
331         // We assume case sensitive, otherwise you'd have to test
332         // xMeta->isCaseSensitive and use qualsIgnoreAsciiCase as needed.
333         if (rColumnName == xMeta->getColumnName(i))
334             return i;
335     }
336 
337     ::dbtools::throwInvalidColumnException(rColumnName, *this);
338     assert(false);
339     return 0; // Never reached
340 }
341 
342 uno::Reference< XInputStream > SAL_CALL OResultSet::getBinaryStream( sal_Int32 )
343 {
344     MutexGuard aGuard(m_rMutex);
345     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
346 
347     return nullptr;
348 }
349 
350 uno::Reference< XInputStream > SAL_CALL OResultSet::getCharacterStream( sal_Int32 )
351 {
352     MutexGuard aGuard(m_rMutex);
353     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
354 
355     return nullptr;
356 }
357 
358 // ---- Internal Utilities ---------------------------------------------------
359 bool OResultSet::isNull(const sal_Int32 nColumnIndex)
360 {
361     assert(nColumnIndex <= m_fieldCount);
362     XSQLVAR* pVar = m_pSqlda->sqlvar;
363 
364     if (pVar[nColumnIndex-1].sqltype & 1) // Indicates column may contain null
365     {
366         if (*pVar[nColumnIndex-1].sqlind == -1)
367             return true;
368     }
369     return false;
370 }
371 
372 template <typename T>
373 OUString OResultSet::makeNumericString(const sal_Int32 nColumnIndex)
374 {
375     //  minus because firebird stores scale as a negative number
376     int nDecimalCount = -(m_pSqlda->sqlvar[nColumnIndex-1].sqlscale);
377     if(nDecimalCount < 0)
378     {
379         // scale should be always positive
380         assert(false);
381         return OUString();
382     }
383 
384     OUStringBuffer sRetBuffer;
385     T nAllDigits = *reinterpret_cast<T*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
386     sal_Int64 nDecimalCountExp = pow10Integer(nDecimalCount);
387 
388     if(nAllDigits < 0)
389     {
390         sRetBuffer.append('-');
391         nAllDigits = -nAllDigits; // abs
392     }
393 
394     sRetBuffer.append(static_cast<sal_Int64>(nAllDigits / nDecimalCountExp) );
395     if( nDecimalCount > 0)
396     {
397         sRetBuffer.append('.');
398 
399         sal_Int64 nFractionalPart = nAllDigits % nDecimalCountExp;
400 
401         int iCount = 0; // digit count
402         sal_Int64 nFracTemp = nFractionalPart;
403         while(nFracTemp>0)
404         {
405             nFracTemp /= 10;
406             iCount++;
407         }
408 
409         int nMissingNulls = nDecimalCount - iCount;
410 
411         // append nulls after dot and before nFractionalPart
412         for(int i=0; i<nMissingNulls; i++)
413         {
414             sRetBuffer.append('0');
415         }
416 
417         // the rest
418         sRetBuffer.append(nFractionalPart);
419     }
420 
421     return sRetBuffer.makeStringAndClear();
422 }
423 
424 template <typename T>
425 T OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT nType)
426 {
427     m_bWasNull = isNull(nColumnIndex);
428     if (m_bWasNull)
429         return T();
430 
431     if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == nType)
432         return *reinterpret_cast<T*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
433     else
434         return retrieveValue< ORowSetValue >(nColumnIndex, 0);
435 }
436 
437 template <>
438 ORowSetValue OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/)
439 {
440     // See http://wiki.openoffice.org/wiki/Documentation/DevGuide/Database/Using_the_getXXX_Methods
441     // (bottom of page) for a chart of possible conversions, we should allow all
442     // of these -- Blob/Clob will probably need some specialist handling especially
443     // w.r.t. to generating Strings for them.
444     //
445     // Basically we just have to map to the correct direct request and
446     // ORowSetValue does the rest for us here.
447     int nSqlSubType = m_pSqlda->sqlvar[nColumnIndex-1].sqlsubtype;
448 
449     // TODO Firebird 3.0 does not set subtype (i.e. set to 0) for computed numeric/decimal value.
450     // It may change in the future.
451     // Imply numeric data type when subtype is 0 and scale is negative
452     if( nSqlSubType == 0 && m_pSqlda->sqlvar[nColumnIndex-1].sqlscale < 0 )
453         nSqlSubType = 1;
454 
455     switch (m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1)
456     {
457         case SQL_TEXT:
458         case SQL_VARYING:
459             return getString(nColumnIndex);
460         case SQL_SHORT:
461             if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal
462                 return getString(nColumnIndex);
463             return getShort(nColumnIndex);
464         case SQL_LONG:
465             if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal
466                 return getString(nColumnIndex);
467             return getInt(nColumnIndex);
468         case SQL_FLOAT:
469             return getFloat(nColumnIndex);
470         case SQL_DOUBLE:
471             if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal
472                 return getString(nColumnIndex);
473             return getDouble(nColumnIndex);
474         case SQL_D_FLOAT:
475             return getFloat(nColumnIndex);
476         case SQL_TIMESTAMP:
477             return getTimestamp(nColumnIndex);
478         case SQL_TYPE_TIME:
479             return getTime(nColumnIndex);
480         case SQL_TYPE_DATE:
481             return getDate(nColumnIndex);
482         case SQL_INT64:
483             if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal
484                 return getString(nColumnIndex);
485             return getLong(nColumnIndex);
486         case SQL_BOOLEAN:
487             return ORowSetValue(bool(getBoolean(nColumnIndex)));
488         case SQL_BLOB:
489         case SQL_NULL:
490         case SQL_QUAD:
491         case SQL_ARRAY:
492             // TODO: these are all invalid conversions, so maybe we should
493             // throw an exception?
494             return ORowSetValue();
495         default:
496             assert(false);
497             return ORowSetValue();
498     }
499 }
500 
501 template <>
502 Date OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/)
503 {
504     if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == SQL_TYPE_DATE)
505     {
506         ISC_DATE aISCDate = *reinterpret_cast<ISC_DATE*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
507 
508         struct tm aCTime;
509         isc_decode_sql_date(&aISCDate, &aCTime);
510 
511         return Date(aCTime.tm_mday, aCTime.tm_mon + 1, aCTime.tm_year + 1900);
512     }
513     else
514     {
515         return retrieveValue< ORowSetValue >(nColumnIndex, 0);
516     }
517 }
518 
519 template <>
520 Time OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/)
521 {
522     if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == SQL_TYPE_TIME)
523     {
524         ISC_TIME aISCTime = *reinterpret_cast<ISC_TIME*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
525 
526         struct tm aCTime;
527         isc_decode_sql_time(&aISCTime, &aCTime);
528 
529         // First field is nanoseconds.
530         // last field denotes UTC (true) or unknown (false)
531         // Here we "know" that ISC_TIME is simply in units of seconds/ISC_TIME_SECONDS_PRECISION
532         // with no other funkiness, so we can get the fractional seconds easily.
533         return Time((aISCTime % ISC_TIME_SECONDS_PRECISION) * (1000000000 / ISC_TIME_SECONDS_PRECISION),
534                     aCTime.tm_sec, aCTime.tm_min, aCTime.tm_hour, false);
535     }
536     else
537     {
538         return retrieveValue< ORowSetValue >(nColumnIndex, 0);
539     }
540 }
541 
542 template <>
543 DateTime OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/)
544 {
545     if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == SQL_TIMESTAMP)
546     {
547         ISC_TIMESTAMP aISCTimestamp = *reinterpret_cast<ISC_TIMESTAMP*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
548 
549         struct tm aCTime;
550         isc_decode_timestamp(&aISCTimestamp, &aCTime);
551 
552         // Ditto here, see comment in previous function about ISC_TIME and ISC_TIME_SECONDS_PRECISION.
553         return DateTime((aISCTimestamp.timestamp_time % ISC_TIME_SECONDS_PRECISION) * (1000000000 / ISC_TIME_SECONDS_PRECISION), //nanoseconds
554                         aCTime.tm_sec,
555                         aCTime.tm_min,
556                         aCTime.tm_hour,
557                         aCTime.tm_mday,
558                         aCTime.tm_mon + 1, // tm is from 0 to 11
559                         aCTime.tm_year + 1900, //tm_year is the years since 1900
560                         false); // denotes UTC (true), or unknown (false)
561     }
562     else
563     {
564         return retrieveValue< ORowSetValue >(nColumnIndex, 0);
565     }
566 }
567 
568 template <>
569 OUString OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/)
570 {
571     // &~1 to remove the "can contain NULL" indicator
572     int aSqlType = m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1;
573     int aSqlSubType = m_pSqlda->sqlvar[nColumnIndex-1].sqlsubtype;
574     if (aSqlType == SQL_TEXT )
575     {
576         return OUString(m_pSqlda->sqlvar[nColumnIndex-1].sqldata,
577                         m_pSqlda->sqlvar[nColumnIndex-1].sqllen,
578                         RTL_TEXTENCODING_UTF8);
579     }
580     else if (aSqlType == SQL_VARYING)
581     {
582         // First 2 bytes are a short containing the length of the string
583         // No idea if sqllen is still valid here?
584         sal_uInt16 aLength = *reinterpret_cast<sal_uInt16*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
585         return OUString(m_pSqlda->sqlvar[nColumnIndex-1].sqldata + 2,
586                         aLength,
587                         RTL_TEXTENCODING_UTF8);
588     }
589     else if ((aSqlType == SQL_SHORT || aSqlType == SQL_LONG ||
590               aSqlType == SQL_DOUBLE || aSqlType == SQL_INT64)
591           && (aSqlSubType == 1 ||
592               aSqlSubType == 2 ||
593               (aSqlSubType == 0 && m_pSqlda->sqlvar[nColumnIndex-1].sqlscale < 0) ) )
594     {
595         // decimal and numeric types
596         switch(aSqlType)
597         {
598             case SQL_SHORT:
599                 return makeNumericString<sal_Int16>(nColumnIndex);
600             case SQL_LONG:
601                 return makeNumericString<sal_Int32>(nColumnIndex);
602             case SQL_DOUBLE:
603                 // TODO FIXME 64 bits?
604             case SQL_INT64:
605                 return makeNumericString<sal_Int64>(nColumnIndex);
606             default:
607                 assert(false);
608                 return OUString(); // never reached
609         }
610     }
611     else if(aSqlType == SQL_BLOB && aSqlSubType == static_cast<short>(BlobSubtype::Clob) )
612     {
613         uno::Reference<XClob> xClob = getClob(nColumnIndex);
614         return xClob->getSubString( 0, xClob->length() );
615     }
616     else
617     {
618         return retrieveValue< ORowSetValue >(nColumnIndex, 0);
619     }
620 }
621 
622 template <>
623 ISC_QUAD* OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT nType)
624 {
625     // TODO: this is probably wrong
626     if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) != nType)
627         throw SQLException(); // TODO: better exception (can't convert Blob)
628 
629     return reinterpret_cast<ISC_QUAD*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata);
630 }
631 
632 template <typename T>
633 T OResultSet::safelyRetrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT nType)
634 {
635     MutexGuard aGuard(m_rMutex);
636     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
637 
638     checkColumnIndex(nColumnIndex);
639     checkRowIndex();
640 
641     m_bWasNull = isNull(nColumnIndex);
642     if (m_bWasNull)
643         return T();
644 
645     return retrieveValue< T >(nColumnIndex, nType);
646 }
647 
648 // ---- XRow -----------------------------------------------------------------
649 sal_Bool SAL_CALL OResultSet::wasNull()
650 {
651     MutexGuard aGuard(m_rMutex);
652     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
653 
654     return m_bWasNull;
655 }
656 
657 // ---- XRow: Simple Numerical types ------------------------------------------
658 sal_Bool SAL_CALL OResultSet::getBoolean(sal_Int32 nColumnIndex)
659 {
660     return safelyRetrieveValue< bool >(nColumnIndex, SQL_BOOLEAN);
661 }
662 
663 sal_Int8 SAL_CALL OResultSet::getByte(sal_Int32 nColumnIndex)
664 {
665     // Not a native firebird type hence we always have to convert.
666     return safelyRetrieveValue< ORowSetValue >(nColumnIndex);
667 }
668 
669 Sequence< sal_Int8 > SAL_CALL OResultSet::getBytes(sal_Int32)
670 {
671     return Sequence< sal_Int8 >(); // TODO: implement
672     //return safelyRetrieveValue(columnIndex);
673 }
674 
675 sal_Int16 SAL_CALL OResultSet::getShort(sal_Int32 columnIndex)
676 {
677     return safelyRetrieveValue< sal_Int16 >(columnIndex, SQL_SHORT);
678 }
679 
680 sal_Int32 SAL_CALL OResultSet::getInt(sal_Int32 columnIndex)
681 {
682     return safelyRetrieveValue< sal_Int32 >(columnIndex, SQL_LONG);
683 }
684 
685 sal_Int64 SAL_CALL OResultSet::getLong(sal_Int32 columnIndex)
686 {
687     return safelyRetrieveValue< sal_Int64 >(columnIndex, SQL_INT64);
688 }
689 
690 float SAL_CALL OResultSet::getFloat(sal_Int32 columnIndex)
691 {
692     return safelyRetrieveValue< float >(columnIndex, SQL_FLOAT);
693 }
694 
695 double SAL_CALL OResultSet::getDouble(sal_Int32 columnIndex)
696 {
697     return safelyRetrieveValue< double >(columnIndex, SQL_DOUBLE);
698 }
699 
700 // ---- XRow: More complex types ----------------------------------------------
701 OUString SAL_CALL OResultSet::getString(sal_Int32 nIndex)
702 {
703     return safelyRetrieveValue< OUString >(nIndex);
704 }
705 
706 Date SAL_CALL OResultSet::getDate(sal_Int32 nIndex)
707 {
708     return safelyRetrieveValue< Date >(nIndex, SQL_TYPE_DATE);
709 }
710 
711 Time SAL_CALL OResultSet::getTime(sal_Int32 nIndex)
712 {
713     return safelyRetrieveValue< css::util::Time >(nIndex, SQL_TYPE_TIME);
714 }
715 
716 DateTime SAL_CALL OResultSet::getTimestamp(sal_Int32 nIndex)
717 {
718     return safelyRetrieveValue< DateTime >(nIndex, SQL_TIMESTAMP);
719 }
720 
721 
722 uno::Reference< XResultSetMetaData > SAL_CALL OResultSet::getMetaData(  )
723 {
724     MutexGuard aGuard(m_rMutex);
725     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
726 
727     if(!m_xMetaData.is())
728         m_xMetaData = new OResultSetMetaData(m_pConnection
729                                            , m_pSqlda);
730     return m_xMetaData;
731 }
732 
733 uno::Reference< XArray > SAL_CALL OResultSet::getArray( sal_Int32 )
734 {
735     MutexGuard aGuard(m_rMutex);
736     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
737 
738     return nullptr;
739 }
740 
741 
742 uno::Reference< XClob > SAL_CALL OResultSet::getClob( sal_Int32 columnIndex )
743 {
744     MutexGuard aGuard(m_rMutex);
745     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
746 
747     int aSqlSubType = m_pSqlda->sqlvar[columnIndex-1].sqlsubtype;
748 
749     SAL_WARN_IF(aSqlSubType != 1,
750         "connectivity.firebird", "wrong subtype, not a textual blob");
751 
752     ISC_QUAD* pBlobID = safelyRetrieveValue< ISC_QUAD* >(columnIndex, SQL_BLOB);
753     if (!pBlobID)
754         return nullptr;
755     return m_pConnection->createClob(pBlobID);
756 }
757 
758 uno::Reference< XBlob > SAL_CALL OResultSet::getBlob(sal_Int32 columnIndex)
759 {
760     MutexGuard aGuard(m_rMutex);
761     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
762 
763     // TODO: CLOB etc. should be valid here too, but we probably want some more
764     // cleverness around this.
765     ISC_QUAD* pBlobID = safelyRetrieveValue< ISC_QUAD* >(columnIndex, SQL_BLOB);
766     if (!pBlobID)
767         return nullptr;
768     return m_pConnection->createBlob(pBlobID);
769 }
770 
771 
772 uno::Reference< XRef > SAL_CALL OResultSet::getRef( sal_Int32 )
773 {
774     MutexGuard aGuard(m_rMutex);
775     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
776 
777     return nullptr;
778 }
779 
780 
781 Any SAL_CALL OResultSet::getObject( sal_Int32, const uno::Reference< css::container::XNameAccess >& )
782 {
783     MutexGuard aGuard(m_rMutex);
784     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
785 
786     return Any();
787 }
788 
789 
790 void SAL_CALL OResultSet::close()
791 {
792     SAL_INFO("connectivity.firebird", "close().");
793 
794     {
795         MutexGuard aGuard(m_rMutex);
796         checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
797     }
798     dispose();
799 }
800 
801 
802 uno::Reference< XInterface > SAL_CALL OResultSet::getStatement()
803 {
804     MutexGuard aGuard(m_rMutex);
805     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
806 
807     return m_xStatement;
808 }
809 //----- XResultSet: unsupported change detection methods ---------------------
810 sal_Bool SAL_CALL OResultSet::rowDeleted()
811 {
812     ::dbtools::throwFunctionNotSupportedSQLException("rowDeleted not supported in firebird",
813                                                   *this);
814     return false;
815 }
816 sal_Bool SAL_CALL OResultSet::rowInserted()
817 {
818     ::dbtools::throwFunctionNotSupportedSQLException("rowInserted not supported in firebird",
819                                                   *this);
820     return false;
821 }
822 
823 sal_Bool SAL_CALL OResultSet::rowUpdated()
824 {
825     ::dbtools::throwFunctionNotSupportedSQLException("rowUpdated not supported in firebird",
826                                                   *this);
827     return false;
828 }
829 
830 void SAL_CALL OResultSet::refreshRow()
831 {
832     ::dbtools::throwFunctionNotSupportedSQLException("refreshRow not supported in firebird",
833                                                   *this);
834 }
835 
836 
837 void SAL_CALL OResultSet::cancel(  )
838 {
839     MutexGuard aGuard(m_rMutex);
840     checkDisposed(OResultSet_BASE::rBHelper.bDisposed);
841 
842 }
843 
844 //----- OIdPropertyArrayUsageHelper ------------------------------------------
845 IPropertyArrayHelper* OResultSet::createArrayHelper() const
846 {
847     Sequence< Property > aProperties;
848     describeProperties(aProperties);
849     return new ::cppu::OPropertyArrayHelper(aProperties);
850 }
851 
852 IPropertyArrayHelper & OResultSet::getInfoHelper()
853 {
854     return *getArrayHelper();
855 }
856 
857 void SAL_CALL OResultSet::acquire() throw()
858 {
859     OResultSet_BASE::acquire();
860 }
861 
862 void SAL_CALL OResultSet::release() throw()
863 {
864     OResultSet_BASE::release();
865 }
866 
867 uno::Reference< css::beans::XPropertySetInfo > SAL_CALL OResultSet::getPropertySetInfo(  )
868 {
869     return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper());
870 }
871 
872 // ---- XServiceInfo -----------------------------------------------------------
873 OUString SAL_CALL OResultSet::getImplementationName()
874 {
875     return "com.sun.star.sdbcx.firebird.ResultSet";
876 }
877 
878 Sequence< OUString > SAL_CALL OResultSet::getSupportedServiceNames()
879 {
880     return {"com.sun.star.sdbc.ResultSet","com.sun.star.sdbcx.ResultSet"};
881 }
882 
883 sal_Bool SAL_CALL OResultSet::supportsService(const OUString& _rServiceName)
884 {
885     return cppu::supportsService(this, _rServiceName);
886 }
887 
888 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
889