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

PowerDNS / pdns / 17611467588

10 Sep 2025 10:53AM UTC coverage: 66.01% (+0.03%) from 65.978%
17611467588

Pull #16108

github

web-flow
Merge b6eb1a724 into 29382c4af
Pull Request #16108: dnsdist: implement simple packet shuffle in cache

42255 of 92634 branches covered (45.62%)

Branch coverage included in aggregate %.

9 of 120 new or added lines in 6 files covered. (7.5%)

12 existing lines in 5 files now uncovered.

128490 of 166031 relevant lines covered (77.39%)

5519579.39 hits per line

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

68.18
/modules/gpgsqlbackend/spgsql.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 <string>
27
#include "spgsql.hh"
28
#include <sys/time.h>
29
#include <iostream>
30
#include "pdns/logger.hh"
31
#include "pdns/dns.hh"
32
#include "pdns/namespaces.hh"
33
#include <algorithm>
34

35
class SPgSQLStatement : public SSqlStatement
36
{
37
public:
38
  SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db, unsigned int nstatement)
39
  {
128,048✔
40
    d_query = query;
128,048✔
41
    d_dolog = dolog;
128,048✔
42
    d_parent = db;
128,048✔
43
    d_nparams = nparams;
128,048✔
44
    d_nstatement = nstatement;
128,048✔
45
  }
128,048✔
46

47
  SSqlStatement* bind(const string& name, bool value) override { return bind(name, string(value ? "t" : "f")); }
529,374✔
48
  SSqlStatement* bind(const string& name, int value) override { return bind(name, std::to_string(value)); }
547,603✔
49
  SSqlStatement* bind(const string& name, uint32_t value) override { return bind(name, std::to_string(value)); }
223,790✔
50
  SSqlStatement* bind(const string& name, long value) override { return bind(name, std::to_string(value)); }
81✔
51
  SSqlStatement* bind(const string& name, unsigned long value) override { return bind(name, std::to_string(value)); }
11✔
52
  SSqlStatement* bind(const string& name, long long value) override { return bind(name, std::to_string(value)); }
×
53
  SSqlStatement* bind(const string& name, unsigned long long value) override { return bind(name, std::to_string(value)); }
×
54
  SSqlStatement* bind(const string& /* name */, const std::string& value) override
55
  {
2,226,414✔
56
    prepareStatement();
2,226,414✔
57
    allocate();
2,226,414✔
58
    if (d_paridx >= d_nparams) {
2,226,414!
59
      releaseStatement();
×
60
      throw SSqlException("Attempt to bind more parameters than query has: " + d_query);
×
61
    }
×
62
    paramValues[d_paridx] = new char[value.size() + 1];
2,226,414✔
63
    memset(paramValues[d_paridx], 0, sizeof(char) * (value.size() + 1));
2,226,414✔
64
    value.copy(paramValues[d_paridx], value.size());
2,226,414✔
65
    paramLengths[d_paridx] = value.size();
2,226,414✔
66
    d_paridx++;
2,226,414✔
67
    return this;
2,226,414✔
68
  }
2,226,414✔
69
  SSqlStatement* bindNull(const string& /* name */) override
70
  {
162,882✔
71
    prepareStatement();
162,882✔
72
    d_paridx++;
162,882✔
73
    return this;
162,882✔
74
  } // these are set null in allocate()
162,882✔
75
  SSqlStatement* execute() override
76
  {
331,257✔
77
    prepareStatement();
331,257✔
78
    if (d_dolog) {
331,257!
79
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": Statement: " << d_query << endl;
×
80
      if (d_paridx) {
×
81
        // Log message is similar, but not exactly the same as the postgres server log.
82
        std::stringstream log_message;
×
83
        log_message << "Query " << ((long)(void*)this) << ": Parameters: ";
×
84
        for (int i = 0; i < d_paridx; i++) {
×
85
          if (i != 0) {
×
86
            log_message << ", ";
×
87
          }
×
88
          log_message << "$" << (i + 1) << " = ";
×
89
          if (paramValues[i] == nullptr) {
×
90
            log_message << "NULL";
×
91
          }
×
92
          else {
×
93
            log_message << "'" << paramValues[i] << "'";
×
94
          }
×
95
        }
×
96
        g_log << Logger::Warning << log_message.str() << endl;
×
97
      }
×
98
      d_dtime.set();
×
99
    }
×
100
    if (!d_stmt.empty()) {
331,257!
101
      d_res_set = PQexecPrepared(d_db(), d_stmt.c_str(), d_nparams, paramValues, paramLengths, nullptr, 0);
331,257✔
102
    }
331,257✔
UNCOV
103
    else {
×
UNCOV
104
      d_res_set = PQexecParams(d_db(), d_query.c_str(), d_nparams, nullptr, paramValues, paramLengths, nullptr, 0);
×
UNCOV
105
    }
×
106
    ExecStatusType status = PQresultStatus(d_res_set);
331,257✔
107
    if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
331,257!
108
      string errmsg(PQresultErrorMessage(d_res_set));
×
109
      releaseStatement();
×
110
      throw SSqlException("Fatal error during query: " + d_query + string(": ") + errmsg);
×
111
    }
×
112
    d_cur_set = 0;
331,257✔
113
    if (d_dolog) {
331,257!
114
      auto diff = d_dtime.udiffNoReset();
×
115
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << diff << " us to execute" << endl;
×
116
    }
×
117

118
    nextResult();
331,257✔
119
    return this;
331,257✔
120
  }
331,257✔
121

122
  void nextResult()
123
  {
349,429✔
124
    if (d_res_set == nullptr)
349,429✔
125
      return;
18,171✔
126
    if (d_cur_set >= PQntuples(d_res_set)) {
331,258✔
127
      PQclear(d_res_set);
313,083✔
128
      d_res_set = nullptr;
313,083✔
129
      return;
313,083✔
130
    }
313,083✔
131
    if (PQftype(d_res_set, 0) == 1790) { // REFCURSOR
18,175!
132
      g_log << Logger::Error << "Postgres query returned a REFCURSOR and we do not support those - see https://github.com/PowerDNS/pdns/pull/10259" << endl;
×
133
      PQclear(d_res_set);
×
134
      d_res_set = nullptr;
×
135
    }
×
136
    else {
18,175✔
137
      d_res = d_res_set;
18,175✔
138
      d_res_set = nullptr;
18,175✔
139
      d_resnum = PQntuples(d_res);
18,175✔
140
    }
18,175✔
141
  }
18,175✔
142

143
  bool hasNextRow() override
144
  {
295,265✔
145
    if (d_dolog && d_residx == d_resnum) {
295,265!
146
      g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiff() << " us total to last row" << endl;
×
147
    }
×
148

149
    return d_residx < d_resnum;
295,265✔
150
  }
295,265✔
151

152
  SSqlStatement* nextRow(row_t& row) override
153
  {
271,633✔
154
    int i;
271,633✔
155
    row.clear();
271,633✔
156
    if (d_residx >= d_resnum || !d_res)
271,633!
157
      return this;
×
158
    row.reserve(PQnfields(d_res));
271,633✔
159
    for (i = 0; i < PQnfields(d_res); i++) {
2,610,535✔
160
      if (PQgetisnull(d_res, d_residx, i)) {
2,338,902✔
161
        row.emplace_back("");
198,207✔
162
      }
198,207✔
163
      else if (PQftype(d_res, i) == 16) { // BOOLEAN
2,140,695✔
164
        char* val = PQgetvalue(d_res, d_residx, i);
166✔
165
        row.emplace_back(val[0] == 't' ? "1" : "0");
166✔
166
      }
166✔
167
      else {
2,140,529✔
168
        row.emplace_back(PQgetvalue(d_res, d_residx, i));
2,140,529✔
169
      }
2,140,529✔
170
    }
2,338,902✔
171
    d_residx++;
271,633✔
172
    if (d_residx >= d_resnum) {
271,633✔
173
      PQclear(d_res);
18,171✔
174
      d_res = nullptr;
18,171✔
175
      nextResult();
18,171✔
176
    }
18,171✔
177
    return this;
271,633✔
178
  }
271,633✔
179

180
  SSqlStatement* getResult(result_t& result) override
181
  {
1,193✔
182
    result.clear();
1,193✔
183
    if (d_res == nullptr)
1,193✔
184
      return this;
163✔
185
    result.reserve(d_resnum);
1,030✔
186
    row_t row;
1,030✔
187
    while (hasNextRow()) {
2,125✔
188
      nextRow(row);
1,095✔
189
      result.push_back(std::move(row));
1,095✔
190
    }
1,095✔
191
    return this;
1,030✔
192
  }
1,193✔
193

194
  SSqlStatement* reset() override
195
  {
451,792✔
196
    int i;
451,792✔
197
    if (d_res) {
451,792!
198
      PQclear(d_res);
×
199
    }
×
200
    if (d_res_set) {
451,792!
201
      PQclear(d_res_set);
×
202
    }
×
203
    d_res_set = nullptr;
451,792✔
204
    d_res = nullptr;
451,792✔
205
    d_paridx = d_residx = d_resnum = 0;
451,792✔
206
    if (paramValues) {
451,792✔
207
      for (i = 0; i < d_nparams; i++) {
2,720,527✔
208
        if (paramValues[i]) {
2,389,294✔
209
          delete[] paramValues[i];
2,226,412✔
210
        }
2,226,412✔
211
      }
2,389,294✔
212
    }
331,233✔
213
    delete[] paramValues;
451,792✔
214
    paramValues = nullptr;
451,792✔
215
    delete[] paramLengths;
451,792✔
216
    paramLengths = nullptr;
451,792✔
217
    return this;
451,792✔
218
  }
451,792✔
219

220
  const std::string& getQuery() override { return d_query; }
×
221

222
  ~SPgSQLStatement() override
223
  {
120,290✔
224
    releaseStatement();
120,290✔
225
  }
120,290✔
226

227
private:
228
  PGconn* d_db()
229
  {
344,838✔
230
    return d_parent->db();
344,838✔
231
  }
344,838✔
232

233
  void releaseStatement()
234
  {
120,524✔
235
    d_prepared = false;
120,524✔
236
    reset();
120,524✔
237
    if (!d_stmt.empty()) {
120,524✔
238
      string cmd = string("DEALLOCATE " + d_stmt);
6,588✔
239
      PGresult* res = PQexec(d_db(), cmd.c_str());
6,588✔
240
      PQclear(res);
6,588✔
241
      d_stmt.clear();
6,588✔
242
    }
6,588✔
243
  }
120,524✔
244

245
  void prepareStatement()
246
  {
2,720,551✔
247
    if (d_prepared)
2,720,551✔
248
      return;
2,713,560✔
249
    if (d_parent->usePrepared()) {
6,992✔
250
      // prepare a statement; name must be unique per session (using d_nstatement to ensure this).
251
      this->d_stmt = string("stmt") + std::to_string(d_nstatement);
6,992✔
252
      PGresult* res = PQprepare(d_db(), d_stmt.c_str(), d_query.c_str(), d_nparams, nullptr);
6,992✔
253
      ExecStatusType status = PQresultStatus(res);
6,992✔
254
      string errmsg(PQresultErrorMessage(res));
6,992✔
255
      PQclear(res);
6,992✔
256
      if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
6,992!
257
        releaseStatement();
×
258
        throw SSqlException("Fatal error during prePQpreparepare: " + d_query + string(": ") + errmsg);
×
259
      }
×
260
    }
6,992✔
261
    paramValues = nullptr;
6,991✔
262
    paramLengths = nullptr;
6,991✔
263
    d_cur_set = d_paridx = d_residx = d_resnum = 0;
6,991✔
264
    d_res = nullptr;
6,991✔
265
    d_res_set = nullptr;
6,991✔
266
    d_prepared = true;
6,991✔
267
  }
6,991✔
268

269
  void allocate()
270
  {
2,226,414✔
271
    if (paramValues != nullptr)
2,226,414✔
272
      return;
1,895,182✔
273
    paramValues = new char*[d_nparams];
331,232✔
274
    paramLengths = new int[d_nparams];
331,232✔
275
    memset(paramValues, 0, sizeof(char*) * d_nparams);
331,232✔
276
    memset(paramLengths, 0, sizeof(int) * d_nparams);
331,232✔
277
  }
331,232✔
278

279
  string d_query;
280
  string d_stmt;
281
  SPgSQL* d_parent;
282
  PGresult* d_res_set{nullptr};
283
  PGresult* d_res{nullptr};
284
  bool d_dolog;
285
  DTime d_dtime; // only used if d_dolog is set
286
  bool d_prepared{false};
287
  int d_nparams;
288
  int d_paridx{0};
289
  char** paramValues{nullptr};
290
  int* paramLengths{nullptr};
291
  int d_residx{0};
292
  int d_resnum{0};
293
  int d_cur_set{0};
294
  unsigned int d_nstatement;
295
};
296

297
bool SPgSQL::s_dolog;
298

299
static string escapeForPQparam(const string& v)
300
{
3,297✔
301
  string ret = v;
3,297✔
302
  boost::replace_all(ret, "\\", "\\\\");
3,297✔
303
  boost::replace_all(ret, "'", "\\'");
3,297✔
304

305
  return string("'") + ret + string("'");
3,297✔
306
}
3,297✔
307

308
SPgSQL::SPgSQL(const string& database, const string& host, const string& port, const string& user,
309
               const string& password, const string& extra_connection_parameters, const bool use_prepared)
310
{
1,884✔
311
  d_db = nullptr;
1,884✔
312
  d_in_trx = false;
1,884✔
313
  d_connectstr = "";
1,884✔
314
  d_nstatements = 0;
1,884✔
315

316
  if (!database.empty())
1,884✔
317
    d_connectstr += "dbname=" + escapeForPQparam(database);
1,881✔
318

319
  if (!user.empty())
1,884✔
320
    d_connectstr += " user=" + escapeForPQparam(user);
1,414✔
321

322
  if (!host.empty())
1,884!
323
    d_connectstr += " host=" + escapeForPQparam(host);
×
324

325
  if (!port.empty())
1,884!
326
    d_connectstr += " port=" + escapeForPQparam(port);
×
327

328
  if (!extra_connection_parameters.empty())
1,884!
329
    d_connectstr += " " + extra_connection_parameters;
×
330

331
  d_connectlogstr = d_connectstr;
1,884✔
332

333
  if (!password.empty()) {
1,884!
334
    d_connectlogstr += " password=<HIDDEN>";
×
335
    d_connectstr += " password=" + escapeForPQparam(password);
×
336
  }
×
337

338
  d_use_prepared = use_prepared;
1,884✔
339

340
  d_db = PQconnectdb(d_connectstr.c_str());
1,884✔
341

342
  if (!d_db || PQstatus(d_db) == CONNECTION_BAD) {
1,884!
343
    try {
×
344
      throw sPerrorException("Unable to connect to database, connect string: " + d_connectlogstr);
×
345
    }
×
346
    catch (...) {
×
347
      if (d_db)
×
348
        PQfinish(d_db);
×
349
      d_db = 0;
×
350
      throw;
×
351
    }
×
352
  }
×
353
}
1,884✔
354

355
void SPgSQL::setLog(bool state)
356
{
1,883✔
357
  s_dolog = state;
1,883✔
358
}
1,883✔
359

360
SPgSQL::~SPgSQL()
361
{
1,779✔
362
  PQfinish(d_db);
1,779✔
363
}
1,779✔
364

365
SSqlException SPgSQL::sPerrorException(const string& reason)
366
{
×
367
  return SSqlException(reason + string(": ") + (d_db ? PQerrorMessage(d_db) : "no connection"));
×
368
}
×
369

370
void SPgSQL::execute(const string& query)
371
{
1,083✔
372
  PGresult* res = PQexec(d_db, query.c_str());
1,083✔
373
  ExecStatusType status = PQresultStatus(res);
1,083✔
374
  string errmsg(PQresultErrorMessage(res));
1,083✔
375
  PQclear(res);
1,083✔
376
  if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
1,083!
377
    throw sPerrorException("Fatal error during query: " + errmsg);
×
378
  }
×
379
}
1,083✔
380

381
std::unique_ptr<SSqlStatement> SPgSQL::prepare(const string& query, int nparams)
382
{
128,096✔
383
  d_nstatements++;
128,096✔
384
  return std::make_unique<SPgSQLStatement>(query, s_dolog, nparams, this, d_nstatements);
128,096✔
385
}
128,096✔
386

387
void SPgSQL::startTransaction()
388
{
549✔
389
  execute("begin");
549✔
390
  d_in_trx = true;
549✔
391
}
549✔
392

393
void SPgSQL::commit()
394
{
517✔
395
  execute("commit");
517✔
396
  d_in_trx = false;
517✔
397
}
517✔
398

399
void SPgSQL::rollback()
400
{
17✔
401
  execute("rollback");
17✔
402
  d_in_trx = false;
17✔
403
}
17✔
404

405
bool SPgSQL::isConnectionUsable()
406
{
22,920✔
407
  if (PQstatus(d_db) != CONNECTION_OK) {
22,920!
408
    return false;
×
409
  }
×
410

411
  bool usable = false;
22,920✔
412
  int sd = PQsocket(d_db);
22,920✔
413
  bool wasNonBlocking = isNonBlocking(sd);
22,920✔
414

415
  if (!wasNonBlocking) {
22,920!
416
    if (!setNonBlocking(sd)) {
×
417
      return usable;
×
418
    }
×
419
  }
×
420

421
  usable = isTCPSocketUsable(sd);
22,920✔
422

423
  if (!wasNonBlocking) {
22,920!
424
    if (!setBlocking(sd)) {
×
425
      usable = false;
×
426
    }
×
427
  }
×
428

429
  return usable;
22,920✔
430
}
22,920✔
431

432
void SPgSQL::reconnect()
433
{
×
434
  PQreset(d_db);
×
435
}
×
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