Coverage Report

Created: 2026-05-30 06:57

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
#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
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
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
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
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
    SQLWCHAR* wURL, *wUser = nullptr, *wPwd = nullptr;
355
    encode(&wURL, _priv->databaseURL, p);
356
    if (!_priv->databaseUser.empty())
357
      encode(&wUser, _priv->databaseUser, p);
358
    if (!_priv->databasePassword.empty())
359
      encode(&wPwd, _priv->databasePassword, p);
360
361
    ret = SQLConnectW( _priv->connection
362
      , wURL, SQL_NTS
363
      , wUser, SQL_NTS
364
      , wPwd, SQL_NTS
365
      );
366
#endif
367
368
    if (ret < 0)
369
    {
370
      SQLException ex(SQL_HANDLE_DBC, _priv->connection, "Failed to connect to database", p);
371
      SQLFreeHandle(SQL_HANDLE_DBC, _priv->connection);
372
      _priv->connection = SQL_NULL_HDBC;
373
      throw ex;
374
    }
375
  }
376
377
  return _priv->connection;
378
#else
379
0
  return 0;
380
0
#endif
381
0
}
382
383
void ODBCAppender::close()
384
0
{
385
0
  if (_priv->setClosed())
386
0
  {
387
#if LOG4CXX_HAVE_ODBC
388
    if (!_priv->buffer.empty() && 0 == _priv->preparedStatement)
389
    {
390
      Pool p;
391
      _priv->setPreparedStatement(getConnection(p), p);
392
    }
393
#endif
394
0
    _priv->close();
395
0
  }
396
0
}
397
398
void ODBCAppender::ODBCAppenderPriv::close()
399
0
{
400
0
  try
401
0
  {
402
0
    Pool p;
403
0
    flushBuffer(p);
404
0
  }
405
0
  catch (SQLException& e)
406
0
  {
407
0
    this->errorHandler->error(LOG4CXX_STR("Error closing connection"),
408
0
      e, ErrorCode::GENERIC_FAILURE);
409
0
  }
410
411
#if LOG4CXX_HAVE_ODBC
412
413
  if (this->connection != SQL_NULL_HDBC)
414
  {
415
    SQLDisconnect(this->connection);
416
    SQLFreeHandle(SQL_HANDLE_DBC, this->connection);
417
  }
418
419
  if (this->env != SQL_NULL_HENV)
420
  {
421
    SQLFreeHandle(SQL_HANDLE_ENV, this->env);
422
  }
423
424
#endif
425
0
}
426
427
#if LOG4CXX_HAVE_ODBC
428
void ODBCAppender::ODBCAppenderPriv::setPreparedStatement(SQLHDBC con, Pool& p)
429
{
430
  auto ret = SQLAllocHandle( SQL_HANDLE_STMT, con, &this->preparedStatement);
431
  if (ret < 0)
432
  {
433
    throw SQLException( SQL_HANDLE_DBC, con, "Failed to allocate statement handle.", p);
434
  }
435
436
#if LOG4CXX_LOGCHAR_IS_WCHAR
437
  ret = SQLPrepareW(this->preparedStatement, (SQLWCHAR*)this->sqlStatement.c_str(), SQL_NTS);
438
#elif LOG4CXX_LOGCHAR_IS_UTF8
439
  ret = SQLPrepareA(this->preparedStatement, (SQLCHAR*)this->sqlStatement.c_str(), SQL_NTS);
440
#else
441
  SQLWCHAR* wsql;
442
  encode(&wsql, this->sqlStatement, p);
443
  ret = SQLPrepareW(this->preparedStatement, wsql, SQL_NTS);
444
#endif
445
  if (ret < 0)
446
  {
447
    throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to prepare sql statement.", p);
448
  }
449
450
  int parameterNumber = 0;
451
  for (auto& item : this->parameterValue)
452
  {
453
    ++parameterNumber;
454
    SQLSMALLINT  targetType;
455
    SQLULEN      targetMaxCharCount;
456
    SQLSMALLINT  decimalDigits;
457
    SQLSMALLINT  nullable;
458
    auto ret = SQLDescribeParam
459
      ( this->preparedStatement
460
      , parameterNumber
461
      , &targetType
462
      , &targetMaxCharCount
463
      , &decimalDigits
464
      , &nullable
465
      );
466
    if (ret < 0)
467
    {
468
      throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to describe parameter", p);
469
    }
470
    if (SQL_CHAR == targetType || SQL_VARCHAR == targetType || SQL_LONGVARCHAR == targetType)
471
    {
472
      item.paramType = SQL_C_CHAR;
473
      item.paramMaxCharCount = targetMaxCharCount;
474
      {
475
        size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
476
        size_t cnt = (size_t)(item.paramMaxCharCount);
477
        size_t max_chars = (max_sz - sizeof(char)) / sizeof(char);
478
        if (cnt > max_chars) cnt = max_chars;
479
        size_t bytes = cnt * sizeof(char) + sizeof(char);
480
        item.paramValueSize = (SQLINTEGER)bytes;
481
      }
482
      item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(char));
483
    }
484
    else if (SQL_WCHAR == targetType || SQL_WVARCHAR == targetType || SQL_WLONGVARCHAR == targetType)
485
    {
486
      item.paramType = SQL_C_WCHAR;
487
      item.paramMaxCharCount = targetMaxCharCount;
488
      {
489
        size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
490
        size_t cnt = (size_t)(targetMaxCharCount);
491
        size_t max_chars = (max_sz - sizeof(wchar_t)) / sizeof(wchar_t);
492
        if (cnt > max_chars) cnt = max_chars;
493
        size_t bytes = cnt * sizeof(wchar_t) + sizeof(wchar_t);
494
        item.paramValueSize = (SQLINTEGER)bytes;
495
      }
496
      item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(wchar_t));
497
    }
498
    else if (SQL_TYPE_TIMESTAMP == targetType || SQL_TYPE_DATE == targetType || SQL_TYPE_TIME == targetType
499
      || SQL_DATETIME == targetType)
500
    {
501
      item.paramType = SQL_C_TYPE_TIMESTAMP;
502
      item.paramMaxCharCount = (0 <= decimalDigits) ? decimalDigits : 6;
503
      item.paramValueSize = sizeof(SQL_TIMESTAMP_STRUCT);
504
      item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize);
505
    }
506
    else
507
    {
508
      if (SQL_INTEGER != targetType)
509
      {
510
        LogString msg(LOG4CXX_STR("Unexpected targetType ("));
511
        helpers::StringHelper::toString(targetType, msg);
512
        msg += LOG4CXX_STR(") at parameter ");
513
        helpers::StringHelper::toString(parameterNumber, msg);
514
        msg += LOG4CXX_STR(" while preparing SQL");
515
        LogLog::warn(msg);
516
      }
517
      item.paramMaxCharCount = 30;
518
#if LOG4CXX_LOGCHAR_IS_UTF8
519
      item.paramType = SQL_C_CHAR;
520
    {
521
      size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
522
      size_t cnt = (size_t)(item.paramMaxCharCount);
523
      size_t max_chars = (max_sz - sizeof(char)) / sizeof(char);
524
      if (cnt > max_chars) cnt = max_chars;
525
      size_t bytes = cnt * sizeof(char) + sizeof(char);
526
      item.paramValueSize = (SQLINTEGER)bytes;
527
    }
528
    item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(char));
529
#else
530
      item.paramType = SQL_C_WCHAR;
531
    {
532
      size_t max_sz = (size_t)(std::numeric_limits<SQLINTEGER>::max)();
533
      size_t cnt = (size_t)(item.paramMaxCharCount);
534
      size_t max_chars = (max_sz - sizeof(wchar_t)) / sizeof(wchar_t);
535
      if (cnt > max_chars) cnt = max_chars;
536
      size_t bytes = cnt * sizeof(wchar_t) + sizeof(wchar_t);
537
      item.paramValueSize = (SQLINTEGER)bytes;
538
    }
539
    item.paramValue = (SQLPOINTER)this->pool.palloc(item.paramValueSize + sizeof(wchar_t));
540
#endif
541
    }
542
    item.strLen_or_Ind = SQL_NTS;
543
    ret = SQLBindParameter
544
      ( this->preparedStatement
545
      , parameterNumber
546
      , SQL_PARAM_INPUT
547
      , item.paramType  // ValueType
548
      , targetType
549
      , targetMaxCharCount
550
      , decimalDigits
551
      , item.paramValue
552
      , item.paramValueSize
553
      , &item.strLen_or_Ind
554
      );
555
    if (ret < 0)
556
    {
557
      throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to bind parameter", p);
558
    }
559
  }
560
}
561
562
void ODBCAppender::ODBCAppenderPriv::setParameterValues(const spi::LoggingEventPtr& event, Pool& p)
563
{
564
  for (auto& item : this->parameterValue)
565
  {
566
    if (!item.paramValue || item.paramValueSize <= 0)
567
      ;
568
    else if (SQL_C_WCHAR == item.paramType)
569
    {
570
      LogString sbuf;
571
      item.converter->format(event, sbuf, p);
572
#if LOG4CXX_LOGCHAR_IS_WCHAR
573
      std::wstring& tmp = sbuf;
574
#else
575
      std::wstring tmp;
576
      Transcoder::encode(sbuf, tmp);
577
#endif
578
      auto dst = (wchar_t*)item.paramValue;
579
      auto charCount = std::min(size_t(item.paramMaxCharCount), tmp.size());
580
      auto copySize = std::min(size_t(item.paramValueSize - 1), charCount * sizeof(wchar_t));
581
      std::memcpy(dst, tmp.data(), copySize);
582
      dst[copySize / sizeof(wchar_t)] = 0;
583
    }
584
    else if (SQL_C_CHAR == item.paramType)
585
    {
586
      LogString sbuf;
587
      item.converter->format(event, sbuf, p);
588
#if LOG4CXX_LOGCHAR_IS_UTF8
589
      std::string& tmp = sbuf;
590
#else
591
      std::string tmp;
592
      Transcoder::encode(sbuf, tmp);
593
#endif
594
      auto dst = (char*)item.paramValue;
595
      auto sz = std::min(size_t(item.paramMaxCharCount), tmp.size());
596
      auto copySize = std::min(size_t(item.paramValueSize - 1), sz * sizeof(char));
597
      std::memcpy(dst, tmp.data(), copySize);
598
      dst[copySize] = 0;
599
    }
600
    else if (SQL_C_TYPE_TIMESTAMP == item.paramType)
601
    {
602
      apr_time_exp_t exploded;
603
      apr_status_t stat = this->timeZone->explode(&exploded, event->getTimeStamp());
604
      if (stat == APR_SUCCESS)
605
      {
606
        auto dst = (SQL_TIMESTAMP_STRUCT*)item.paramValue;
607
        dst->year = 1900 + exploded.tm_year;
608
        dst->month = 1 + exploded.tm_mon;
609
        dst->day = exploded.tm_mday;
610
        dst->hour = exploded.tm_hour;
611
        dst->minute = exploded.tm_min;
612
        dst->second = exploded.tm_sec;
613
        // Prevent '[ODBC SQL Server Driver]Datetime field overflow' by rounding to the target field precision
614
        int roundingExponent = 6 - (int)item.paramMaxCharCount;
615
        if (0 < roundingExponent)
616
        {
617
          int roundingDivisor = (int)std::pow(10, roundingExponent);
618
          dst->fraction = 1000 * roundingDivisor * ((exploded.tm_usec + roundingDivisor / 2) / roundingDivisor);
619
        }
620
        else
621
          dst->fraction = 1000 * exploded.tm_usec;
622
      }
623
    }
624
  }
625
}
626
#endif
627
628
void ODBCAppender::flushBuffer(Pool& p)
629
0
{
630
#if LOG4CXX_HAVE_ODBC
631
  if (0 == _priv->preparedStatement)
632
    _priv->setPreparedStatement(getConnection(p), p);
633
  _priv->flushBuffer(p);
634
#endif
635
0
}
636
637
void ODBCAppender::ODBCAppenderPriv::flushBuffer(Pool& p)
638
0
{
639
0
  if (0 == this->preparedStatement)
640
0
    ;
641
0
  else for (auto& logEvent : this->buffer)
642
0
  {
643
0
    if (this->parameterValue.empty())
644
0
      this->errorHandler->error(LOG4CXX_STR("ODBCAppender column mappings not defined"));
645
#if LOG4CXX_HAVE_ODBC
646
    else try
647
    {
648
      this->setParameterValues(logEvent, p);
649
      auto ret = SQLExecute(this->preparedStatement);
650
      if (ret < 0)
651
      {
652
        throw SQLException(SQL_HANDLE_STMT, this->preparedStatement, "Failed to execute prepared statement", p);
653
      }
654
    }
655
    catch (SQLException& e)
656
    {
657
      this->errorHandler->error(LOG4CXX_STR("Failed to execute sql"), e,
658
        ErrorCode::FLUSH_FAILURE);
659
    }
660
#endif
661
0
  }
662
663
  // clear the buffer of reported events
664
0
  this->buffer.clear();
665
0
}
666
667
void ODBCAppender::setSql(const LogString& s)
668
0
{
669
0
  const logchar doubleQuote{ 0x22 };
670
0
  const logchar singleQuote{ 0x27 };
671
0
  const logchar semiColan{ 0x3b };
672
  // A basic check which disallows multiple SQL statements - for defense-in-depth security.
673
  // Allow a semicolan in a quoted context or as the last character.
674
0
  logchar currentQuote{ 0 };
675
0
  int charCount{ 0 };
676
0
  for (auto ch : s)
677
0
  {
678
0
    ++charCount;
679
0
    if (currentQuote == ch)
680
0
      currentQuote = 0;
681
0
    else if (currentQuote == 0)
682
0
    {
683
0
      if (doubleQuote == ch || singleQuote == ch)
684
0
        currentQuote = ch;
685
0
      else if (semiColan == ch && s.size() != charCount)
686
0
        throw IllegalArgumentException(LOG4CXX_STR("SQL statement cannot contain a ';'"));
687
0
    }
688
0
  }
689
0
  if (0 != currentQuote)
690
0
    throw IllegalArgumentException(LogString(LOG4CXX_STR("Unmatched ")) + currentQuote + LOG4CXX_STR(" in SQL statement"));
691
0
    _priv->sqlStatement = s;
692
0
}
693
694
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR || defined(WIN32) || defined(_WIN32)
695
void ODBCAppender::encode(wchar_t** dest, const LogString& src, Pool& p)
696
0
{
697
0
  *dest = Transcoder::wencode(src, p);
698
0
}
699
#endif
700
701
void ODBCAppender::encode(unsigned short** dest,
702
  const LogString& src, Pool& p)
703
0
{
704
  //  worst case double number of characters from UTF-8 or wchar_t
705
0
  *dest = (unsigned short*)
706
0
    p.palloc((src.size() + 1) * 2 * sizeof(unsigned short));
707
0
  unsigned short* current = *dest;
708
709
0
  for (LogString::const_iterator i = src.begin();
710
0
    i != src.end();)
711
0
  {
712
0
    unsigned int sv = Transcoder::decode(src, i);
713
714
0
    if (sv < 0x10000)
715
0
    {
716
0
      *current++ = (unsigned short) sv;
717
0
    }
718
0
    else
719
0
    {
720
0
      unsigned char u = (unsigned char) (sv >> 16);
721
0
      unsigned char w = (unsigned char) (u - 1);
722
0
      unsigned short hs = (0xD800 + ((w & 0xF) << 6) + ((sv & 0xFFFF) >> 10));
723
0
      unsigned short ls = (0xDC00 + (sv & 0x3FF));
724
0
      *current++ = (unsigned short) hs;
725
0
      *current++ = (unsigned short) ls;
726
0
    }
727
0
  }
728
729
0
  *current = 0;
730
0
}
731
732
const LogString& ODBCAppender::getSql() const
733
0
{
734
0
  return _priv->sqlStatement;
735
0
}
736
737
void ODBCAppender::setUser(const LogString& user)
738
0
{
739
0
  _priv->databaseUser = user;
740
0
}
741
742
void ODBCAppender::setURL(const LogString& url)
743
0
{
744
0
  _priv->databaseURL = url;
745
0
}
746
747
void ODBCAppender::setPassword(const LogString& password)
748
0
{
749
0
  _priv->databasePassword = password;
750
0
}
751
752
void ODBCAppender::setBufferSize(size_t newBufferSize)
753
0
{
754
0
  _priv->bufferSize = newBufferSize;
755
0
}
756
757
const LogString& ODBCAppender::getUser() const
758
0
{
759
0
  return _priv->databaseUser;
760
0
}
761
762
const LogString& ODBCAppender::getURL() const
763
0
{
764
0
  return _priv->databaseURL;
765
0
}
766
767
const LogString& ODBCAppender::getPassword() const
768
0
{
769
0
  return _priv->databasePassword;
770
0
}
771
772
size_t ODBCAppender::getBufferSize() const
773
0
{
774
0
  return _priv->bufferSize;
775
0
}
776