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 
10 #include "Util.hxx"
11 #include <rtl/ustrbuf.hxx>
12 #include <sal/log.hxx>
13 
14 #include <com/sun/star/sdbc/DataType.hpp>
15 #include <com/sun/star/sdbc/SQLException.hpp>
16 #include <o3tl/string_view.hxx>
17 
18 using namespace ::connectivity;
19 
20 using namespace ::com::sun::star;
21 using namespace ::com::sun::star::sdbc;
22 using namespace ::com::sun::star::uno;
23 
24 using namespace firebird;
25 
26 OUString firebird::sanitizeIdentifier(std::u16string_view rIdentifier)
27 {
28     std::u16string_view sRet = o3tl::trim(rIdentifier);
29     assert(sRet.size() <= 31); // Firebird identifiers cannot be longer than this.
30 
31     return OUString(sRet);
32 }
33 
34 OUString firebird::StatusVectorToString(const ISC_STATUS_ARRAY& rStatusVector,
35                                     std::u16string_view rCause)
36 {
37     OUStringBuffer buf;
38     const ISC_STATUS* pStatus = reinterpret_cast<const ISC_STATUS*>(&rStatusVector);
39 
40     buf.append("firebird_sdbc error:");
41     try
42     {
43         char msg[512]; // Size is based on suggestion in docs.
44         while(fb_interpret(msg, sizeof(msg), &pStatus))
45         {
46             // TODO: verify encoding
47             buf.append("\n*");
48             buf.append(OUString(msg, strlen(msg), RTL_TEXTENCODING_UTF8));
49         }
50     }
51     catch (...)
52     {
53         SAL_WARN("connectivity.firebird", "ignore fb_interpret exception");
54     }
55     buf.append(OUString::Concat("\ncaused by\n'") + rCause + "'\n");
56 
57     OUString error = buf.makeStringAndClear();
58     SAL_WARN("connectivity.firebird", error);
59     return error;
60 }
61 
62 void firebird::evaluateStatusVector(const ISC_STATUS_ARRAY& rStatusVector,
63                                     std::u16string_view rCause,
64                                     const uno::Reference< XInterface >& _rxContext)
65 {
66     if (IndicatesError(rStatusVector))
67     {
68         OUString error = StatusVectorToString(rStatusVector, rCause);
69         throw SQLException(error, _rxContext, OUString(), 1, Any());
70     }
71 }
72 
73 static sal_Int32 lcl_getNumberType( short aType, NumberSubType aSubType )
74 {
75     switch(aSubType)
76     {
77         case NumberSubType::Numeric:
78             return DataType::NUMERIC;
79         case NumberSubType::Decimal:
80             return DataType::DECIMAL;
81         default:
82             switch(aType)
83             {
84                 case SQL_SHORT:
85                     return DataType::SMALLINT;
86                 case SQL_LONG:
87                     return DataType::INTEGER;
88                 case SQL_DOUBLE:
89                     return DataType::DOUBLE;
90                 case SQL_INT64:
91                     return DataType::BIGINT;
92                 default:
93                     assert(false); // not a number
94                     return 0;
95             }
96     }
97 }
98 static sal_Int32 lcl_getCharColumnType( short aType, std::u16string_view sCharset )
99 {
100     switch(aType)
101     {
102         case SQL_TEXT:
103             if( sCharset == u"OCTETS")
104                 return DataType::BINARY;
105             else
106                 return DataType::CHAR;
107         case SQL_VARYING:
108             if( sCharset == u"OCTETS")
109                 return DataType::VARBINARY;
110             else
111                 return DataType::VARCHAR;
112         default:
113             assert(false);
114             return 0;
115     }
116 }
117 
118 sal_Int32 firebird::ColumnTypeInfo::getSdbcType() const
119 {
120     short aType = m_aType & ~1; // Remove last bit -- it is used to denote whether column
121                  // can store Null, not needed for type determination
122     short aSubType = m_aSubType;
123     if( m_nScale > 0 )
124     {
125         // numeric / decimal
126         if(aType == SQL_SHORT || aType == SQL_LONG || aType == SQL_DOUBLE
127                 || aType == SQL_INT64)
128         {
129             // if scale is set without subtype then imply numeric
130             if( static_cast<NumberSubType>(aSubType) == NumberSubType::Other )
131                 aSubType = static_cast<short>(NumberSubType::Numeric);
132         }
133     }
134 
135     switch (aType)
136     {
137     case SQL_TEXT:
138     case SQL_VARYING:
139         return lcl_getCharColumnType(aType, m_sCharsetName);
140     case SQL_SHORT:
141     case SQL_LONG:
142     case SQL_DOUBLE:
143     case SQL_INT64:
144         return lcl_getNumberType(aType, static_cast<NumberSubType>(aSubType) );
145     case SQL_FLOAT:
146         return DataType::FLOAT;
147     case SQL_D_FLOAT:
148         return DataType::DOUBLE;
149     case SQL_TIMESTAMP:
150         return DataType::TIMESTAMP;
151     case SQL_BLOB:
152         switch (static_cast<BlobSubtype>(aSubType))
153         {
154             case BlobSubtype::Blob:
155                 return DataType::BLOB;
156             case BlobSubtype::Clob:
157                 return DataType::CLOB;
158             case BlobSubtype::Image:
159                 return DataType::LONGVARBINARY;
160             default:
161                 SAL_WARN("connectivity.firebird", "Unknown subtype for Blob type: " << aSubType);
162                 assert(!"Unknown subtype for Blob type"); // Should never happen
163                 return 0;
164         }
165     case SQL_ARRAY:
166         return DataType::ARRAY;
167     case SQL_TYPE_TIME:
168         return DataType::TIME;
169     case SQL_TYPE_DATE:
170         return DataType::DATE;
171     case SQL_NULL:
172         return DataType::SQLNULL;
173     case SQL_QUAD:      // Is a "Blob ID" according to the docs
174         return 0;       // TODO: verify
175     case SQL_BOOLEAN:
176         return DataType::BOOLEAN;
177     default:
178         assert(false); // Should never happen
179         return 0;
180     }
181 }
182 
183 OUString firebird::ColumnTypeInfo::getColumnTypeName() const
184 {
185     sal_Int32 nDataType = this->getSdbcType();
186     switch (nDataType)
187     {
188         case DataType::BIT:
189             return "BIT";
190         case DataType::TINYINT:
191             return "TINYINT";
192         case DataType::SMALLINT:
193             return "SMALLINT";
194         case DataType::INTEGER:
195             return "INTEGER";
196         case DataType::BIGINT:
197             return "BIGINT";
198         case DataType::FLOAT:
199             return "FLOAT";
200         case DataType::REAL:
201             return "REAL";
202         case DataType::DOUBLE:
203             return "DOUBLE";
204         case DataType::NUMERIC:
205             return "NUMERIC";
206         case DataType::DECIMAL:
207             return "DECIMAL";
208         case DataType::CHAR:
209             return "CHAR";
210         case DataType::VARCHAR:
211             return "VARCHAR";
212         case DataType::LONGVARCHAR:
213             return "LONGVARCHAR";
214         case DataType::DATE:
215             return "DATE";
216         case DataType::TIME:
217             return "TIME";
218         case DataType::TIMESTAMP:
219             return "TIMESTAMP";
220         case DataType::BINARY:
221             // in Firebird, that is the same datatype "CHAR" as DataType::CHAR,
222             // only with CHARACTER SET OCTETS; we use the synonym CHARACTER
223             // to fool LO into seeing it as different types.
224             return "CHARACTER";
225         case DataType::VARBINARY:
226             // see above comment about DataType::BINARY.
227             return "CHARACTER VARYING";
228         case DataType::LONGVARBINARY:
229             return "BLOB SUB_TYPE " + OUString::number(static_cast<short>(BlobSubtype::Image));
230         case DataType::ARRAY:
231             return "ARRAY";
232         case DataType::BLOB:
233             return "BLOB SUB_TYPE BINARY";
234         case DataType::CLOB:
235             return "BLOB SUB_TYPE TEXT";
236         case DataType::BOOLEAN:
237             return "BOOLEAN";
238         case DataType::SQLNULL:
239             return "NULL";
240         default:
241             assert(false); // Should never happen
242             return OUString();
243      }
244 }
245 
246 short firebird::getFBTypeFromBlrType(short blrType)
247 {
248     switch (blrType)
249     {
250     case blr_text:
251         return SQL_TEXT;
252     case blr_text2:
253         assert(false);
254         return 0; // No idea if this should be supported
255     case blr_varying:
256         return SQL_VARYING;
257     case blr_varying2:
258         assert(false);
259         return 0; // No idea if this should be supported
260     case blr_short:
261         return SQL_SHORT;
262     case blr_long:
263         return SQL_LONG;
264     case blr_float:
265         return SQL_FLOAT;
266     case blr_double:
267         return SQL_DOUBLE;
268     case blr_d_float:
269         return SQL_D_FLOAT;
270     case blr_timestamp:
271         return SQL_TIMESTAMP;
272     case blr_blob:
273         return SQL_BLOB;
274 //     case blr_SQL_ARRAY:
275 //         return OUString("SQL_ARRAY");
276     case blr_sql_time:
277         return SQL_TYPE_TIME;
278     case blr_sql_date:
279         return SQL_TYPE_DATE;
280     case blr_int64:
281         return SQL_INT64;
282 //     case SQL_NULL:
283 //         return OUString("SQL_NULL");
284     case blr_quad:
285         return SQL_QUAD;
286     case blr_bool:
287         return SQL_BOOLEAN;
288     default:
289         // If this happens we have hit one of the extra types in ibase.h
290         // look up blr_* for a list, e.g. blr_domain_name, blr_not_nullable etc.
291         assert(false);
292         return 0;
293     }
294 }
295 
296 void firebird::mallocSQLVAR(XSQLDA* pSqlda)
297 {
298     // TODO: confirm the sizings below.
299     XSQLVAR* pVar = pSqlda->sqlvar;
300     for (int i=0; i < pSqlda->sqld; i++, pVar++)
301     {
302         int dtype = (pVar->sqltype & ~1); /* drop flag bit for now */
303         switch(dtype) {
304         case SQL_TEXT:
305             pVar->sqldata = static_cast<char *>(malloc(sizeof(char)*pVar->sqllen));
306             break;
307         case SQL_VARYING:
308             pVar->sqldata = static_cast<char *>(malloc(sizeof(char)*pVar->sqllen + 2));
309             break;
310         case SQL_SHORT:
311             pVar->sqldata = static_cast<char*>(malloc(sizeof(sal_Int16)));
312             break;
313         case SQL_LONG:
314             pVar->sqldata = static_cast<char*>(malloc(sizeof(sal_Int32)));
315             break;
316         case SQL_FLOAT:
317             pVar->sqldata = static_cast<char *>(malloc(sizeof(float)));
318             break;
319         case SQL_DOUBLE:
320             pVar->sqldata = static_cast<char *>(malloc(sizeof(double)));
321             break;
322         case SQL_D_FLOAT:
323             pVar->sqldata = static_cast<char *>(malloc(sizeof(double)));
324             break;
325         case SQL_TIMESTAMP:
326             pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_TIMESTAMP)));
327             break;
328         // an ARRAY is in fact a BLOB of a specialized type
329         // See https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-bnrytypes.html#fblangref25-datatypes-array
330         case SQL_ARRAY:
331         case SQL_BLOB:
332             pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_QUAD)));
333             break;
334         case SQL_TYPE_TIME:
335             pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_TIME)));
336             break;
337         case SQL_TYPE_DATE:
338             pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_DATE)));
339             break;
340         case SQL_INT64:
341             pVar->sqldata = static_cast<char *>(malloc(sizeof(sal_Int64)));
342             break;
343         case SQL_BOOLEAN:
344             pVar->sqldata = static_cast<char *>(malloc(sizeof(sal_Bool)));
345             break;
346         // See https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref25/firebird-25-language-reference.html#fblangref25-datatypes-special-sqlnull
347         case SQL_NULL:
348             pVar->sqldata = nullptr;
349             break;
350         case SQL_QUAD:
351             assert(false); // TODO: implement
352             break;
353         default:
354             SAL_WARN("connectivity.firebird", "Unknown type: " << dtype);
355             assert(false);
356             break;
357         }
358         /* allocate variable to hold NULL status */
359         pVar->sqlind = static_cast<short *>(malloc(sizeof(short)));
360     }
361 }
362 
363 void firebird::freeSQLVAR(XSQLDA* pSqlda)
364 {
365     XSQLVAR* pVar = pSqlda->sqlvar;
366     for (int i=0; i < pSqlda->sqld; i++, pVar++)
367     {
368         int dtype = (pVar->sqltype & ~1); /* drop flag bit for now */
369         switch(dtype) {
370         case SQL_TEXT:
371         case SQL_VARYING:
372         case SQL_SHORT:
373         case SQL_LONG:
374         case SQL_FLOAT:
375         case SQL_DOUBLE:
376         case SQL_D_FLOAT:
377         case SQL_TIMESTAMP:
378         // an ARRAY is in fact a BLOB of a specialized type
379         // See https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-bnrytypes.html#fblangref25-datatypes-array
380         case SQL_ARRAY:
381         case SQL_BLOB:
382         case SQL_INT64:
383         case SQL_TYPE_TIME:
384         case SQL_TYPE_DATE:
385         case SQL_BOOLEAN:
386             if(pVar->sqldata)
387             {
388                 free(pVar->sqldata);
389                 pVar->sqldata = nullptr;
390             }
391             break;
392         case SQL_NULL:
393             // See SQL_NULL case in mallocSQLVAR
394             assert(pVar->sqldata == nullptr);
395             break;
396         case SQL_QUAD:
397             assert(false); // TODO: implement
398             break;
399         default:
400             SAL_WARN("connectivity.firebird", "Unknown type: " << dtype);
401 //            assert(false);
402             break;
403         }
404 
405         if(pVar->sqlind)
406         {
407             free(pVar->sqlind);
408             pVar->sqlind = nullptr;
409         }
410     }
411 }
412 
413 
414 OUString firebird::escapeWith( const OUString& sText, const char aKey, const char aEscapeChar)
415 {
416     OUString sRet(sText);
417     sal_Int32 aIndex = 0;
418     for (;;)
419     {
420         aIndex = sRet.indexOf(aKey, aIndex);
421         if ( aIndex <= 0 || aIndex >= sRet.getLength())
422             break;
423         sRet = sRet.replaceAt(aIndex, 1, rtl::OUStringConcatenation(OUStringChar(aEscapeChar) + OUStringChar(aKey))  );
424         aIndex += 2;
425     }
426 
427     return sRet;
428 }
429 
430 sal_Int64 firebird::pow10Integer(int nDecimalCount)
431 {
432     sal_Int64 nRet = 1;
433     for(int i=0; i< nDecimalCount; i++)
434     {
435         nRet *= 10;
436     }
437     return nRet;
438 }
439 
440 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
441