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