Coverage Report

Created: 2025-12-11 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/odbcappender.cpp
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
#include <log4cxx/db/odbcappender.h>
18
#include <log4cxx/helpers/loglog.h>
19
#include <log4cxx/helpers/optionconverter.h>
20
#include <log4cxx/helpers/stringhelper.h>
21
#include <log4cxx/helpers/transcoder.h>
22
#include <log4cxx/patternlayout.h>
23
#include <log4cxx/pattern/mdcpatternconverter.h>
24
#include <apr_strings.h>
25
#include <apr_time.h>
26
#include <cmath> // std::pow
27
28
#include <log4cxx/pattern/loggerpatternconverter.h>
29
#include <log4cxx/pattern/classnamepatternconverter.h>
30
#include <log4cxx/pattern/datepatternconverter.h>
31
#include <log4cxx/pattern/filelocationpatternconverter.h>
32
#include <log4cxx/pattern/fulllocationpatternconverter.h>
33
#include <log4cxx/pattern/shortfilelocationpatternconverter.h>
34
#include <log4cxx/pattern/linelocationpatternconverter.h>
35
#include <log4cxx/pattern/messagepatternconverter.h>
36
#include <log4cxx/pattern/methodlocationpatternconverter.h>
37
#include <log4cxx/pattern/levelpatternconverter.h>
38
#include <log4cxx/pattern/threadpatternconverter.h>
39
#include <log4cxx/pattern/threadusernamepatternconverter.h>
40
#include <log4cxx/pattern/ndcpatternconverter.h>
41
42
#if !defined(LOG4CXX)
43
  #define LOG4CXX 1
44
#endif
45
#include <log4cxx/private/log4cxx_private.h>
46
#if LOG4CXX_HAVE_ODBC
47
  #if defined(WIN32) || defined(_WIN32)
48
    #include <windows.h>
49
  #endif
50
  #include <sqlext.h>
51
#else
52
  typedef void* SQLHSTMT;
53
#endif
54
#include <log4cxx/private/odbcappender_priv.h>
55
#if defined(min)
56
  #undef min
57
#endif
58
#include <cstring>
59
#include <algorithm>
60
61
62
using namespace LOG4CXX_NS;
63
using namespace LOG4CXX_NS::helpers;
64
using namespace LOG4CXX_NS::db;
65
using namespace LOG4CXX_NS::spi;
66
using namespace LOG4CXX_NS::pattern;
67
68
SQLException::SQLException(short fHandleType,
69
  void* hInput, const char* prolog,
70
  LOG4CXX_NS::helpers::Pool& p)
71
0
  : Exception(formatMessage(fHandleType, hInput, prolog, p))
72
0
{
73
0
}
74
75
76
SQLException::SQLException(const char* msg)
77
0
  : Exception(msg)
78
0
{
79
0
}
80
81
SQLException::SQLException(const SQLException& src)
82
0
  : Exception(src)
83
0
{
84
0
}
85
86
const char* SQLException::formatMessage(short fHandleType,
87
  void* hInput, const char* prolog, LOG4CXX_NS::helpers::Pool& p)
88
0
{
89
0
  std::string strReturn(prolog);
90
0
  strReturn.append(" - ");
91
#if LOG4CXX_HAVE_ODBC
92
  SQLCHAR       SqlState[6];
93
  SQLCHAR       Msg[SQL_MAX_MESSAGE_LENGTH];
94
  SQLINTEGER    NativeError;
95
  SQLSMALLINT   i;
96
  SQLSMALLINT   MsgLen;
97
  SQLRETURN     rc2;
98
99
  // Get the status records.
100
  i = 1;
101
102
  while ((rc2 = SQLGetDiagRecA(fHandleType, hInput, i, SqlState, &NativeError,
103
          Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA)
104
  {
105
    strReturn.append((char*) Msg);
106
    i++;
107
  }
108
109
#else
110
0
  strReturn.append("log4cxx built without ODBC support");
111
0
#endif
112
113
0
  return apr_pstrdup((apr_pool_t*) p.getAPRPool(), strReturn.c_str());
114
0
}
115
116
117
IMPLEMENT_LOG4CXX_OBJECT(ODBCAppender)
118
119
0
#define _priv static_cast<ODBCAppenderPriv*>(m_priv.get())
120
121
ODBCAppender::ODBCAppender()
122
0
  : AppenderSkeleton (std::make_unique<ODBCAppenderPriv>(
123
#if LOG4CXX_EVENTS_AT_EXIT
124
    [this] {
125
      std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
126
      if(_priv->closed)
127
        return;
128
      try
129
      {
130
        flushBuffer(_priv->pool);
131
      }
132
      catch (SQLException& e)
133
      {
134
        _priv->errorHandler->error(LOG4CXX_STR("Error flushing connection"),
135
          e, ErrorCode::GENERIC_FAILURE);
136
      }
137
    }
138
#endif
139
0
                ))
140
0
{
141
0
}
Unexecuted instantiation: log4cxx::db::ODBCAppender::ODBCAppender()
Unexecuted instantiation: log4cxx::db::ODBCAppender::ODBCAppender()
142
143
ODBCAppender::~ODBCAppender()
144
0
{
145
0
  finalize();
146
0
}
147
148
#define RULES_PUT(spec, cls) \
149
  specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), cls ::newInstance))
150
151
static PatternMap getFormatSpecifiers()
152
0
{
153
0
  PatternMap specs;
154
0
  if (specs.empty())
155
0
  {
156
0
    RULES_PUT("logger", LoggerPatternConverter);
157
0
    RULES_PUT("class", ClassNamePatternConverter);
158
0
    RULES_PUT("time", DatePatternConverter);
159
0
    RULES_PUT("shortfilename", ShortFileLocationPatternConverter);
160
0
    RULES_PUT("fullfilename", FileLocationPatternConverter);
161
0
    RULES_PUT("location", FullLocationPatternConverter);
162
0
    RULES_PUT("line", LineLocationPatternConverter);
163
0
    RULES_PUT("message", MessagePatternConverter);
164
0
    RULES_PUT("method", MethodLocationPatternConverter);
165
0
    RULES_PUT("level", LevelPatternConverter);
166
0
    RULES_PUT("thread", ThreadPatternConverter);
167
0
    RULES_PUT("threadname", ThreadUsernamePatternConverter);
168
0
    RULES_PUT("mdc", MDCPatternConverter);
169
0
    RULES_PUT("ndc", NDCPatternConverter);
170
0
  }
171
0
  return specs;
172
0
}
173
174
void ODBCAppender::setOption(const LogString& option, const LogString& value)
175
0
{
176
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
177
0
  {
178
0
    setBufferSize((size_t)OptionConverter::toInt(value, 1));
179
0
  }
180
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PASSWORD"), LOG4CXX_STR("password")))
181
0
  {
182
0
    setPassword(value);
183
0
  }
184
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SQL"), LOG4CXX_STR("sql")))
185
0
  {
186
0
    setSql(value);
187
0
  }
188
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("URL"), LOG4CXX_STR("url"))
189
0
    || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DSN"), LOG4CXX_STR("dsn"))
190
0
    || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CONNECTIONSTRING"), LOG4CXX_STR("connectionstring"))  )
191
0
  {
192
0
    setURL(value);
193
0
  }
194
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("USER"), LOG4CXX_STR("user")))
195
0
  {
196
0
    setUser(value);
197
0
  }
198
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("COLUMNMAPPING"), LOG4CXX_STR("columnmapping")))
199
0
  {
200
0
    _priv->mappedName.push_back(value);
201
0
  }
202
0
  else
203
0
  {
204
0
    AppenderSkeleton::setOption(option, value);
205
0
  }
206
0
}
207
208
// Does ODBCAppender require a layout?
209
210
bool ODBCAppender::requiresLayout() const
211
0
{
212
0
  return false;
213
0
}
214
215
void ODBCAppender::activateOptions(LOG4CXX_NS::helpers::Pool&)
216
0
{
217
0
#if !LOG4CXX_HAVE_ODBC
218
0
  LogLog::error(LOG4CXX_STR("Can not activate ODBCAppender unless compiled with ODBC support."));
219
#else
220
  if (_priv->mappedName.empty())
221
  {
222
    LogLog::error(LOG4CXX_STR("ODBCAppender column mappings not defined, logging events will not be inserted"));
223
  }
224
  auto specs = getFormatSpecifiers();
225
  for (auto& name : _priv->mappedName)
226
  {
227
    auto lowerName = StringHelper::toLowerCase(name);
228
    auto pItem = specs.find(lowerName);
229
    if (specs.end() == pItem)
230
    {
231
      if (lowerName.size() < 5
232
       || lowerName.substr(0, 4) != LOG4CXX_STR("mdc{"))
233
        LogLog::error(name + LOG4CXX_STR(" is not a supported ColumnMapping value"));
234
      else // A single MDC entry
235
      {
236
        auto index = lowerName.find(0x7D /* '}' */, 4);
237
        auto len = (lowerName.npos == index ? lowerName.size() : index) - 4;
238
        ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 };
239
        paramData.converter = std::make_shared<MDCPatternConverter>(lowerName.substr(4, len));
240
        _priv->parameterValue.push_back(paramData);
241
      }
242
    }
243
    else
244
    {
245
      ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 };
246
      std::vector<LogString> options;
247
      if (LOG4CXX_STR("time") == pItem->first)
248
        options.push_back(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss.SSSSSS"));
249
      paramData.converter = LOG4CXX_NS::cast<LoggingEventPatternConverter>((pItem->second)(options));
250
      _priv->parameterValue.push_back(paramData);
251
    }
252
  }
253
#endif
254
0
}
255
256
257
void ODBCAppender::append(const spi::LoggingEventPtr& event, LOG4CXX_NS::helpers::Pool& p)
258
0
{
259
#if LOG4CXX_HAVE_ODBC
260
  _priv->buffer.push_back(event);
261
262
  if (_priv->buffer.size() >= _priv->bufferSize)
263
  {
264
    flushBuffer(p);
265
  }
266
267
#endif
268
0
}
269
270
#if LOG4CXX_ABI_VERSION <= 15
271
LogString ODBCAppender::getLogStatement(const spi::LoggingEventPtr& event, LOG4CXX_NS::helpers::Pool& p) const
272
0
{
273
0
    return LogString();
274
0
}
275
276
void ODBCAppender::execute(const LogString& sql, LOG4CXX_NS::helpers::Pool& p)
277
0
{
278
0
}
279
#endif
280
281
/* The default behavior holds a single connection open until the appender
282
is closed (typically when garbage collected).*/
283
void ODBCAppender::closeConnection(ODBCAppender::SQLHDBC /* con */)
284
0
{
285
0
}
286
287
ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
288
0
{
289
#if LOG4CXX_HAVE_ODBC
290
  SQLRETURN ret;
291
292
  if (_priv->env == SQL_NULL_HENV)
293
  {
294
    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_priv->env);
295
296
    if (ret < 0)
297
    {
298
      SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to allocate SQL handle", p);
299
      _priv->env = SQL_NULL_HENV;
300
      throw ex;
301
    }
302
303
    ret = SQLSetEnvAttr(_priv->env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);
304
305
    if (ret < 0)
306
    {
307
      SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to set odbc version", p);
308
      SQLFreeHandle(SQL_HANDLE_ENV, _priv->env);
309
      _priv->env = SQL_NULL_HENV;
310
      throw ex;
311
    }
312
  }
313
314
  if (_priv->connection == SQL_NULL_HDBC)
315
  {
316
    ret = SQLAllocHandle(SQL_HANDLE_DBC, _priv->env, &_priv->connection);
317
318
    if (ret < 0)
319
    {
320
      SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to allocate sql handle", p);
321
      _priv->connection = SQL_NULL_HDBC;
322
      throw ex;
323
    }
324
325
#if LOG4CXX_LOGCHAR_IS_WCHAR
326
    SQLWCHAR *wUser = nullptr, *wPwd = nullptr;
327
    if (!_priv->databaseUser.empty())
328
      wUser = (SQLWCHAR*)_priv->databaseUser.c_str();
329
    if (!_priv->databasePassword.empty())
330
      wPwd = (SQLWCHAR*)_priv->databasePassword.c_str();
331
    ret = SQLConnectW(_priv->connection
332
      , (SQLWCHAR*)_priv->databaseURL.c_str(), SQL_NTS
333
      , wUser, SQL_NTS
334
      , wPwd, SQL_NTS
335
      );
336
#elif LOG4CXX_LOGCHAR_IS_UTF8
337
    SQLCHAR *wUser = nullptr, *wPwd = nullptr;
338
    if (!_priv->databaseUser.empty())
339
      wUser = (SQLCHAR*)_priv->databaseUser.c_str();
340
    if (!_priv->databasePassword.empty())
341
      wPwd = (SQLCHAR*)_priv->databasePassword.c_str();
342
    ret = SQLConnectA(_priv->connection
343
      , (SQLCHAR*)_priv->databaseURL.c_str(), SQL_NTS
344
      , wUser, SQL_NTS
345
      , wPwd, SQL_NTS
346
      );
347
#else
348
    SQLWCHAR* wURL, *wUser = nullptr, *wPwd = nullptr;
349
    encode(&wURL, _priv->databaseURL, p);
350
    if (!_priv->databaseUser.empty())
351
      encode(&wUser, _priv->databaseUser, p);
352
    if (!_priv->databasePassword.empty())
353
      encode(&wPwd, _priv->databasePassword, p);
354
355
    ret = SQLConnectW( _priv->connection
356
      , wURL, SQL_NTS
357
      , wUser, SQL_NTS
358
      , wPwd, SQL_NTS
359
      );
360
#endif
361
362
    if (ret < 0)
363
    {
364
      SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to connect to database", p);
365
      SQLFreeHandle(SQL_HANDLE_DBC, _priv->connection);
366
      _priv->connection = SQL_NULL_HDBC;
367
      throw ex;
368
    }
369
  }
370
371
  return _priv->connection;
372
#else
373
0
  return 0;
374
0
#endif
375
0
}
376
377
void ODBCAppender::close()
378
0
{
379
0
  if (_priv->closed)
380
0
  {
381
0
    return;
382
0
  }
383
384
0
  Pool p;
385
386
0
  try
387
0
  {
388
0
    flushBuffer(p);
389
0
  }
390
0
  catch (SQLException& e)
391
0
  {
392
0
    _priv->errorHandler->error(LOG4CXX_STR("Error closing connection"),
393
0
      e, ErrorCode::GENERIC_FAILURE);
394
0
  }
395
396
#if LOG4CXX_HAVE_ODBC
397
398
  if (_priv->connection != SQL_NULL_HDBC)
399
  {
400
    SQLDisconnect(_priv->connection);
401
    SQLFreeHandle(SQL_HANDLE_DBC, _priv->connection);
402
  }
403
404
  if (_priv->env != SQL_NULL_HENV)
405
  {
406
    SQLFreeHandle(SQL_HANDLE_ENV, _priv->env);
407
  }
408
409
#endif
410
0
  _priv->closed = true;
411
0
}
412
413
#if LOG4CXX_HAVE_ODBC
414
void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
415
{
416
  auto ret = SQLAllocHandle( SQL_HANDLE_STMT, con, &this->preparedStatement);
417
  if (ret < 0)
418
  {
419
    throw SQLException( SQL_HANDLE_DBC, con, "Failed to allocate statement handle.", p);
420
  }
421
422
#if LOG4CXX_LOGCHAR_IS_WCHAR
423
  ret = SQLPrepareW(this->preparedStatement, (SQLWCHAR*)this->sqlStatement.c_str(), SQL_NTS);
424
#elif LOG4CXX_LOGCHAR_IS_UTF8
425
  ret = SQLPrepareA(this->preparedStatement, (SQLCHAR*)this->sqlStatement.c_str(), SQL_NTS);
426
#else
427
  SQLWCHAR* wsql;
428
  encode(&wsql, this->sqlStatement, p);
429
  ret = SQLPrepareW(this->preparedStatement, wsql, SQL_NTS);
430
#endif
431
  if (ret < 0)
432
  {
433
    throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to prepare sql statement.", p);
434
  }
435
436
  int parameterNumber = 0;
437
  for (auto& item : this->parameterValue)
438
  {
439
    ++parameterNumber;
440
    SQLSMALLINT  targetType;
441
    SQLULEN      targetMaxCharCount;
442
    SQLSMALLINT  decimalDigits;
443
    SQLSMALLINT  nullable;
444
    auto ret = SQLDescribeParam
445
      ( this->preparedStatement
446
      , parameterNumber
447
      , &targetType
448
      , &targetMaxCharCount
449
      , &decimalDigits
450
      , &nullable
451
      );
452
    if (ret < 0)
453
    {
454
      throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to describe parameter", p);
455
    }
456
    if (SQL_CHAR == targetType || SQL_VARCHAR == targetType || SQL_LONGVARCHAR == targetType)
457
    {
458
      item.paramType = SQL_C_CHAR;
459
      item.paramMaxCharCount = targetMaxCharCount;
460
      item.paramValueSize = (SQLINTEGER)(item.paramMaxCharCount) * sizeof(char) + sizeof(char);
461
      item.paramValue = (SQLPOINTER)p.palloc(item.paramValueSize + sizeof(char));
462
    }
463
    else if (SQL_WCHAR == targetType || SQL_WVARCHAR == targetType || SQL_WLONGVARCHAR == targetType)
464
    {
465
      item.paramType = SQL_C_WCHAR;
466
      item.paramMaxCharCount = targetMaxCharCount;
467
      item.paramValueSize = (SQLINTEGER)(targetMaxCharCount) * sizeof(wchar_t) + sizeof(wchar_t);
468
      item.paramValue = (SQLPOINTER)p.palloc(item.paramValueSize + sizeof(wchar_t));
469
    }
470
    else if (SQL_TYPE_TIMESTAMP == targetType || SQL_TYPE_DATE == targetType || SQL_TYPE_TIME == targetType
471
      || SQL_DATETIME == targetType)
472
    {
473
      item.paramType = SQL_C_TYPE_TIMESTAMP;
474
      item.paramMaxCharCount = (0 <= decimalDigits) ? decimalDigits : 6;
475
      item.paramValueSize = sizeof(SQL_TIMESTAMP_STRUCT);
476
      item.paramValue = (SQLPOINTER)p.palloc(item.paramValueSize);
477
    }
478
    else
479
    {
480
      if (SQL_INTEGER != targetType)
481
      {
482
        LogString msg(LOG4CXX_STR("Unexpected targetType ("));
483
        helpers::StringHelper::toString(targetType, p, msg);
484
        msg += LOG4CXX_STR(") at parameter ");
485
        helpers::StringHelper::toString(parameterNumber, p, msg);
486
        msg += LOG4CXX_STR(" while preparing SQL");
487
        LogLog::warn(msg);
488
      }
489
      item.paramMaxCharCount = 30;
490
#if LOG4CXX_LOGCHAR_IS_UTF8
491
      item.paramType = SQL_C_CHAR;
492
      item.paramValueSize = (SQLINTEGER)(item.paramMaxCharCount) * sizeof(char);
493
      item.paramValue = (SQLPOINTER)p.palloc(item.paramValueSize + sizeof(char));
494
#else
495
      item.paramType = SQL_C_WCHAR;
496
      item.paramValueSize = (SQLINTEGER)(item.paramMaxCharCount) * sizeof(wchar_t);
497
      item.paramValue = (SQLPOINTER)p.palloc(item.paramValueSize + sizeof(wchar_t));
498
#endif
499
    }
500
    item.strLen_or_Ind = SQL_NTS;
501
    ret = SQLBindParameter
502
      ( this->preparedStatement
503
      , parameterNumber
504
      , SQL_PARAM_INPUT
505
      , item.paramType  // ValueType
506
      , targetType
507
      , targetMaxCharCount
508
      , decimalDigits
509
      , item.paramValue
510
      , item.paramValueSize
511
      , &item.strLen_or_Ind
512
      );
513
    if (ret < 0)
514
    {
515
      throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to bind parameter", p);
516
    }
517
  }
518
}
519
520
void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventPtr& event, Pool& p)
521
{
522
  for (auto& item : this->parameterValue)
523
  {
524
    if (!item.paramValue || item.paramValueSize <= 0)
525
      ;
526
    else if (SQL_C_WCHAR == item.paramType)
527
    {
528
      LogString sbuf;
529
      item.converter->format(event, sbuf, p);
530
#if LOG4CXX_LOGCHAR_IS_WCHAR_T
531
      std::wstring& tmp = sbuf;
532
#else
533
      std::wstring tmp;
534
      Transcoder::encode(sbuf, tmp);
535
#endif
536
      auto dst = (wchar_t*)item.paramValue;
537
      auto charCount = std::min(size_t(item.paramMaxCharCount), tmp.size());
538
      auto copySize = std::min(size_t(item.paramValueSize - 1), charCount * sizeof(wchar_t));
539
      std::memcpy(dst, tmp.data(), copySize);
540
      dst[copySize / sizeof(wchar_t)] = 0;
541
    }
542
    else if (SQL_C_CHAR == item.paramType)
543
    {
544
      LogString sbuf;
545
      item.converter->format(event, sbuf, p);
546
#if LOG4CXX_LOGCHAR_IS_UTF8
547
      std::string& tmp = sbuf;
548
#else
549
      std::string tmp;
550
      Transcoder::encode(sbuf, tmp);
551
#endif
552
      auto dst = (char*)item.paramValue;
553
      auto sz = std::min(size_t(item.paramMaxCharCount), tmp.size());
554
      auto copySize = std::min(size_t(item.paramValueSize - 1), sz * sizeof(char));
555
      std::memcpy(dst, tmp.data(), copySize);
556
      dst[copySize] = 0;
557
    }
558
    else if (SQL_C_TYPE_TIMESTAMP == item.paramType)
559
    {
560
      apr_time_exp_t exploded;
561
      apr_status_t stat = this->timeZone->explode(&exploded, event->getTimeStamp());
562
      if (stat == APR_SUCCESS)
563
      {
564
        auto dst = (SQL_TIMESTAMP_STRUCT*)item.paramValue;
565
        dst->year = 1900 + exploded.tm_year;
566
        dst->month = 1 + exploded.tm_mon;
567
        dst->day = exploded.tm_mday;
568
        dst->hour = exploded.tm_hour;
569
        dst->minute = exploded.tm_min;
570
        dst->second = exploded.tm_sec;
571
        // Prevent '[ODBC SQL Server Driver]Datetime field overflow' by rounding to the target field precision
572
        int roundingExponent = 6 - (int)item.paramMaxCharCount;
573
        if (0 < roundingExponent)
574
        {
575
          int roundingDivisor = (int)std::pow(10, roundingExponent);
576
          dst->fraction = 1000 * roundingDivisor * ((exploded.tm_usec + roundingDivisor / 2) / roundingDivisor);
577
        }
578
        else
579
          dst->fraction = 1000 * exploded.tm_usec;
580
      }
581
    }
582
  }
583
}
584
#endif
585
586
void ODBCAppender::flushBuffer(Pool& p)
587
0
{
588
0
  for (auto& logEvent : _priv->buffer)
589
0
  {
590
0
    if (_priv->parameterValue.empty())
591
0
      _priv->errorHandler->error(LOG4CXX_STR("ODBCAppender column mappings not defined"));
592
#if LOG4CXX_HAVE_ODBC
593
    else try
594
    {
595
      if (0 == _priv->preparedStatement)
596
        _priv->setPreparedStatement(getConnection(p), p);
597
      _priv->setParameterValues(logEvent, p);
598
      auto ret = SQLExecute(_priv->preparedStatement);
599
      if (ret < 0)
600
      {
601
        throw SQLException(SQL_HANDLE_STMT, _priv->preparedStatement, "Failed to execute prepared statement", p);
602
      }
603
    }
604
    catch (SQLException& e)
605
    {
606
      _priv->errorHandler->error(LOG4CXX_STR("Failed to execute sql"), e,
607
        ErrorCode::FLUSH_FAILURE);
608
    }
609
#endif
610
0
  }
611
612
  // clear the buffer of reported events
613
0
  _priv->buffer.clear();
614
0
}
615
616
void ODBCAppender::setSql(const LogString& s)
617
0
{
618
0
  const logchar doubleQuote{ 0x22 };
619
0
  const logchar singleQuote{ 0x27 };
620
0
  const logchar semiColan{ 0x3b };
621
  // A basic check which disallows multiple SQL statements - for defense-in-depth security.
622
  // Allow a semicolan in a quoted context or as the last character.
623
0
  logchar currentQuote{ 0 };
624
0
  int charCount{ 0 };
625
0
  for (auto ch : s)
626
0
  {
627
0
    ++charCount;
628
0
    if (currentQuote == ch)
629
0
      currentQuote = 0;
630
0
    else if (currentQuote == 0)
631
0
    {
632
0
      if (doubleQuote == ch || singleQuote == ch)
633
0
        currentQuote = ch;
634
0
      else if (semiColan == ch && s.size() != charCount)
635
0
        throw IllegalArgumentException(LOG4CXX_STR("SQL statement cannot contain a ';'"));
636
0
    }
637
0
  }
638
0
  if (0 != currentQuote)
639
0
    throw IllegalArgumentException(LogString(LOG4CXX_STR("Unmatched ")) + currentQuote + LOG4CXX_STR(" in SQL statement"));
640
0
    _priv->sqlStatement = s;
641
0
}
642
643
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR_T || defined(WIN32) || defined(_WIN32)
644
void ODBCAppender::encode(wchar_t** dest, const LogString& src, Pool& p)
645
0
{
646
0
  *dest = Transcoder::wencode(src, p);
647
0
}
648
#endif
649
650
void ODBCAppender::encode(unsigned short** dest,
651
  const LogString& src, Pool& p)
652
0
{
653
  //  worst case double number of characters from UTF-8 or wchar_t
654
0
  *dest = (unsigned short*)
655
0
    p.palloc((src.size() + 1) * 2 * sizeof(unsigned short));
656
0
  unsigned short* current = *dest;
657
658
0
  for (LogString::const_iterator i = src.begin();
659
0
    i != src.end();)
660
0
  {
661
0
    unsigned int sv = Transcoder::decode(src, i);
662
663
0
    if (sv < 0x10000)
664
0
    {
665
0
      *current++ = (unsigned short) sv;
666
0
    }
667
0
    else
668
0
    {
669
0
      unsigned char u = (unsigned char) (sv >> 16);
670
0
      unsigned char w = (unsigned char) (u - 1);
671
0
      unsigned short hs = (0xD800 + ((w & 0xF) << 6) + ((sv & 0xFFFF) >> 10));
672
0
      unsigned short ls = (0xDC00 + (sv & 0x3FF));
673
0
      *current++ = (unsigned short) hs;
674
0
      *current++ = (unsigned short) ls;
675
0
    }
676
0
  }
677
678
0
  *current = 0;
679
0
}
680
681
const LogString& ODBCAppender::getSql() const
682
0
{
683
0
  return _priv->sqlStatement;
684
0
}
685
686
void ODBCAppender::setUser(const LogString& user)
687
0
{
688
0
  _priv->databaseUser = user;
689
0
}
690
691
void ODBCAppender::setURL(const LogString& url)
692
0
{
693
0
  _priv->databaseURL = url;
694
0
}
695
696
void ODBCAppender::setPassword(const LogString& password)
697
0
{
698
0
  _priv->databasePassword = password;
699
0
}
700
701
void ODBCAppender::setBufferSize(size_t newBufferSize)
702
0
{
703
0
  _priv->bufferSize = newBufferSize;
704
0
}
705
706
const LogString& ODBCAppender::getUser() const
707
0
{
708
0
  return _priv->databaseUser;
709
0
}
710
711
const LogString& ODBCAppender::getURL() const
712
0
{
713
0
  return _priv->databaseURL;
714
0
}
715
716
const LogString& ODBCAppender::getPassword() const
717
0
{
718
0
  return _priv->databasePassword;
719
0
}
720
721
size_t ODBCAppender::getBufferSize() const
722
0
{
723
0
  return _priv->bufferSize;
724
0
}
725