• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 17235120617

26 Aug 2025 10:17AM UTC coverage: 65.959% (-0.02%) from 65.977%
17235120617

Pull #16016

github

web-flow
Merge d1e0ec6fc into 9eeac00a7
Pull Request #16016: auth: random doc nits

42117 of 92446 branches covered (45.56%)

Branch coverage included in aggregate %.

128034 of 165518 relevant lines covered (77.35%)

5925196.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.71
/modules/gmysqlbackend/smysql.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22

23
#ifdef HAVE_CONFIG_H
24
#include "config.h"
25
#endif
26
#include "smysql.hh"
27
#include <string>
28
#include <iostream>
29
#include "pdns/misc.hh"
30
#include "pdns/logger.hh"
31
#include "pdns/dns.hh"
32
#include "pdns/namespaces.hh"
33
#include "pdns/lock.hh"
34

35
#if MYSQL_VERSION_ID >= 80000 && !defined(MARIADB_BASE_VERSION)
36
// Need to keep this for compatibility with MySQL < 8.0.0, which used typedef char my_bool;
37
// MariaDB up to 10.4 also always define it.
38
typedef bool my_bool;
39
#endif
40

41
/*
42
 * Older versions of the MySQL and MariaDB client leak memory
43
 * because they expect the application to call mysql_thread_end()
44
 * when a thread ends. This thread_local static object provides
45
 * that closure, but only when the user has asked for it
46
 * by setting gmysql-thread-cleanup.
47
 * For more discussion, see https://github.com/PowerDNS/pdns/issues/6231
48
 */
49
class MySQLThreadCloser
50
{
51
public:
52
  ~MySQLThreadCloser()
53
  {
×
54
    if (d_enabled) {
×
55
      mysql_thread_end();
×
56
    }
×
57
  }
×
58
  void enable()
59
  {
×
60
    d_enabled = true;
×
61
  }
×
62

63
private:
64
  bool d_enabled = false;
65
};
66

67
static thread_local MySQLThreadCloser threadcloser;
68

69
bool SMySQL::s_dolog;
70
std::mutex SMySQL::s_myinitlock;
71

72
class SMySQLStatement : public SSqlStatement
73
{
74
public:
75
  SMySQLStatement(const string& query, bool dolog, int nparams, MYSQL* db) :
76
    d_prepared(false)
77
  {
286,216✔
78
    d_db = db;
286,216✔
79
    d_dolog = dolog;
286,216✔
80
    d_query = query;
286,216✔
81
    d_paridx = d_fnum = d_resnum = d_residx = 0;
286,216✔
82
    d_parnum = nparams;
286,216✔
83
    d_req_bind = d_res_bind = nullptr;
286,216✔
84
    d_stmt = nullptr;
286,216✔
85

86
    if (query.empty()) {
286,216✔
87
      return;
4,217✔
88
    }
4,217✔
89
  }
286,216✔
90

91
  SSqlStatement* bind(const string& /* name */, bool value) override
92
  {
1,030,094✔
93
    prepareStatement();
1,030,094✔
94
    if (d_paridx >= d_parnum) {
1,030,094!
95
      releaseStatement();
×
96
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
97
    }
×
98
    d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_TINY;
1,030,094✔
99
    d_req_bind[d_paridx].buffer = new char[1];
1,030,094✔
100
    *((char*)d_req_bind[d_paridx].buffer) = (value ? 1 : 0);
1,030,094✔
101
    d_paridx++;
1,030,094✔
102
    return this;
1,030,094✔
103
  }
1,030,094✔
104
  SSqlStatement* bind(const string& name, int value) override
105
  {
1,108,874✔
106
    return bind(name, (long)value);
1,108,874✔
107
  }
1,108,874✔
108
  SSqlStatement* bind(const string& name, uint32_t value) override
109
  {
431,293✔
110
    return bind(name, (unsigned long)value);
431,293✔
111
  }
431,293✔
112
  SSqlStatement* bind(const string& /* name */, long value) override
113
  {
1,109,065✔
114
    prepareStatement();
1,109,065✔
115
    if (d_paridx >= d_parnum) {
1,109,065!
116
      releaseStatement();
×
117
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
118
    }
×
119
    if constexpr (sizeof(long) == 4) {
1,109,065✔
120
      d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_LONG; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
×
121
    }
×
122
    else {
1,109,065✔
123
      d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_LONGLONG; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1,109,065✔
124
    }
1,109,065✔
125
    d_req_bind[d_paridx].buffer = new long[1];
1,109,065✔
126
    *((long*)d_req_bind[d_paridx].buffer) = value;
1,109,065✔
127
    d_paridx++;
1,109,065✔
128
    return this;
1,109,065✔
129
  }
1,109,065✔
130
  SSqlStatement* bind(const string& /* name */, unsigned long value) override
131
  {
431,304✔
132
    prepareStatement();
431,304✔
133
    if (d_paridx >= d_parnum) {
431,304!
134
      releaseStatement();
×
135
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
136
    }
×
137
    if constexpr (sizeof(long) == 4) {
431,304✔
138
      d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_LONG; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
×
139
    }
×
140
    else {
431,304✔
141
      d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_LONGLONG; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
431,304✔
142
    }
431,304✔
143
    d_req_bind[d_paridx].buffer = new unsigned long[1];
431,304✔
144
    d_req_bind[d_paridx].is_unsigned = 1;
431,304✔
145
    *((unsigned long*)d_req_bind[d_paridx].buffer) = value;
431,304✔
146
    d_paridx++;
431,304✔
147
    return this;
431,304✔
148
  }
431,304✔
149
  SSqlStatement* bind(const string& /* name */, long long value) override
150
  {
×
151
    prepareStatement();
×
152
    if (d_paridx >= d_parnum) {
×
153
      releaseStatement();
×
154
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
155
    }
×
156
    d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_LONGLONG;
×
157
    d_req_bind[d_paridx].buffer = new long long[1];
×
158
    *((long long*)d_req_bind[d_paridx].buffer) = value;
×
159
    d_paridx++;
×
160
    return this;
×
161
  }
×
162
  SSqlStatement* bind(const string& /* name */, unsigned long long value) override
163
  {
×
164
    prepareStatement();
×
165
    if (d_paridx >= d_parnum) {
×
166
      releaseStatement();
×
167
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
168
    }
×
169
    d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_LONGLONG;
×
170
    d_req_bind[d_paridx].buffer = new unsigned long long[1];
×
171
    d_req_bind[d_paridx].is_unsigned = 1;
×
172
    *((unsigned long long*)d_req_bind[d_paridx].buffer) = value;
×
173
    d_paridx++;
×
174
    return this;
×
175
  }
×
176
  SSqlStatement* bind(const string& /* name */, const std::string& value) override
177
  {
1,885,403✔
178
    prepareStatement();
1,885,403✔
179
    if (d_paridx >= d_parnum) {
1,885,403!
180
      releaseStatement();
×
181
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
182
    }
×
183
    d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_STRING;
1,885,403✔
184
    d_req_bind[d_paridx].buffer = new char[value.size() + 1];
1,885,403✔
185
    d_req_bind[d_paridx].length = new unsigned long[1];
1,885,403✔
186
    *d_req_bind[d_paridx].length = value.size();
1,885,403✔
187
    d_req_bind[d_paridx].buffer_length = *d_req_bind[d_paridx].length + 1;
1,885,403✔
188
    memset(d_req_bind[d_paridx].buffer, 0, value.size() + 1);
1,885,403✔
189
    value.copy((char*)d_req_bind[d_paridx].buffer, value.size());
1,885,403✔
190
    d_paridx++;
1,885,403✔
191
    return this;
1,885,403✔
192
  }
1,885,403✔
193
  SSqlStatement* bindNull(const string& /* name */) override
194
  {
309,241✔
195
    prepareStatement();
309,241✔
196
    if (d_paridx >= d_parnum) {
309,241!
197
      releaseStatement();
×
198
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
199
    }
×
200
    d_req_bind[d_paridx].buffer_type = MYSQL_TYPE_NULL;
309,241✔
201
    d_paridx++;
309,241✔
202
    return this;
309,241✔
203
  }
309,241✔
204

205
  SSqlStatement* execute() override
206
  {
706,025✔
207
    prepareStatement();
706,025✔
208

209
    if (!d_stmt)
706,025!
210
      return this;
×
211

212
    if (d_dolog) {
706,025!
213
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_query << endl;
×
214
      d_dtime.set();
×
215
    }
×
216

217
    if (mysql_stmt_bind_param(d_stmt, d_req_bind) != 0) {
706,025!
218
      string error(mysql_stmt_error(d_stmt));
×
219
      releaseStatement();
×
220
      throw SSqlException("Could not bind mysql statement: " + d_query + string(": ") + error);
×
221
    }
×
222

223
    if (mysql_stmt_execute(d_stmt) != 0) {
706,025!
224
      string error(mysql_stmt_error(d_stmt));
×
225
      releaseStatement();
×
226
      throw SSqlException("Could not execute mysql statement: " + d_query + string(": ") + error);
×
227
    }
×
228

229
    // MySQL documentation says you can call this safely for all queries
230
    if (mysql_stmt_store_result(d_stmt) != 0) {
706,025!
231
      string error(mysql_stmt_error(d_stmt));
×
232
      releaseStatement();
×
233
      throw SSqlException("Could not store mysql statement: " + d_query + string(": ") + error);
×
234
    }
×
235

236
    if ((d_fnum = static_cast<int>(mysql_stmt_field_count(d_stmt))) > 0) {
706,025✔
237
      // prepare for result
238
      d_resnum = mysql_stmt_num_rows(d_stmt);
98,120✔
239

240
      if (d_resnum > 0 && d_res_bind == nullptr) {
98,120✔
241
        MYSQL_RES* meta = mysql_stmt_result_metadata(d_stmt);
8,253✔
242
        d_fnum = static_cast<int>(mysql_num_fields(meta)); // ensure correct number of fields
8,253✔
243
        d_res_bind = new MYSQL_BIND[d_fnum];
8,253✔
244
        memset(d_res_bind, 0, sizeof(MYSQL_BIND) * d_fnum);
8,253✔
245
        MYSQL_FIELD* fields = mysql_fetch_fields(meta);
8,253✔
246

247
        for (int i = 0; i < d_fnum; i++) {
63,410✔
248
          unsigned long len = std::max(fields[i].max_length, fields[i].length) + 1;
55,157✔
249
          if (len > 128 * 1024)
55,157✔
250
            len = 128 * 1024; // LONGTEXT may tell us it needs 4GB!
7,567✔
251
          d_res_bind[i].is_null = new my_bool[1];
55,157✔
252
          d_res_bind[i].error = new my_bool[1];
55,157✔
253
          d_res_bind[i].length = new unsigned long[1];
55,157✔
254
          d_res_bind[i].buffer = new char[len];
55,157✔
255
          d_res_bind[i].buffer_length = len;
55,157✔
256
          d_res_bind[i].buffer_type = MYSQL_TYPE_STRING;
55,157✔
257
        }
55,157✔
258

259
        mysql_free_result(meta);
8,253✔
260
      }
8,253✔
261

262
      /* we need to bind the results array again because a call to mysql_stmt_next_result() followed
263
         by a call to mysql_stmt_store_result() might have invalidated it (the first one sets
264
         stmt->bind_result_done to false, causing the second to reset the existing binding),
265
         and we can't bind it right after the call to mysql_stmt_store_result() if it returned
266
         no rows, because then the statement 'contains no metadata' */
267
      if (d_res_bind != nullptr && mysql_stmt_bind_result(d_stmt, d_res_bind) != 0) {
98,120!
268
        string error(mysql_stmt_error(d_stmt));
×
269
        releaseStatement();
×
270
        throw SSqlException("Could not bind parameters to mysql statement: " + d_query + string(": ") + error);
×
271
      }
×
272
    }
98,120✔
273

274
    if (d_dolog)
706,025!
275
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
×
276

277
    return this;
706,025✔
278
  }
706,025✔
279

280
  bool hasNextRow() override
281
  {
1,436,203✔
282
    if (d_dolog && d_residx == d_resnum) {
1,436,203!
283
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us total to last row" << endl;
×
284
    }
×
285
    return d_residx < d_resnum;
1,436,203✔
286
  }
1,436,203✔
287

288
  SSqlStatement* nextRow(row_t& row) override
289
  {
669,043✔
290
    int err;
669,043✔
291
    row.clear();
669,043✔
292
    if (!hasNextRow()) {
669,043!
293
      return this;
×
294
    }
×
295

296
    if ((err = mysql_stmt_fetch(d_stmt))) {
669,043!
297
      if (err != MYSQL_DATA_TRUNCATED) {
×
298
        string error(mysql_stmt_error(d_stmt));
×
299
        releaseStatement();
×
300
        throw SSqlException("Could not fetch result: " + d_query + string(": ") + error);
×
301
      }
×
302
    }
×
303

304
    row.reserve(d_fnum);
669,043✔
305

306
    for (int i = 0; i < d_fnum; i++) {
6,164,188✔
307
      if (err == MYSQL_DATA_TRUNCATED && *d_res_bind[i].error) {
5,495,145!
308
        g_log << Logger::Warning << "Result field at row " << d_residx << " column " << i << " has been truncated, we allocated " << d_res_bind[i].buffer_length << " bytes but at least " << *d_res_bind[i].length << " was needed" << endl;
×
309
      }
×
310
      if (*d_res_bind[i].is_null) {
5,495,145✔
311
        row.emplace_back("");
365,658✔
312
        continue;
365,658✔
313
      }
365,658✔
314
      else {
5,129,487✔
315
        row.emplace_back((char*)d_res_bind[i].buffer, std::min(d_res_bind[i].buffer_length, *d_res_bind[i].length));
5,129,487✔
316
      }
5,129,487✔
317
    }
5,495,145✔
318

319
    d_residx++;
669,043✔
320
#if MYSQL_VERSION_ID >= 50500
669,043✔
321
    if (d_residx >= d_resnum) {
669,043✔
322
      mysql_stmt_free_result(d_stmt);
70,994✔
323
      while (!mysql_stmt_next_result(d_stmt)) {
71,382✔
324
        if (mysql_stmt_store_result(d_stmt) != 0) {
388!
325
          string error(mysql_stmt_error(d_stmt));
×
326
          releaseStatement();
×
327
          throw SSqlException("Could not store mysql statement while processing additional sets: " + d_query + string(": ") + error);
×
328
        }
×
329
        d_resnum = mysql_stmt_num_rows(d_stmt);
388✔
330
        // XXX: For some reason mysql_stmt_result_metadata returns NULL here, so we cannot
331
        // ensure row field count matches first result set.
332
        // We need to check the field count as stored procedure return the final values of OUT and INOUT parameters
333
        // as an extra single-row result set following any result sets produced by the procedure itself.
334
        // mysql_stmt_field_count() will return 0 for those.
335
        if (mysql_stmt_field_count(d_stmt) > 0 && d_resnum > 0) { // ignore empty result set
388!
336
          if (d_res_bind != nullptr && mysql_stmt_bind_result(d_stmt, d_res_bind) != 0) {
×
337
            string error(mysql_stmt_error(d_stmt));
×
338
            releaseStatement();
×
339
            throw SSqlException("Could not bind parameters to mysql statement: " + d_query + string(": ") + error);
×
340
          }
×
341
          d_residx = 0;
×
342
          break;
×
343
        }
×
344
        mysql_stmt_free_result(d_stmt);
388✔
345
      }
388✔
346
    }
70,994✔
347
#endif
669,043✔
348
    return this;
669,043✔
349
  }
669,043✔
350

351
  SSqlStatement* getResult(result_t& result) override
352
  {
4,095✔
353
    result.clear();
4,095✔
354
    result.reserve(d_resnum);
4,095✔
355
    row_t row;
4,095✔
356

357
    while (hasNextRow()) {
8,790✔
358
      nextRow(row);
4,695✔
359
      result.push_back(std::move(row));
4,695✔
360
    }
4,695✔
361

362
    return this;
4,095✔
363
  }
4,095✔
364

365
  SSqlStatement* reset() override
366
  {
706,024✔
367
    if (!d_stmt)
706,024!
368
      return this;
×
369
    int err = 0;
706,024✔
370
    mysql_stmt_free_result(d_stmt);
706,024✔
371
#if MYSQL_VERSION_ID >= 50500
706,024✔
372
    while ((err = mysql_stmt_next_result(d_stmt)) == 0) {
706,786✔
373
      mysql_stmt_free_result(d_stmt);
762✔
374
    }
762✔
375
#endif
706,024✔
376
    if (err > 0) {
706,024!
377
      string error(mysql_stmt_error(d_stmt));
×
378
      releaseStatement();
×
379
      throw SSqlException("Could not get next result from mysql statement: " + d_query + string(": ") + error);
×
380
    }
×
381
    mysql_stmt_reset(d_stmt);
706,024✔
382
    if (d_req_bind) {
706,024✔
383
      for (int i = 0; i < d_parnum; i++) {
5,470,802✔
384
        if (d_req_bind[i].buffer)
4,765,083✔
385
          delete[] (char*)d_req_bind[i].buffer;
4,455,853✔
386
        if (d_req_bind[i].length)
4,765,083✔
387
          delete[] d_req_bind[i].length;
1,885,394✔
388
      }
4,765,083✔
389
      memset(d_req_bind, 0, sizeof(MYSQL_BIND) * d_parnum);
705,719✔
390
    }
705,719✔
391
    d_residx = d_resnum = 0;
706,024✔
392
    d_paridx = 0;
706,024✔
393
    return this;
706,024✔
394
  }
706,024✔
395

396
  const std::string& getQuery() override { return d_query; }
×
397

398
  ~SMySQLStatement() override
399
  {
270,821✔
400
    releaseStatement();
270,821✔
401
  }
270,821✔
402

403
private:
404
  void prepareStatement()
405
  {
5,471,127✔
406
    if (d_prepared)
5,471,127✔
407
      return;
5,456,282✔
408
    if (d_query.empty()) {
14,845!
409
      d_prepared = true;
×
410
      return;
×
411
    }
×
412

413
    if ((d_stmt = mysql_stmt_init(d_db)) == nullptr)
14,845!
414
      throw SSqlException("Could not initialize mysql statement, out of memory: " + d_query);
×
415

416
    if (mysql_stmt_prepare(d_stmt, d_query.c_str(), d_query.size()) != 0) {
14,845!
417
      string error(mysql_stmt_error(d_stmt));
×
418
      releaseStatement();
×
419
      throw SSqlException("Could not prepare statement: " + d_query + string(": ") + error);
×
420
    }
×
421

422
    if (static_cast<int>(mysql_stmt_param_count(d_stmt)) != d_parnum) {
14,845!
423
      releaseStatement();
×
424
      throw SSqlException("Provided parameter count does not match statement: " + d_query);
×
425
    }
×
426

427
    if (d_parnum > 0) {
14,845✔
428
      d_req_bind = new MYSQL_BIND[d_parnum];
14,552✔
429
      memset(d_req_bind, 0, sizeof(MYSQL_BIND) * d_parnum);
14,552✔
430
    }
14,552✔
431

432
    d_prepared = true;
14,845✔
433
  }
14,845✔
434

435
  void releaseStatement()
436
  {
270,812✔
437
    d_prepared = false;
270,812✔
438
    if (d_stmt)
270,812✔
439
      mysql_stmt_close(d_stmt);
13,199✔
440
    d_stmt = nullptr;
270,812✔
441
    if (d_req_bind) {
270,812✔
442
      for (int i = 0; i < d_parnum; i++) {
42,210✔
443
        if (d_req_bind[i].buffer)
29,257!
444
          delete[] (char*)d_req_bind[i].buffer;
×
445
        if (d_req_bind[i].length)
29,257!
446
          delete[] d_req_bind[i].length;
×
447
      }
29,257✔
448
      delete[] d_req_bind;
12,953✔
449
      d_req_bind = nullptr;
12,953✔
450
    }
12,953✔
451
    if (d_res_bind) {
270,812✔
452
      for (int i = 0; i < d_fnum; i++) {
57,361✔
453
        if (d_res_bind[i].buffer)
50,177!
454
          delete[] (char*)d_res_bind[i].buffer;
50,177✔
455
        if (d_res_bind[i].length)
50,177!
456
          delete[] d_res_bind[i].length;
50,177✔
457
        if (d_res_bind[i].error)
50,177!
458
          delete[] d_res_bind[i].error;
50,177✔
459
        if (d_res_bind[i].is_null)
50,177!
460
          delete[] d_res_bind[i].is_null;
50,177✔
461
      }
50,177✔
462
      delete[] d_res_bind;
7,184✔
463
      d_res_bind = nullptr;
7,184✔
464
    }
7,184✔
465
    d_paridx = d_fnum = d_resnum = d_residx = 0;
270,812✔
466
  }
270,812✔
467
  MYSQL* d_db;
468

469
  MYSQL_STMT* d_stmt;
470
  MYSQL_BIND* d_req_bind;
471
  MYSQL_BIND* d_res_bind;
472

473
  string d_query;
474

475
  bool d_prepared;
476
  bool d_dolog;
477
  DTime d_dtime; // only used if d_dolog is set
478
  int d_parnum;
479
  int d_paridx;
480
  int d_fnum;
481
  int d_resnum;
482
  int d_residx;
483
};
484

485
void SMySQL::connect()
486
{
4,217✔
487
  int retry = 1;
4,217✔
488

489
  {
4,217✔
490
    auto lock = std::scoped_lock(s_myinitlock);
4,217✔
491
    if (d_threadCleanup) {
4,217!
492
      threadcloser.enable();
×
493
    }
×
494

495
    if (!mysql_init(&d_db)) {
4,217!
496
      throw sPerrorException("Unable to initialize mysql driver");
×
497
    }
×
498
  }
4,217✔
499

500
  do {
4,217✔
501

502
#if MYSQL_VERSION_ID >= 50100
4,217✔
503
    if (d_timeout) {
4,217!
504
      mysql_options(&d_db, MYSQL_OPT_READ_TIMEOUT, &d_timeout);
4,217✔
505
      mysql_options(&d_db, MYSQL_OPT_WRITE_TIMEOUT, &d_timeout);
4,217✔
506
      mysql_options(&d_db, MYSQL_OPT_CONNECT_TIMEOUT, &d_timeout);
4,217✔
507
    }
4,217✔
508
#endif
4,217✔
509

510
    if (d_setIsolation && (retry == 1))
4,217!
511
      mysql_options(&d_db, MYSQL_INIT_COMMAND, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED");
4,217✔
512

513
    mysql_options(&d_db, MYSQL_READ_DEFAULT_GROUP, d_group.c_str());
4,217✔
514

515
    if (!mysql_real_connect(&d_db, d_host.empty() ? nullptr : d_host.c_str(),
4,217!
516
                            d_user.empty() ? nullptr : d_user.c_str(),
4,217!
517
                            d_password.empty() ? nullptr : d_password.c_str(),
4,217!
518
                            d_database.empty() ? nullptr : d_database.c_str(),
4,217!
519
                            d_port,
4,217✔
520
                            d_msocket.empty() ? nullptr : d_msocket.c_str(),
4,217!
521
                            CLIENT_MULTI_RESULTS)) {
4,217✔
522

523
      if (retry == 0)
×
524
        throw sPerrorException("Unable to connect to database");
×
525
      --retry;
×
526
    }
×
527
    else {
4,217✔
528
      if (retry == 0) {
4,217!
529
        mysql_close(&d_db);
×
530
        throw sPerrorException("Please add '(gmysql-)innodb-read-committed=no' to your PowerDNS configuration, and reconsider your storage engine if it does not support transactions.");
×
531
      }
×
532
      retry = -1;
4,217✔
533
    }
4,217✔
534
  } while (retry >= 0);
4,217!
535
}
4,217✔
536

537
SMySQL::SMySQL(string database, string host, uint16_t port, string msocket, string user,
538
               string password, string group, bool setIsolation, unsigned int timeout, bool threadCleanup) :
539
  d_database(std::move(database)), d_host(std::move(host)), d_msocket(std::move(msocket)), d_user(std::move(user)), d_password(std::move(password)), d_group(std::move(group)), d_timeout(timeout), d_port(port), d_setIsolation(setIsolation), d_threadCleanup(threadCleanup)
540
{
4,216✔
541
  connect();
4,216✔
542
}
4,216✔
543

544
void SMySQL::setLog(bool state)
545
{
4,216✔
546
  s_dolog = state;
4,216✔
547
}
4,216✔
548

549
SMySQL::~SMySQL()
550
{
4,008✔
551
  mysql_close(&d_db);
4,008✔
552
}
4,008✔
553

554
SSqlException SMySQL::sPerrorException(const string& reason)
555
{
×
556
  return SSqlException(reason + string(": ERROR ") + std::to_string(mysql_errno(&d_db)) + " (" + string(mysql_sqlstate(&d_db)) + "): " + mysql_error(&d_db));
×
557
}
×
558

559
std::unique_ptr<SSqlStatement> SMySQL::prepare(const string& query, int nparams)
560
{
286,637✔
561
  return std::make_unique<SMySQLStatement>(query, s_dolog, nparams, &d_db);
286,637✔
562
}
286,637✔
563

564
void SMySQL::execute(const string& query)
565
{
4,591✔
566
  if (s_dolog)
4,591!
567
    g_log << Logger::Warning << "Query: " << query << endl;
×
568

569
  int err;
4,591✔
570
  if ((err = mysql_query(&d_db, query.c_str())))
4,591!
571
    throw sPerrorException("Failed to execute mysql_query '" + query + "' Err=" + std::to_string(err));
×
572
}
4,591✔
573

574
void SMySQL::startTransaction()
575
{
2,309✔
576
  execute("begin");
2,309✔
577
}
2,309✔
578

579
void SMySQL::commit()
580
{
2,037✔
581
  execute("commit");
2,037✔
582
}
2,037✔
583

584
void SMySQL::rollback()
585
{
245✔
586
  execute("rollback");
245✔
587
}
245✔
588

589
bool SMySQL::isConnectionUsable()
590
{
88,598✔
591
  bool usable = false;
88,598✔
592
  int sd = d_db.net.fd;
88,598✔
593
  bool wasNonBlocking = isNonBlocking(sd);
88,598✔
594

595
  if (!wasNonBlocking) {
88,598✔
596
    if (!setNonBlocking(sd)) {
88,597!
597
      return usable;
×
598
    }
×
599
  }
88,597✔
600

601
  usable = isTCPSocketUsable(sd);
88,598✔
602

603
  if (!wasNonBlocking) {
88,598✔
604
    if (!setBlocking(sd)) {
88,597!
605
      usable = false;
×
606
    }
×
607
  }
88,597✔
608

609
  return usable;
88,598✔
610
}
88,598✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc