Coverage Report

Created: 2026-06-15 06:22

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 && !LOG4CXX_LOGCHAR_IS_UNICHAR
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
#include <limits>
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 && !LOG4CXX_LOGCHAR_IS_UNICHAR
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->buffer.empty() || _priv->closed)
127
        ;
128
      else 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
  if (_priv->setClosed())
146
0
    _priv->close();
147
0
}
148
149
#define RULES_PUT(spec, cls) \
150
  specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), cls ::newInstance))
151
152
static PatternMap getFormatSpecifiers()
153
0
{
154
0
  PatternMap specs;
155
0
  if (specs.empty())
156
0
  {
157
0
    RULES_PUT("logger", LoggerPatternConverter);
158
0
    RULES_PUT("class", ClassNamePatternConverter);
159
0
    RULES_PUT("time", DatePatternConverter);
160
0
    RULES_PUT("shortfilename", ShortFileLocationPatternConverter);
161
0
    RULES_PUT("fullfilename", FileLocationPatternConverter);
162
0
    RULES_PUT("location", FullLocationPatternConverter);
163
0
    RULES_PUT("line", LineLocationPatternConverter);
164
0
    RULES_PUT("message", MessagePatternConverter);
165
0
    RULES_PUT("method", MethodLocationPatternConverter);
166
0
    RULES_PUT("level", LevelPatternConverter);
167
0
    RULES_PUT("thread", ThreadPatternConverter);
168
0
    RULES_PUT("threadname", ThreadUsernamePatternConverter);
169
0
    RULES_PUT("mdc", MDCPatternConverter);
170
0
    RULES_PUT("ndc", NDCPatternConverter);
171
0
  }
172
0
  return specs;
173
0
}
174
175
void ODBCAppender::setOption(const LogString& option, const LogString& value)
176
0
{
177
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
178
0
  {
179
0
    int parsed = OptionConverter::toInt(value, 1);
180
0
    if (parsed < 0)
181
0
    {
182
0
      parsed = 1;
183
0
    }
184
0
    setBufferSize((size_t) parsed);
185
0
  }
186
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PASSWORD"), LOG4CXX_STR("password")))
187
0
  {
188
0
    setPassword(value);
189
0
  }
190
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SQL"), LOG4CXX_STR("sql")))
191
0
  {
192
0
    setSql(value);
193
0
  }
194
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("URL"), LOG4CXX_STR("url"))
195
0
    || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("DSN"), LOG4CXX_STR("dsn"))
196
0
    || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CONNECTIONSTRING"), LOG4CXX_STR("connectionstring"))  )
197
0
  {
198
0
    setURL(value);
199
0
  }
200
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("USER"), LOG4CXX_STR("user")))
201
0
  {
202
0
    setUser(value);
203
0
  }
204
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("COLUMNMAPPING"), LOG4CXX_STR("columnmapping")))
205
0
  {
206
0
    _priv->mappedName.push_back(value);
207
0
  }
208
0
  else
209
0
  {
210
0
    AppenderSkeleton::setOption(option, value);
211
0
  }
212
0
}
213
214
// Does ODBCAppender require a layout?
215
216
bool ODBCAppender::requiresLayout() const
217
0
{
218
0
  return false;
219
0
}
220
221
void ODBCAppender::activateOptions( LOG4CXX_ACTIVATE_OPTIONS_FORMAL_PARAMETERS )
222
0
{
223
0
#if !LOG4CXX_HAVE_ODBC || LOG4CXX_LOGCHAR_IS_UNICHAR
224
0
  LogLog::error(LOG4CXX_STR("Can not activate ODBCAppender unless compiled with ODBC support."));
225
#else
226
  if (_priv->mappedName.empty())
227
  {
228
    LogLog::error(LOG4CXX_STR("ODBCAppender column mappings not defined, logging events will not be inserted"));
229
  }
230
  auto specs = getFormatSpecifiers();
231
  for (auto& name : _priv->mappedName)
232
  {
233
    auto lowerName = StringHelper::toLowerCase(name);
234
    auto pItem = specs.find(lowerName);
235
    if (specs.end() == pItem)
236
    {
237
      if (lowerName.size() < 5
238
       || lowerName.substr(0, 4) != LOG4CXX_STR("mdc{"))
239
        LogLog::error(name + LOG4CXX_STR(" is not a supported ColumnMapping value"));
240
      else // A single MDC entry
241
      {
242
        auto index = lowerName.find(0x7D /* '}' */, 4);
243
        auto len = (lowerName.npos == index ? lowerName.size() : index) - 4;
244
        ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 };
245
        paramData.converter = std::make_shared<MDCPatternConverter>(lowerName.substr(4, len));
246
        _priv->parameterValue.push_back(paramData);
247
      }
248
    }
249
    else
250
    {
251
      ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 };
252
      std::vector<LogString> options;
253
      if (LOG4CXX_STR("time") == pItem->first)
254
        options.push_back(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss.SSSSSS"));
255
      paramData.converter = LOG4CXX_NS::cast<LoggingEventPatternConverter>((pItem->second)(options));
256
      _priv->parameterValue.push_back(paramData);
257
    }
258
  }
259
#endif
260
0
}
261
262
263
void ODBCAppender::append( LOG4CXX_APPEND_FORMAL_PARAMETERS )
264
0
{
265
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
266
  _priv->buffer.push_back(event);
267
268
  if (_priv->buffer.size() >= _priv->bufferSize)
269
  {
270
    flushBuffer(_priv->pool);
271
  }
272
273
#endif
274
0
}
275
276
#if LOG4CXX_ABI_VERSION <= 15
277
LogString ODBCAppender::getLogStatement(const spi::LoggingEventPtr& event, LOG4CXX_NS::helpers::Pool& p) const
278
0
{
279
0
    return LogString();
280
0
}
281
282
void ODBCAppender::execute(const LogString& sql, LOG4CXX_NS::helpers::Pool& p)
283
0
{
284
0
}
285
#endif
286
287
/* The default behavior holds a single connection open until the appender
288
is closed (typically when garbage collected).*/
289
void ODBCAppender::closeConnection(ODBCAppender::SQLHDBC /* con */)
290
0
{
291
0
}
292
293
ODBCAppender::SQLHDBC ODBCAppender::getConnection(LOG4CXX_NS::helpers::Pool& p)
294
0
{
295
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
296
  SQLRETURN ret;
297
298
  if (_priv->env == SQL_NULL_HENV)
299
  {
300
    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &_priv->env);
301
302
    if (ret < 0)
303
    {
304
      SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to allocate SQL handle", p);
305
      _priv->env = SQL_NULL_HENV;
306
      throw ex;
307
    }
308
309
    ret = SQLSetEnvAttr(_priv->env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);
310
311
    if (ret < 0)
312
    {
313
      SQLException ex(SQL_HANDLE_ENV, _priv->env, "Failed to set odbc version", p);
314
      SQLFreeHandle(SQL_HANDLE_ENV, _priv->env);
315
      _priv->env = SQL_NULL_HENV;
316
      throw ex;
317
    }
318
  }
319
320
  if (_priv->connection == SQL_NULL_HDBC)
321
  {
322
    ret = SQLAllocHandle(SQL_HANDLE_DBC, _priv->env, &_priv->connection);
323
324
    if (ret < 0)
325
    {
326
      SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to allocate sql handle", p);
327
      _priv->connection = SQL_NULL_HDBC;
328
      throw ex;
329
    }
330
331
#if LOG4CXX_LOGCHAR_IS_WCHAR
332
    SQLWCHAR *wUser = nullptr, *wPwd = nullptr;
333
    if (!_priv->databaseUser.empty())
334
      wUser = (SQLWCHAR*)_priv->databaseUser.c_str();
335
    if (!_priv->databasePassword.empty())
336
      wPwd = (SQLWCHAR*)_priv->databasePassword.c_str();
337
    ret = SQLConnectW(_priv->connection
338
      , (SQLWCHAR*)_priv->databaseURL.c_str(), SQL_NTS
339
      , wUser, SQL_NTS
340
      , wPwd, SQL_NTS
341
      );
342
#elif LOG4CXX_LOGCHAR_IS_UTF8
343
    SQLCHAR *wUser = nullptr, *wPwd = nullptr;
344
    if (!_priv->databaseUser.empty())
345
      wUser = (SQLCHAR*)_priv->databaseUser.c_str();
346
    if (!_priv->databasePassword.empty())
347
      wPwd = (SQLCHAR*)_priv->databasePassword.c_str();
348
    ret = SQLConnectA(_priv->connection
349
      , (SQLCHAR*)_priv->databaseURL.c_str(), SQL_NTS
350
      , wUser, SQL_NTS
351
      , wPwd, SQL_NTS
352
      );
353
#else
354
  #error ODBCAppender is not supported when logchar is unichar
355
#endif
356
357
    if (ret < 0)
358
    {
359
      SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to connect to database", p);
360
      SQLFreeHandle(SQL_HANDLE_DBC, _priv->connection);
361
      _priv->connection = SQL_NULL_HDBC;
362
      throw ex;
363
    }
364
  }
365
366
  return _priv->connection;
367
#else
368
0
  return 0;
369
0
#endif
370
0
}
371
372
void ODBCAppender::close()
373
0
{
374
0
  if (_priv->setClosed())
375
0
  {
376
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
377
    if (!_priv->buffer.empty() && 0 == _priv->preparedStatement)
378
    {
379
      Pool p;
380
      _priv->setPreparedStatement(getConnection(p), p);
381
    }
382
#endif
383
0
    _priv->close();
384
0
  }
385
0
}
386
387
void ODBCAppender::ODBCAppenderPriv::close()
388
0
{
389
0
  try
390
0
  {
391
0
    Pool p;
392
0
    flushBuffer(p);
393
0
  }
394
0
  catch (SQLException& e)
395
0
  {
396
0
    this->errorHandler->error(LOG4CXX_STR("Error closing connection"),
397
0
      e, ErrorCode::GENERIC_FAILURE);
398
0
  }
399
400
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
401
402
  if (this->connection != SQL_NULL_HDBC)
403
  {
404
    SQLDisconnect(this->connection);
405
    SQLFreeHandle(SQL_HANDLE_DBC, this->connection);
406
  }
407
408
  if (this->env != SQL_NULL_HENV)
409
  {
410
    SQLFreeHandle(SQL_HANDLE_ENV, this->env);
411
  }
412
413
#endif
414
0
}
415
416
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
417
void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
418
{
419
  auto ret = SQLAllocHandle( SQL_HANDLE_STMT, con, &this->preparedStatement);
420
  if (ret < 0)
421
  {
422
    throw SQLException( SQL_HANDLE_DBC, con, "Failed to allocate statement handle.", p);
423
  }
424
425
#if LOG4CXX_LOGCHAR_IS_WCHAR
426
  ret = SQLPrepareW(this->preparedStatement, (SQLWCHAR*)this->sqlStatement.c_str(), SQL_NTS);
427
#elif LOG4CXX_LOGCHAR_IS_UTF8
428
  ret = SQLPrepareA(this->preparedStatement, (SQLCHAR*)this->sqlStatement.c_str(), SQL_NTS);
429
#else
430
  #error ODBCAppender is not supported when logchar is unichar
431
#endif
432
  if (ret < 0)
433
  {
434
    throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to prepare sql statement.", p);
435
  }
436
437
  int parameterNumber = 0;
438
  for (auto& item : this->parameterValue)
439
  {
440
    ++parameterNumber;
441
    SQLSMALLINT  targetType;
442
    SQLULEN      targetMaxCharCount;
443
    SQLSMALLINT  decimalDigits;
444
    SQLSMALLINT  nullable;
445
    auto ret = SQLDescribeParam
446
      ( this->preparedStatement
447
      , parameterNumber
448
      , &targetType
449
      , &targetMaxCharCount
450
      , &decimalDigits
451
      , &nullable
452
      );
453
    if (ret < 0)
454
    {
455
      throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to describe parameter", p);
456
    }
457
    if (SQL_CHAR == targetType || SQL_VARCHAR == targetType || SQL_LONGVARCHAR == targetType)
458
    {
459
      item.paramType = SQL_C_CHAR;
460
      item.paramMaxCharCount = targetMaxCharCount;
461
      {
462
        size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
463
        size_t cnt = (size_t)(item.paramMaxCharCount);
464
        size_t max_chars = (max_sz - sizeof(char)) / sizeof(char);
465
        if (cnt > max_chars) cnt = max_chars;
466
        size_t bytes = cnt * sizeof(char) + sizeof(char);
467
        item.paramValueSize = (SQLINTEGER)bytes;
468
      }
469
      item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(char));
470
    }
471
    else if (SQL_WCHAR == targetType || SQL_WVARCHAR == targetType || SQL_WLONGVARCHAR == targetType)
472
    {
473
      item.paramType = SQL_C_WCHAR;
474
      item.paramMaxCharCount = targetMaxCharCount;
475
      {
476
        size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
477
        size_t cnt = (size_t)(targetMaxCharCount);
478
        size_t max_chars = (max_sz - sizeof(wchar_t)) / sizeof(wchar_t);
479
        if (cnt > max_chars) cnt = max_chars;
480
        size_t bytes = cnt * sizeof(wchar_t) + sizeof(wchar_t);
481
        item.paramValueSize = (SQLINTEGER)bytes;
482
      }
483
      item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(wchar_t));
484
    }
485
    else if (SQL_TYPE_TIMESTAMP == targetType || SQL_TYPE_DATE == targetType || SQL_TYPE_TIME == targetType
486
      || SQL_DATETIME == targetType)
487
    {
488
      item.paramType = SQL_C_TYPE_TIMESTAMP;
489
      item.paramMaxCharCount = (0 <= decimalDigits) ? decimalDigits : 6;
490
      item.paramValueSize = sizeof(SQL_TIMESTAMP_STRUCT);
491
      item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize);
492
    }
493
    else
494
    {
495
      if (SQL_INTEGER != targetType)
496
      {
497
        LogString msg(LOG4CXX_STR("Unexpected targetType ("));
498
        helpers::StringHelper::toString(targetType, msg);
499
        msg += LOG4CXX_STR(") at parameter ");
500
        helpers::StringHelper::toString(parameterNumber, msg);
501
        msg += LOG4CXX_STR(" while preparing SQL");
502
        LogLog::warn(msg);
503
      }
504
      item.paramMaxCharCount = 30;
505
#if LOG4CXX_LOGCHAR_IS_UTF8
506
      item.paramType = SQL_C_CHAR;
507
    {
508
      size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
509
      size_t cnt = (size_t)(item.paramMaxCharCount);
510
      size_t max_chars = (max_sz - sizeof(char)) / sizeof(char);
511
      if (cnt > max_chars) cnt = max_chars;
512
      size_t bytes = cnt * sizeof(char) + sizeof(char);
513
      item.paramValueSize = (SQLINTEGER)bytes;
514
    }
515
    item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(char));
516
#else
517
      item.paramType = SQL_C_WCHAR;
518
    {
519
      size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
520
      size_t cnt = (size_t)(item.paramMaxCharCount);
521
      size_t max_chars = (max_sz - sizeof(wchar_t)) / sizeof(wchar_t);
522
      if (cnt > max_chars) cnt = max_chars;
523
      size_t bytes = cnt * sizeof(wchar_t) + sizeof(wchar_t);
524
      item.paramValueSize = (SQLINTEGER)bytes;
525
    }
526
    item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(wchar_t));
527
#endif
528
    }
529
    item.strLen_or_Ind = SQL_NTS;
530
    ret = SQLBindParameter
531
      ( this->preparedStatement
532
      , parameterNumber
533
      , SQL_PARAM_INPUT
534
      , item.paramType  // ValueType
535
      , targetType
536
      , targetMaxCharCount
537
      , decimalDigits
538
      , item.paramValue
539
      , item.paramValueSize
540
      , &item.strLen_or_Ind
541
      );
542
    if (ret < 0)
543
    {
544
      throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to bind parameter", p);
545
    }
546
  }
547
}
548
549
void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventPtr& event, Pool& p)
550
{
551
  for (auto& item : this->parameterValue)
552
  {
553
    if (!item.paramValue || item.paramValueSize <= 0)
554
      ;
555
    else if (SQL_C_WCHAR == item.paramType)
556
    {
557
      LogString sbuf;
558
      item.converter->format(event, sbuf, p);
559
#if LOG4CXX_LOGCHAR_IS_WCHAR
560
      std::wstring& tmp = sbuf;
561
#else
562
      std::wstring tmp;
563
      Transcoder::encode(sbuf, tmp);
564
#endif
565
      auto dst = (wchar_t*)item.paramValue;
566
      auto charCount = std::min(size_t(item.paramMaxCharCount), tmp.size());
567
      auto copySize = std::min(size_t(item.paramValueSize - 1), charCount * sizeof(wchar_t));
568
      std::memcpy(dst, tmp.data(), copySize);
569
      dst[copySize / sizeof(wchar_t)] = 0;
570
    }
571
    else if (SQL_C_CHAR == item.paramType)
572
    {
573
      LogString sbuf;
574
      item.converter->format(event, sbuf, p);
575
#if LOG4CXX_LOGCHAR_IS_UTF8
576
      std::string& tmp = sbuf;
577
#else
578
      std::string tmp;
579
      Transcoder::encode(sbuf, tmp);
580
#endif
581
      auto dst = (char*)item.paramValue;
582
      auto sz = std::min(size_t(item.paramMaxCharCount), tmp.size());
583
      auto copySize = std::min(size_t(item.paramValueSize - 1), sz * sizeof(char));
584
      std::memcpy(dst, tmp.data(), copySize);
585
      dst[copySize] = 0;
586
    }
587
    else if (SQL_C_TYPE_TIMESTAMP == item.paramType)
588
    {
589
      apr_time_exp_t exploded;
590
      apr_status_t stat = this->timeZone->explode(&exploded, event->getTimeStamp());
591
      if (stat == APR_SUCCESS)
592
      {
593
        auto dst = (SQL_TIMESTAMP_STRUCT*)item.paramValue;
594
        dst->year = 1900 + exploded.tm_year;
595
        dst->month = 1 + exploded.tm_mon;
596
        dst->day = exploded.tm_mday;
597
        dst->hour = exploded.tm_hour;
598
        dst->minute = exploded.tm_min;
599
        dst->second = exploded.tm_sec;
600
        // Prevent '[ODBC SQL Server Driver]Datetime field overflow' by rounding to the target field precision
601
        int roundingExponent = 6 - (int)item.paramMaxCharCount;
602
        if (0 < roundingExponent)
603
        {
604
          int roundingDivisor = (int)std::pow(10, roundingExponent);
605
          dst->fraction = 1000 * roundingDivisor * ((exploded.tm_usec + roundingDivisor / 2) / roundingDivisor);
606
        }
607
        else
608
          dst->fraction = 1000 * exploded.tm_usec;
609
      }
610
    }
611
  }
612
}
613
#endif
614
615
void ODBCAppender::flushBuffer(Pool& p)
616
0
{
617
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
618
  if (0 == _priv->preparedStatement)
619
    _priv->setPreparedStatement(getConnection(p), p);
620
  _priv->flushBuffer(p);
621
#endif
622
0
}
623
624
void ODBCAppender::ODBCAppenderPriv::flushBuffer(Pool& p)
625
0
{
626
0
  if (0 == this->preparedStatement)
627
0
    ;
628
0
  else for (auto& logEvent : this->buffer)
629
0
  {
630
0
    if (this->parameterValue.empty())
631
0
      this->errorHandler->error(LOG4CXX_STR("ODBCAppender column mappings not defined"));
632
#if LOG4CXX_HAVE_ODBC && !LOG4CXX_LOGCHAR_IS_UNICHAR
633
    else try
634
    {
635
      this->setParameterValues(logEvent, p);
636
      auto ret = SQLExecute(this->preparedStatement);
637
      if (ret < 0)
638
      {
639
        throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to execute prepared statement", p);
640
      }
641
    }
642
    catch (SQLException& e)
643
    {
644
      this->errorHandler->error(LOG4CXX_STR("Failed to execute sql"), e,
645
        ErrorCode::FLUSH_FAILURE);
646
    }
647
#endif
648
0
  }
649
650
  // clear the buffer of reported events
651
0
  this->buffer.clear();
652
0
}
653
654
void ODBCAppender::setSql(const LogString& s)
655
0
{
656
0
  const logchar doubleQuote{ 0x22 };
657
0
  const logchar singleQuote{ 0x27 };
658
0
  const logchar semiColan{ 0x3b };
659
  // A basic check which disallows multiple SQL statements - for defense-in-depth security.
660
  // Allow a semicolan in a quoted context or as the last character.
661
0
  logchar currentQuote{ 0 };
662
0
  int charCount{ 0 };
663
0
  for (auto ch : s)
664
0
  {
665
0
    ++charCount;
666
0
    if (currentQuote == ch)
667
0
      currentQuote = 0;
668
0
    else if (currentQuote == 0)
669
0
    {
670
0
      if (doubleQuote == ch || singleQuote == ch)
671
0
        currentQuote = ch;
672
0
      else if (semiColan == ch && s.size() != charCount)
673
0
        throw IllegalArgumentException(LOG4CXX_STR("SQL statement cannot contain a ';'"));
674
0
    }
675
0
  }
676
0
  if (0 != currentQuote)
677
0
    throw IllegalArgumentException(LogString(LOG4CXX_STR("Unmatched ")) + currentQuote + LOG4CXX_STR(" in SQL statement"));
678
0
    _priv->sqlStatement = s;
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