xref: /core/connectivity/source/drivers/firebird/PreparedStatement.cxx (revision 028ae168ca787e5c92560d051009a0f115911b57)
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 <sal/config.h>
21 #include <cmath>
22 
23 #include "Connection.hxx"
24 #include "PreparedStatement.hxx"
25 #include "ResultSet.hxx"
26 #include "ResultSetMetaData.hxx"
27 #include "Util.hxx"
28 
29 #include <comphelper/sequence.hxx>
30 #include <connectivity/dbexception.hxx>
31 #include <propertyids.hxx>
32 #include <connectivity/dbtools.hxx>
33 #include <sal/log.hxx>
34 
35 #include <com/sun/star/sdbc/DataType.hpp>
36 
37 using namespace connectivity::firebird;
38 
39 using namespace ::comphelper;
40 using namespace ::osl;
41 
42 using namespace com::sun::star;
43 using namespace com::sun::star::uno;
44 using namespace com::sun::star::lang;
45 using namespace com::sun::star::beans;
46 using namespace com::sun::star::sdbc;
47 using namespace com::sun::star::container;
48 using namespace com::sun::star::io;
49 using namespace com::sun::star::util;
50 
51 IMPLEMENT_SERVICE_INFO(OPreparedStatement,u"com.sun.star.sdbcx.firebird.PreparedStatement"_ustr,u"com.sun.star.sdbc.PreparedStatement"_ustr);
52 
53 constexpr size_t MAX_SIZE_SEGMENT = 65535; // max value of a segment of CLOB, if we want more than 65535 bytes, we need more segments
54 
55 
OPreparedStatement(Connection * _pConnection,const OUString & sql)56 OPreparedStatement::OPreparedStatement( Connection* _pConnection,
57                                         const OUString& sql)
58     :OStatementCommonBase(_pConnection)
59     ,m_sSqlStatement(sql)
60     ,m_pOutSqlda(nullptr)
61     ,m_pInSqlda(nullptr)
62 {
63     SAL_INFO("connectivity.firebird", "OPreparedStatement(). "
64              "sql: " << sql);
65 }
66 
ensurePrepared()67 void OPreparedStatement::ensurePrepared()
68 {
69     MutexGuard aGuard(m_aMutex);
70     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
71 
72     if (m_aStatementHandle)
73         return;
74 
75     ISC_STATUS aErr = 0;
76 
77     if (!m_pInSqlda)
78     {
79         m_pInSqlda = static_cast<XSQLDA*>(calloc(1, XSQLDA_LENGTH(10)));
80         assert(m_pInSqlda && "Don't handle OOM conditions");
81         m_pInSqlda->version = SQLDA_VERSION1;
82         m_pInSqlda->sqln = 10;
83     }
84 
85     prepareAndDescribeStatement(m_sSqlStatement, m_pOutSqlda);
86 
87     aErr = isc_dsql_describe_bind(m_statusVector,
88                                   &m_aStatementHandle,
89                                   1,
90                                   m_pInSqlda);
91 
92     if (aErr)
93     {
94         SAL_WARN("connectivity.firebird", "isc_dsql_describe_bind failed");
95     }
96     else if (m_pInSqlda->sqld > m_pInSqlda->sqln) // Not large enough
97     {
98         short nItems = m_pInSqlda->sqld;
99         free(m_pInSqlda);
100         m_pInSqlda = static_cast<XSQLDA*>(calloc(1, XSQLDA_LENGTH(nItems)));
101         assert(m_pInSqlda && "Don't handle OOM conditions");
102         m_pInSqlda->version = SQLDA_VERSION1;
103         m_pInSqlda->sqln = nItems;
104         aErr = isc_dsql_describe_bind(m_statusVector,
105                                       &m_aStatementHandle,
106                                       1,
107                                       m_pInSqlda);
108         SAL_WARN_IF(aErr, "connectivity.firebird", "isc_dsql_describe_bind failed");
109     }
110 
111     if (!aErr)
112         mallocSQLVAR(m_pInSqlda);
113     else
114         evaluateStatusVector(m_statusVector, m_sSqlStatement, *this);
115 }
116 
~OPreparedStatement()117 OPreparedStatement::~OPreparedStatement()
118 {
119 }
120 
acquire()121 void SAL_CALL OPreparedStatement::acquire() noexcept
122 {
123     OStatementCommonBase::acquire();
124 }
125 
release()126 void SAL_CALL OPreparedStatement::release() noexcept
127 {
128     OStatementCommonBase::release();
129 }
130 
queryInterface(const Type & rType)131 Any SAL_CALL OPreparedStatement::queryInterface(const Type& rType)
132 {
133     Any aRet = OStatementCommonBase::queryInterface(rType);
134     if(!aRet.hasValue())
135         aRet = OPreparedStatement_Base::queryInterface(rType);
136     return aRet;
137 }
138 
getTypes()139 uno::Sequence< Type > SAL_CALL OPreparedStatement::getTypes()
140 {
141     return concatSequences(OPreparedStatement_Base::getTypes(),
142                            OStatementCommonBase::getTypes());
143 }
144 
getMetaData()145 Reference< XResultSetMetaData > SAL_CALL OPreparedStatement::getMetaData()
146 {
147     ::osl::MutexGuard aGuard( m_aMutex );
148     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
149     ensurePrepared();
150 
151     if(!m_xMetaData.is())
152         m_xMetaData = new OResultSetMetaData(m_pConnection.get()
153                                            , m_pOutSqlda);
154 
155     return m_xMetaData;
156 }
157 
close()158 void SAL_CALL OPreparedStatement::close()
159 {
160     MutexGuard aGuard( m_aMutex );
161     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
162 
163     OStatementCommonBase::close();
164     if (m_pInSqlda)
165     {
166         freeSQLVAR(m_pInSqlda);
167         free(m_pInSqlda);
168         m_pInSqlda = nullptr;
169     }
170     if (m_pOutSqlda)
171     {
172         freeSQLVAR(m_pOutSqlda);
173         free(m_pOutSqlda);
174         m_pOutSqlda = nullptr;
175     }
176 }
177 
disposing()178 void SAL_CALL OPreparedStatement::disposing()
179 {
180     close();
181 }
182 
setString(sal_Int32 nParameterIndex,const OUString & sInput)183 void SAL_CALL OPreparedStatement::setString(sal_Int32 nParameterIndex,
184                                             const OUString& sInput)
185 {
186     SAL_INFO("connectivity.firebird",
187              "setString(" << nParameterIndex << " , " << sInput << ")");
188 
189     MutexGuard aGuard( m_aMutex );
190     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
191     ensurePrepared();
192 
193     checkParameterIndex(nParameterIndex);
194     setParameterNull(nParameterIndex, false);
195 
196     XSQLVAR* pVar = m_pInSqlda->sqlvar + (nParameterIndex - 1);
197     ColumnTypeInfo columnType(*pVar);
198 
199     switch (auto sdbcType = columnType.getSdbcType()) {
200     case DataType::VARCHAR:
201     case DataType::CHAR:
202     {
203         OString str = OUStringToOString(sInput, RTL_TEXTENCODING_UTF8);
204         const ISC_SHORT nLength = std::min(str.getLength(), static_cast<sal_Int32>(pVar->sqllen));
205         int offset = 0;
206         if (sdbcType == DataType::VARCHAR)
207         {
208             // First 2 bytes indicate string size
209             static_assert(sizeof(nLength) == 2, "must match dest memcpy len");
210             memcpy(pVar->sqldata, &nLength, 2);
211             offset = 2;
212         }
213         // Actual data
214         memcpy(pVar->sqldata + offset, str.getStr(), nLength);
215         if (sdbcType == DataType::CHAR)
216         {
217             // Fill remainder with spaces
218             memset(pVar->sqldata + offset + nLength, ' ', pVar->sqllen - nLength);
219         }
220         break;
221     }
222     case DataType::CLOB:
223         setClob(nParameterIndex, sInput );
224         break;
225     case DataType::SMALLINT:
226     {
227         sal_Int32 int32Value = sInput.toInt32();
228         if ( (int32Value < std::numeric_limits<sal_Int16>::min()) ||
229              (int32Value > std::numeric_limits<sal_Int16>::max()) )
230         {
231             ::dbtools::throwSQLException(
232                 u"Value out of range for SQL_SHORT type"_ustr,
233                 ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE,
234                 *this);
235         }
236         setShort(nParameterIndex, int32Value);
237         break;
238     }
239     case DataType::INTEGER:
240     {
241         sal_Int32 int32Value = sInput.toInt32();
242         setInt(nParameterIndex, int32Value);
243         break;
244     }
245     case DataType::BIGINT:
246     {
247         sal_Int64 int64Value = sInput.toInt64();
248         setLong(nParameterIndex, int64Value);
249         break;
250     }
251     case DataType::FLOAT:
252     {
253         float floatValue = sInput.toFloat();
254         setFloat(nParameterIndex, floatValue);
255         break;
256     }
257     case DataType::DOUBLE:
258         setDouble(nParameterIndex, sInput.toDouble());
259         break;
260     case DataType::NUMERIC:
261     case DataType::DECIMAL:
262         return setObjectWithInfo(nParameterIndex, Any{ sInput }, sdbcType, 0);
263         break;
264     case DataType::BOOLEAN:
265     {
266         bool boolValue = sInput.toBoolean();
267         setBoolean(nParameterIndex, boolValue);
268         break;
269     }
270     case DataType::SQLNULL:
271     {
272         // See https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref25/firebird-25-language-reference.html#fblangref25-datatypes-special-sqlnull
273         pVar->sqldata = nullptr;
274         break;
275     }
276     default:
277         ::dbtools::throwSQLException(
278             u"Incorrect type for setString"_ustr,
279             ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE,
280             *this);
281     }
282 }
283 
getConnection()284 Reference< XConnection > SAL_CALL OPreparedStatement::getConnection()
285 {
286     MutexGuard aGuard( m_aMutex );
287     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
288 
289     return m_pConnection;
290 }
291 
execute()292 sal_Bool SAL_CALL OPreparedStatement::execute()
293 {
294     SAL_INFO("connectivity.firebird", "executeQuery(). "
295         "Got called with sql: " <<  m_sSqlStatement);
296 
297     MutexGuard aGuard( m_aMutex );
298     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
299 
300     ensurePrepared();
301 
302     ISC_STATUS aErr;
303 
304     if (m_xResultSet.is()) // Checks whether we have already run the statement.
305     {
306         disposeResultSet();
307         // Closes the cursor from the last run.
308         // This doesn't actually free the statement -- using DSQL_close closes
309         // the cursor and keeps the statement, using DSQL_drop frees the statement
310         // (and associated cursors).
311         aErr = isc_dsql_free_statement(m_statusVector,
312                                        &m_aStatementHandle,
313                                        DSQL_close);
314         if (aErr)
315         {
316             // Do not throw error. Trying to close a closed cursor is not a
317             // critical mistake.
318             OUString sErrMsg  = StatusVectorToString(m_statusVector,
319                     u"isc_dsql_free_statement: close cursor");
320             SAL_WARN("connectivity.firebird", sErrMsg);
321         }
322     }
323 
324     aErr = isc_dsql_execute(m_statusVector,
325                                 &m_pConnection->getTransaction(),
326                                 &m_aStatementHandle,
327                                 1,
328                                 m_pInSqlda);
329     if (aErr)
330     {
331         SAL_WARN("connectivity.firebird", "isc_dsql_execute failed" );
332         evaluateStatusVector(m_statusVector, u"isc_dsql_execute", *this);
333     }
334 
335     m_xResultSet = new OResultSet(m_pConnection.get(),
336                                   m_aMutex,
337                                   uno::Reference< XInterface >(*this),
338                                   m_aStatementHandle,
339                                   m_pOutSqlda);
340 
341     return m_xResultSet.is();
342     // TODO: implement handling of multiple ResultSets.
343 }
344 
executeUpdate()345 sal_Int32 SAL_CALL OPreparedStatement::executeUpdate()
346 {
347     execute();
348     return getStatementChangeCount();
349 }
350 
executeQuery()351 Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery()
352 {
353     execute();
354     return m_xResultSet;
355 }
356 
357 namespace {
358 
toPowOf10AndRound(double n,int powOf10)359 sal_Int64 toPowOf10AndRound(double n, int powOf10)
360 {
361     static constexpr sal_Int64 powers[] = {
362         1,
363         10,
364         100,
365         1000,
366         10000,
367         100000,
368         1000000,
369         10000000,
370         100000000,
371         1000000000,
372         10000000000,
373         100000000000,
374         1000000000000,
375         10000000000000,
376         100000000000000,
377         1000000000000000,
378         10000000000000000,
379         100000000000000000,
380         1000000000000000000,
381     };
382     powOf10 = std::clamp(powOf10, 0, int(std::size(powers) - 1));
383     return n * powers[powOf10] + (n >= 0 ? 0.5 : -0.5);
384 }
385 
386 /**
387  * Take out the number part of a fix point decimal without
388  * the information of where is the fractional part from a
389  * string representation of a number. (e.g. 54.654 -> 54654)
390  */
toNumericWithoutDecimalPlace(const Any & x,sal_Int32 scale)391 sal_Int64 toNumericWithoutDecimalPlace(const Any& x, sal_Int32 scale)
392 {
393     if (double value = 0; x >>= value)
394         return toPowOf10AndRound(value, scale);
395 
396     // Can't use conversion of string to double, because it could be not representable in double
397 
398     OUString s;
399     x >>= s;
400     std::u16string_view num(o3tl::trim(s));
401     size_t end = num.starts_with('-') ? 1 : 0;
402     for (bool seenDot = false; end < num.size(); ++end)
403     {
404         if (num[end] == '.')
405         {
406             if (seenDot)
407                 break;
408             seenDot = true;
409         }
410         else if (!rtl::isAsciiDigit(num[end]))
411             break;
412     }
413     num = num.substr(0, end);
414 
415     // fill in the number with nulls in fractional part.
416     // We need this because  e.g. 0.450 != 0.045 despite
417     // their scale is equal
418     OUStringBuffer buffer(num);
419     if (auto dotPos = num.find('.'); dotPos != std::u16string_view::npos) // there is a dot
420     {
421         scale -= num.substr(dotPos + 1).size();
422         buffer.remove(dotPos, 1);
423         if (scale < 0)
424         {
425             const sal_Int32 n = std::min(buffer.getLength(), -scale);
426             buffer.truncate(buffer.getLength() - n);
427             scale = 0;
428         }
429     }
430     for (sal_Int32 i = 0; i < scale; ++i)
431         buffer.append('0');
432 
433     return OUString::unacquired(buffer).toInt64();
434 }
435 
toDouble(const Any & x)436 double toDouble(const Any& x)
437 {
438     if (double value = 0; x >>= value)
439         return value;
440     OUString s;
441     x >>= s;
442     return s.toDouble();
443 }
444 }
445 
446 //----- XParameters -----------------------------------------------------------
setNull(sal_Int32 nIndex,sal_Int32)447 void SAL_CALL OPreparedStatement::setNull(sal_Int32 nIndex, sal_Int32 /*nSqlType*/)
448 {
449     MutexGuard aGuard( m_aMutex );
450     ensurePrepared();
451 
452     checkParameterIndex(nIndex);
453     setParameterNull(nIndex);
454 }
455 
setBoolean(sal_Int32 nIndex,sal_Bool bValue)456 void SAL_CALL OPreparedStatement::setBoolean(sal_Int32 nIndex, sal_Bool bValue)
457 {
458     setValue< sal_Bool >(nIndex, bValue, SQL_BOOLEAN);
459 }
460 
461 template <typename T>
setValue(sal_Int32 nIndex,const T & nValue,ISC_SHORT nType)462 void OPreparedStatement::setValue(sal_Int32 nIndex, const T& nValue, ISC_SHORT nType)
463 {
464     MutexGuard aGuard( m_aMutex );
465     ensurePrepared();
466 
467     checkParameterIndex(nIndex);
468     setParameterNull(nIndex, false);
469 
470     XSQLVAR* pVar = m_pInSqlda->sqlvar + (nIndex - 1);
471 
472     if ((pVar->sqltype & ~1) != nType)
473     {
474        ::dbtools::throwSQLException(
475             u"Incorrect type for setValue"_ustr,
476             ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE,
477             *this);
478     }
479 
480     memcpy(pVar->sqldata, &nValue, sizeof(nValue));
481 }
482 
483 // Integral type setters convert transparently to bigger types
484 
setByte(sal_Int32 nIndex,sal_Int8 nValue)485 void SAL_CALL OPreparedStatement::setByte(sal_Int32 nIndex, sal_Int8 nValue)
486 {
487     // there's no TINYINT or equivalent on Firebird,
488     // so do the same as setShort
489     setShort(nIndex, nValue);
490 }
491 
setShort(sal_Int32 nIndex,sal_Int16 nValue)492 void SAL_CALL OPreparedStatement::setShort(sal_Int32 nIndex, sal_Int16 nValue)
493 {
494     MutexGuard aGuard(m_aMutex);
495     ensurePrepared();
496 
497     ColumnTypeInfo columnType{ m_pInSqlda, nIndex };
498     switch (columnType.getSdbcType())
499     {
500         case DataType::INTEGER:
501             return setValue<sal_Int32>(nIndex, nValue, columnType.getType());
502         case DataType::BIGINT:
503             return setValue<sal_Int64>(nIndex, nValue, columnType.getType());
504         case DataType::FLOAT:
505             return setValue<float>(nIndex, nValue, columnType.getType());
506         case DataType::DOUBLE:
507             return setValue<double>(nIndex, nValue, columnType.getType());
508     }
509     setValue< sal_Int16 >(nIndex, nValue, SQL_SHORT);
510 }
511 
setInt(sal_Int32 nIndex,sal_Int32 nValue)512 void SAL_CALL OPreparedStatement::setInt(sal_Int32 nIndex, sal_Int32 nValue)
513 {
514     MutexGuard aGuard(m_aMutex);
515     ensurePrepared();
516 
517     ColumnTypeInfo columnType{ m_pInSqlda, nIndex };
518     switch (columnType.getSdbcType())
519     {
520         case DataType::BIGINT:
521             return setValue<sal_Int64>(nIndex, nValue, columnType.getType());
522         case DataType::DOUBLE:
523             return setValue<double>(nIndex, nValue, columnType.getType());
524     }
525     setValue< sal_Int32 >(nIndex, nValue, SQL_LONG);
526 }
527 
setLong(sal_Int32 nIndex,sal_Int64 nValue)528 void SAL_CALL OPreparedStatement::setLong(sal_Int32 nIndex, sal_Int64 nValue)
529 {
530     setValue< sal_Int64 >(nIndex, nValue, SQL_INT64);
531 }
532 
setFloat(sal_Int32 nIndex,float nValue)533 void SAL_CALL OPreparedStatement::setFloat(sal_Int32 nIndex, float nValue)
534 {
535     setValue< float >(nIndex, nValue, SQL_FLOAT);
536 }
537 
setDouble(sal_Int32 nIndex,double nValue)538 void SAL_CALL OPreparedStatement::setDouble(sal_Int32 nIndex, double nValue)
539 {
540     MutexGuard aGuard( m_aMutex );
541     ensurePrepared();
542 
543     ColumnTypeInfo columnType{ m_pInSqlda, nIndex };
544     // Assume it is a sub type of a number.
545     if (columnType.getSubType() < 0 || columnType.getSubType() > 2)
546     {
547         ::dbtools::throwSQLException(
548             u"Incorrect number sub type"_ustr,
549             ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE,
550             *this);
551     }
552 
553     // Caller might try to set an integer type here. It makes sense to convert
554     // it instead of throwing an error.
555     switch(auto sdbcType = columnType.getSdbcType())
556     {
557         case DataType::SMALLINT:
558             return setValue(nIndex, static_cast<sal_Int16>(nValue), columnType.getType());
559         case DataType::INTEGER:
560             return setValue(nIndex, static_cast<sal_Int32>(nValue), columnType.getType());
561         case DataType::BIGINT:
562             return setValue(nIndex, static_cast<sal_Int64>(nValue), columnType.getType());
563         case DataType::NUMERIC:
564         case DataType::DECIMAL:
565             return setObjectWithInfo(nIndex, Any{ nValue }, sdbcType, 0);
566         // TODO: SQL_D_FLOAT?
567     }
568     setValue<double>(nIndex, nValue, SQL_DOUBLE);
569 }
570 
setDate(sal_Int32 nIndex,const Date & rDate)571 void SAL_CALL OPreparedStatement::setDate(sal_Int32 nIndex, const Date& rDate)
572 {
573     struct tm aCTime;
574     aCTime.tm_mday = rDate.Day;
575     aCTime.tm_mon = rDate.Month -1;
576     aCTime.tm_year = rDate.Year -1900;
577 
578     ISC_DATE aISCDate;
579     isc_encode_sql_date(&aCTime, &aISCDate);
580 
581     setValue< ISC_DATE >(nIndex, aISCDate, SQL_TYPE_DATE);
582 }
583 
setTime(sal_Int32 nIndex,const css::util::Time & rTime)584 void SAL_CALL OPreparedStatement::setTime( sal_Int32 nIndex, const css::util::Time& rTime)
585 {
586     struct tm aCTime;
587     aCTime.tm_sec = rTime.Seconds;
588     aCTime.tm_min = rTime.Minutes;
589     aCTime.tm_hour = rTime.Hours;
590 
591     ISC_TIME aISCTime;
592     isc_encode_sql_time(&aCTime, &aISCTime);
593 
594     // Here we "know" that ISC_TIME is simply in units of seconds/ISC_TIME_SECONDS_PRECISION with no
595     // other funkiness, so we can simply add the fraction of a second.
596     aISCTime += rTime.NanoSeconds / (1000000000 / ISC_TIME_SECONDS_PRECISION);
597 
598     setValue< ISC_TIME >(nIndex, aISCTime, SQL_TYPE_TIME);
599 }
600 
setTimestamp(sal_Int32 nIndex,const DateTime & rTimestamp)601 void SAL_CALL OPreparedStatement::setTimestamp(sal_Int32 nIndex, const DateTime& rTimestamp)
602 {
603     struct tm aCTime;
604     aCTime.tm_sec = rTimestamp.Seconds;
605     aCTime.tm_min = rTimestamp.Minutes;
606     aCTime.tm_hour = rTimestamp.Hours;
607     aCTime.tm_mday = rTimestamp.Day;
608     aCTime.tm_mon = rTimestamp.Month - 1;
609     aCTime.tm_year = rTimestamp.Year - 1900;
610 
611     ISC_TIMESTAMP aISCTimestamp;
612     isc_encode_timestamp(&aCTime, &aISCTimestamp);
613 
614     // As in previous function
615     aISCTimestamp.timestamp_time += rTimestamp.NanoSeconds / (1000000000 / ISC_TIME_SECONDS_PRECISION);
616 
617     setValue< ISC_TIMESTAMP >(nIndex, aISCTimestamp, SQL_TIMESTAMP);
618 }
619 
620 
621 // void OPreparedStatement::set
openBlobForWriting(isc_blob_handle & rBlobHandle,ISC_QUAD & rBlobId)622 void OPreparedStatement::openBlobForWriting(isc_blob_handle& rBlobHandle, ISC_QUAD& rBlobId)
623 {
624     ISC_STATUS aErr;
625 
626     aErr = isc_create_blob2(m_statusVector,
627                             &m_pConnection->getDBHandle(),
628                             &m_pConnection->getTransaction(),
629                             &rBlobHandle,
630                             &rBlobId,
631                             0, // Blob parameter buffer length
632                             nullptr); // Blob parameter buffer handle
633 
634     if (aErr)
635     {
636         evaluateStatusVector(m_statusVector,
637                              Concat2View("setBlob failed on " + m_sSqlStatement),
638                              *this);
639         assert(false);
640     }
641 }
642 
closeBlobAfterWriting(isc_blob_handle & rBlobHandle)643 void OPreparedStatement::closeBlobAfterWriting(isc_blob_handle& rBlobHandle)
644 {
645     ISC_STATUS aErr;
646 
647     aErr = isc_close_blob(m_statusVector,
648             &rBlobHandle);
649     if (aErr)
650     {
651         evaluateStatusVector(m_statusVector,
652                 u"isc_close_blob failed",
653                 *this);
654         assert(false);
655     }
656 }
657 
setClob(sal_Int32 nParameterIndex,const Reference<XClob> & xClob)658 void SAL_CALL OPreparedStatement::setClob(sal_Int32 nParameterIndex, const Reference< XClob >& xClob )
659 {
660     ::osl::MutexGuard aGuard( m_aMutex );
661     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
662 
663     // value-initialization: isc_blob_handle may be either a pointer of an integer
664     isc_blob_handle aBlobHandle{};
665     ISC_QUAD aBlobId;
666 
667     openBlobForWriting(aBlobHandle, aBlobId);
668 
669 
670     // Max segment size is 2^16 == SAL_MAX_UINT16
671     // SAL_MAX_UINT16 / 4 is surely enough for UTF-8
672     // TODO apply max segment size to character encoding
673     sal_Int64 nCharWritten = 1; // XClob is indexed from 1
674     ISC_STATUS aErr = 0;
675     sal_Int64 nLen = xClob->length();
676     while ( nLen >= nCharWritten )
677     {
678         sal_Int64 nCharRemain = nLen - nCharWritten + 1;
679         constexpr sal_uInt16 MAX_SIZE = SAL_MAX_UINT16 / 4;
680         sal_uInt16 nWriteSize = std::min<sal_Int64>(nCharRemain, MAX_SIZE);
681         OString sData = OUStringToOString(
682                 xClob->getSubString(nCharWritten, nWriteSize),
683                 RTL_TEXTENCODING_UTF8);
684         aErr = isc_put_segment( m_statusVector,
685                 &aBlobHandle,
686                 sData.getLength(),
687                 sData.getStr() );
688         nCharWritten += nWriteSize;
689 
690         if (aErr)
691             break;
692     }
693 
694     // We need to make sure we close the Blob even if there are errors, hence evaluate
695     // errors after closing.
696     closeBlobAfterWriting(aBlobHandle);
697 
698     if (aErr)
699     {
700         evaluateStatusVector(m_statusVector,
701                 u"isc_put_segment failed",
702                 *this);
703         assert(false);
704     }
705 
706     setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB);
707 }
708 
setClob(sal_Int32 nParameterIndex,std::u16string_view rStr)709 void OPreparedStatement::setClob(sal_Int32 nParameterIndex, std::u16string_view rStr)
710 {
711     ::osl::MutexGuard aGuard( m_aMutex );
712     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
713     checkParameterIndex(nParameterIndex);
714 
715     // value-initialization: isc_blob_handle may be either a pointer of an integer
716     isc_blob_handle aBlobHandle{};
717     ISC_QUAD aBlobId;
718 
719     openBlobForWriting(aBlobHandle, aBlobId);
720 
721     OString sData = OUStringToOString(
722             rStr,
723             RTL_TEXTENCODING_UTF8);
724     size_t nDataSize = sData.getLength();
725     ISC_STATUS aErr = 0;
726     // we can't store  more than MAX_SIZE_SEGMENT in a segment
727     if (nDataSize <= MAX_SIZE_SEGMENT)
728     {
729         aErr = isc_put_segment( m_statusVector,
730                             &aBlobHandle,
731                             sData.getLength(),
732                             sData.getStr() );
733     }
734     else
735     {
736         // if we need more, let's split the input and first let's calculate the nb of entire chunks needed
737         size_t nNbEntireChunks = nDataSize / MAX_SIZE_SEGMENT;
738         for (size_t i = 0; i < nNbEntireChunks; ++i)
739         {
740             OString strCurrentChunk = sData.copy(i * MAX_SIZE_SEGMENT, MAX_SIZE_SEGMENT);
741             aErr = isc_put_segment( m_statusVector,
742                             &aBlobHandle,
743                             strCurrentChunk.getLength(),
744                             strCurrentChunk.getStr() );
745             if (aErr)
746                 break;
747         }
748         size_t nRemainingBytes = nDataSize - (nNbEntireChunks * MAX_SIZE_SEGMENT);
749         if (nRemainingBytes && !aErr)
750         {
751             // then copy the remaining
752             OString strCurrentChunk = sData.copy(nNbEntireChunks * MAX_SIZE_SEGMENT, nRemainingBytes);
753             aErr = isc_put_segment( m_statusVector,
754                             &aBlobHandle,
755                             strCurrentChunk.getLength(),
756                             strCurrentChunk.getStr() );
757         }
758     }
759 
760     // We need to make sure we close the Blob even if there are errors, hence evaluate
761     // errors after closing.
762     closeBlobAfterWriting(aBlobHandle);
763 
764     if (aErr)
765     {
766         evaluateStatusVector(m_statusVector,
767                              u"isc_put_segment failed",
768                              *this);
769         assert(false);
770     }
771 
772     setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB);
773 }
774 
setBlob(sal_Int32 nParameterIndex,const Reference<XBlob> & xBlob)775 void SAL_CALL OPreparedStatement::setBlob(sal_Int32 nParameterIndex,
776                                           const Reference< XBlob >& xBlob)
777 {
778     ::osl::MutexGuard aGuard(m_aMutex);
779     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
780     checkParameterIndex(nParameterIndex);
781 
782     // value-initialization: isc_blob_handle may be either a pointer of an integer
783     isc_blob_handle aBlobHandle{};
784     ISC_QUAD aBlobId;
785 
786     openBlobForWriting(aBlobHandle, aBlobId);
787 
788     ISC_STATUS aErr = 0;
789     const sal_Int64 nBlobLen = xBlob->length();
790     if (nBlobLen > 0)
791     {
792         // Max write size is 0xFFFF == SAL_MAX_UINT16
793         sal_uInt64 nDataWritten = 0;
794         while (sal::static_int_cast<sal_uInt64>(nBlobLen) > nDataWritten)
795         {
796             sal_uInt64 nDataRemaining = nBlobLen - nDataWritten;
797             sal_uInt16 nWriteSize = std::min(nDataRemaining, sal_uInt64(SAL_MAX_UINT16));
798             aErr = isc_put_segment(m_statusVector,
799                                    &aBlobHandle,
800                                    nWriteSize,
801                                    reinterpret_cast<const char*>(xBlob->getBytes(nDataWritten, nWriteSize).getConstArray()));
802             nDataWritten += nWriteSize;
803 
804             if (aErr)
805                 break;
806         }
807     }
808 
809     // We need to make sure we close the Blob even if there are errors, hence evaluate
810     // errors after closing.
811     closeBlobAfterWriting(aBlobHandle);
812 
813     if (aErr)
814     {
815         evaluateStatusVector(m_statusVector,
816                              u"isc_put_segment failed",
817                              *this);
818         assert(false);
819     }
820 
821     setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB);
822 }
823 
824 
setArray(sal_Int32 nIndex,const Reference<XArray> &)825 void SAL_CALL OPreparedStatement::setArray( sal_Int32 nIndex, const Reference< XArray >& )
826 {
827     ::osl::MutexGuard aGuard( m_aMutex );
828     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
829     checkParameterIndex(nIndex);
830 }
831 
832 
setRef(sal_Int32 nIndex,const Reference<XRef> &)833 void SAL_CALL OPreparedStatement::setRef( sal_Int32 nIndex, const Reference< XRef >& )
834 {
835     ::osl::MutexGuard aGuard( m_aMutex );
836     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
837     checkParameterIndex(nIndex);
838 }
839 
840 
setObjectWithInfo(sal_Int32 parameterIndex,const Any & x,sal_Int32 sqlType,sal_Int32 scale)841 void SAL_CALL OPreparedStatement::setObjectWithInfo( sal_Int32 parameterIndex, const Any& x, sal_Int32 sqlType, sal_Int32 scale )
842 {
843     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
844     ::osl::MutexGuard aGuard( m_aMutex );
845     ensurePrepared();
846 
847     checkParameterIndex(parameterIndex);
848     setParameterNull(parameterIndex, false);
849 
850     ColumnTypeInfo columnType{ m_pInSqlda, parameterIndex };
851     int dType = columnType.getType() & ~1; // drop null flag
852 
853     if(sqlType == DataType::DECIMAL || sqlType == DataType::NUMERIC)
854     {
855         switch(dType)
856         {
857             case SQL_SHORT:
858                 return setValue(
859                     parameterIndex,
860                     static_cast<sal_Int16>(toNumericWithoutDecimalPlace(x, columnType.getScale())),
861                     dType);
862             case SQL_LONG:
863                 return setValue(
864                     parameterIndex,
865                     static_cast<sal_Int32>(toNumericWithoutDecimalPlace(x, columnType.getScale())),
866                     dType);
867             case SQL_INT64:
868                 return setValue(parameterIndex,
869                                 toNumericWithoutDecimalPlace(x, columnType.getScale()), dType);
870             case SQL_DOUBLE:
871                 return setValue(parameterIndex, toDouble(x), dType);
872             default:
873                 SAL_WARN("connectivity.firebird",
874                         "No Firebird sql type found for numeric or decimal types");
875                 ::dbtools::setObjectWithInfo(this,parameterIndex,x,sqlType,scale);
876         }
877     }
878     else
879     {
880         ::dbtools::setObjectWithInfo(this,parameterIndex,x,sqlType,scale);
881     }
882 
883 }
884 
885 
setObjectNull(sal_Int32 nIndex,sal_Int32,const OUString &)886 void SAL_CALL OPreparedStatement::setObjectNull( sal_Int32 nIndex, sal_Int32, const OUString& )
887 {
888     ::osl::MutexGuard aGuard( m_aMutex );
889     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
890     checkParameterIndex(nIndex);
891 }
892 
893 
setObject(sal_Int32 nIndex,const Any &)894 void SAL_CALL OPreparedStatement::setObject( sal_Int32 nIndex, const Any& )
895 {
896     ::osl::MutexGuard aGuard( m_aMutex );
897     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
898     checkParameterIndex(nIndex);
899 }
900 
setBytes(sal_Int32 nParameterIndex,const Sequence<sal_Int8> & xBytes)901 void SAL_CALL OPreparedStatement::setBytes(sal_Int32 nParameterIndex,
902                                            const Sequence< sal_Int8 >& xBytes)
903 {
904     ::osl::MutexGuard aGuard(m_aMutex);
905     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
906     checkParameterIndex(nParameterIndex);
907 
908     XSQLVAR* pVar = m_pInSqlda->sqlvar + (nParameterIndex - 1);
909     int dType = (pVar->sqltype & ~1); // drop flag bit for now
910 
911     if( dType == SQL_BLOB )
912     {
913         // value-initialization: isc_blob_handle may be either a pointer of an integer
914         isc_blob_handle aBlobHandle{};
915         ISC_QUAD aBlobId;
916 
917         openBlobForWriting(aBlobHandle, aBlobId);
918 
919         ISC_STATUS aErr = 0;
920         const sal_Int32 nBytesLen = xBytes.getLength();
921         if (nBytesLen > 0)
922         {
923             // Max write size is 0xFFFF == SAL_MAX_UINT16
924             sal_uInt32 nDataWritten = 0;
925             while (sal::static_int_cast<sal_uInt32>(nBytesLen) > nDataWritten)
926             {
927                 sal_uInt32 nDataRemaining = nBytesLen - nDataWritten;
928                 sal_uInt16 nWriteSize = std::min(nDataRemaining, sal_uInt32(SAL_MAX_UINT16));
929                 aErr = isc_put_segment(m_statusVector,
930                                        &aBlobHandle,
931                                        nWriteSize,
932                                        reinterpret_cast<const char*>(xBytes.getConstArray()) + nDataWritten);
933                 nDataWritten += nWriteSize;
934 
935                 if (aErr)
936                     break;
937             }
938         }
939 
940         // We need to make sure we close the Blob even if there are errors, hence evaluate
941         // errors after closing.
942         closeBlobAfterWriting(aBlobHandle);
943 
944         if (aErr)
945         {
946             evaluateStatusVector(m_statusVector,
947                                  u"isc_put_segment failed",
948                                  *this);
949             assert(false);
950         }
951 
952         setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB);
953     }
954     else if( dType == SQL_VARYING )
955     {
956             setParameterNull(nParameterIndex, false);
957             const sal_Int32 nMaxSize = 0xFFFF;
958             const sal_uInt16 nSize = std::min(xBytes.getLength(), nMaxSize);
959             // 8000 corresponds to value from lcl_addDefaultParameters
960             // in dbaccess/source/filter/hsqldb/createparser.cxx
961             if (nSize > 8000)
962             {
963                 free(pVar->sqldata);
964                 pVar->sqldata = static_cast<char *>(malloc(sizeof(char) * nSize + 2));
965             }
966             static_assert(sizeof(nSize) == 2, "must match dest memcpy len");
967             // First 2 bytes indicate string size
968             memcpy(pVar->sqldata, &nSize, 2);
969             // Actual data
970             memcpy(pVar->sqldata + 2, xBytes.getConstArray(), nSize);
971     }
972     else if( dType == SQL_TEXT )
973     {
974             if (pVar->sqllen < xBytes.getLength())
975                 dbtools::throwSQLException(u"Data too big for this field"_ustr,
976                                            dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, *this);
977             setParameterNull(nParameterIndex, false);
978             memcpy(pVar->sqldata, xBytes.getConstArray(), xBytes.getLength() );
979             // Fill remainder with zeroes
980             memset(pVar->sqldata + xBytes.getLength(), 0, pVar->sqllen - xBytes.getLength());
981     }
982     else
983     {
984         ::dbtools::throwSQLException(
985             u"Incorrect type for setBytes"_ustr,
986             ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE,
987             *this);
988     }
989 }
990 
991 
setCharacterStream(sal_Int32 nIndex,const Reference<css::io::XInputStream> &,sal_Int32)992 void SAL_CALL OPreparedStatement::setCharacterStream( sal_Int32 nIndex, const Reference< css::io::XInputStream >&, sal_Int32 )
993 {
994     ::osl::MutexGuard aGuard( m_aMutex );
995     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
996     checkParameterIndex(nIndex);
997 }
998 
999 
setBinaryStream(sal_Int32 nIndex,const Reference<css::io::XInputStream> &,sal_Int32)1000 void SAL_CALL OPreparedStatement::setBinaryStream( sal_Int32 nIndex, const Reference< css::io::XInputStream >&, sal_Int32 )
1001 {
1002     ::osl::MutexGuard aGuard( m_aMutex );
1003     checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed);
1004     checkParameterIndex(nIndex);
1005 }
1006 
1007 
clearParameters()1008 void SAL_CALL OPreparedStatement::clearParameters(  )
1009 {
1010 }
1011 
1012 // ---- Batch methods -- unsupported -----------------------------------------
clearBatch()1013 void SAL_CALL OPreparedStatement::clearBatch()
1014 {
1015     // Unsupported
1016 }
1017 
addBatch()1018 void SAL_CALL OPreparedStatement::addBatch()
1019 {
1020     // Unsupported by firebird
1021 }
1022 
executeBatch()1023 Sequence< sal_Int32 > SAL_CALL OPreparedStatement::executeBatch()
1024 {
1025     // Unsupported by firebird
1026     return Sequence< sal_Int32 >();
1027 }
1028 
setFastPropertyValue_NoBroadcast(sal_Int32 nHandle,const Any & rValue)1029 void OPreparedStatement::setFastPropertyValue_NoBroadcast(sal_Int32 nHandle,const Any& rValue)
1030 {
1031     switch(nHandle)
1032     {
1033         case PROPERTY_ID_RESULTSETCONCURRENCY:
1034             break;
1035         case PROPERTY_ID_RESULTSETTYPE:
1036             break;
1037         case PROPERTY_ID_FETCHDIRECTION:
1038             break;
1039         case PROPERTY_ID_USEBOOKMARKS:
1040             break;
1041         default:
1042             OStatementCommonBase::setFastPropertyValue_NoBroadcast(nHandle,rValue);
1043     }
1044 }
1045 
checkParameterIndex(sal_Int32 nParameterIndex)1046 void OPreparedStatement::checkParameterIndex(sal_Int32 nParameterIndex)
1047 {
1048     ensurePrepared();
1049     if ((nParameterIndex == 0) || (nParameterIndex > m_pInSqlda->sqld))
1050     {
1051         ::dbtools::throwSQLException(
1052             "No column " + OUString::number(nParameterIndex),
1053             ::dbtools::StandardSQLState::COLUMN_NOT_FOUND,
1054             *this);
1055     }
1056 }
1057 
setParameterNull(sal_Int32 nParameterIndex,bool bSetNull)1058 void OPreparedStatement::setParameterNull(sal_Int32 nParameterIndex,
1059                                           bool bSetNull)
1060 {
1061     XSQLVAR* pVar = m_pInSqlda->sqlvar + (nParameterIndex - 1);
1062     if (bSetNull)
1063     {
1064         pVar->sqltype |= 1;
1065         *pVar->sqlind = -1;
1066     }
1067     else
1068         *pVar->sqlind = 0;
1069 }
1070 
1071 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
1072