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

PowerDNS / pdns / 20618548088

31 Dec 2025 12:00PM UTC coverage: 72.648% (-0.7%) from 73.336%
20618548088

Pull #16693

github

web-flow
Merge 3f7d9a75b into 65de281db
Pull Request #16693: auth: plumbing for structured logging

39009 of 65430 branches covered (59.62%)

Branch coverage included in aggregate %.

807 of 2400 new or added lines in 58 files covered. (33.63%)

200 existing lines in 39 files now uncovered.

129187 of 166092 relevant lines covered (77.78%)

5266744.49 hits per line

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

64.96
/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(std::shared_ptr<Logr::Logger> log, const string& query, bool dolog, int nparams, SPgSQL* db, unsigned int nstatement)
39
  {
130,129✔
40
    d_slog = log;
130,129✔
41
    d_query = query;
130,129✔
42
    d_dolog = dolog;
130,129✔
43
    d_parent = db;
130,129✔
44
    d_nparams = nparams;
130,129✔
45
    d_nstatement = nstatement;
130,129✔
46
  }
130,129✔
47

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

131
    nextResult();
331,911✔
132
    return this;
331,911✔
133
  }
331,911✔
134

135
  void nextResult()
136
  {
350,297✔
137
    if (d_res_set == nullptr)
350,297✔
138
      return;
18,386✔
139
    if (d_cur_set >= PQntuples(d_res_set)) {
331,911✔
140
      PQclear(d_res_set);
313,517✔
141
      d_res_set = nullptr;
313,517✔
142
      return;
313,517✔
143
    }
313,517✔
144
    if (PQftype(d_res_set, 0) == 1790) { // REFCURSOR
18,394!
NEW
145
      SLOG(g_log << Logger::Error << "Postgres query returned a REFCURSOR and we do not support those - see https://github.com/PowerDNS/pdns/pull/10259" << endl,
×
NEW
146
           d_slog->info(Logr::Error, "query returned a REFCURSOR which is not supported by PowerDNS", "more information", Logging::Loggable("https://github.com/PowerDNS/pdns/pull/10259")));
×
147
      PQclear(d_res_set);
×
148
      d_res_set = nullptr;
×
149
    }
×
150
    else {
18,394✔
151
      d_res = d_res_set;
18,394✔
152
      d_res_set = nullptr;
18,394✔
153
      d_resnum = PQntuples(d_res);
18,394✔
154
    }
18,394✔
155
  }
18,394✔
156

157
  bool hasNextRow() override
158
  {
296,140✔
159
    if (d_dolog && d_residx == d_resnum) {
296,140!
NEW
160
      SLOG(g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiff() << " us total to last row" << endl,
×
NEW
161
           d_slog->info(Logr::Warning, "all query results procesed", "microseconds", Logging::Loggable(d_dtime.udiff())));
×
UNCOV
162
    }
×
163

164
    return d_residx < d_resnum;
296,140✔
165
  }
296,140✔
166

167
  SSqlStatement* nextRow(row_t& row) override
168
  {
272,009✔
169
    int i;
272,009✔
170
    row.clear();
272,009✔
171
    if (d_residx >= d_resnum || !d_res)
272,009!
172
      return this;
×
173
    row.reserve(PQnfields(d_res));
272,009✔
174
    for (i = 0; i < PQnfields(d_res); i++) {
2,613,721✔
175
      if (PQgetisnull(d_res, d_residx, i)) {
2,341,712✔
176
        row.emplace_back("");
198,605✔
177
      }
198,605✔
178
      else if (PQftype(d_res, i) == 16) { // BOOLEAN
2,143,107✔
179
        char* val = PQgetvalue(d_res, d_residx, i);
166✔
180
        row.emplace_back(val[0] == 't' ? "1" : "0");
166✔
181
      }
166✔
182
      else {
2,142,941✔
183
        row.emplace_back(PQgetvalue(d_res, d_residx, i));
2,142,941✔
184
      }
2,142,941✔
185
    }
2,341,712✔
186
    d_residx++;
272,009✔
187
    if (d_residx >= d_resnum) {
272,009✔
188
      PQclear(d_res);
18,387✔
189
      d_res = nullptr;
18,387✔
190
      nextResult();
18,387✔
191
    }
18,387✔
192
    return this;
272,009✔
193
  }
272,009✔
194

195
  SSqlStatement* getResult(result_t& result) override
196
  {
1,248✔
197
    result.clear();
1,248✔
198
    if (d_res == nullptr)
1,248✔
199
      return this;
171✔
200
    result.reserve(d_resnum);
1,077✔
201
    row_t row;
1,077✔
202
    while (hasNextRow()) {
2,219✔
203
      nextRow(row);
1,142✔
204
      result.push_back(std::move(row));
1,142✔
205
    }
1,142✔
206
    return this;
1,077✔
207
  }
1,248✔
208

209
  SSqlStatement* reset() override
210
  {
454,221✔
211
    int i;
454,221✔
212
    if (d_res) {
454,221!
213
      PQclear(d_res);
×
214
    }
×
215
    if (d_res_set) {
454,221!
216
      PQclear(d_res_set);
×
217
    }
×
218
    d_res_set = nullptr;
454,221✔
219
    d_res = nullptr;
454,221✔
220
    d_paridx = d_residx = d_resnum = 0;
454,221✔
221
    if (paramValues) {
454,221✔
222
      for (i = 0; i < d_nparams; i++) {
2,722,854✔
223
        if (paramValues[i]) {
2,390,968✔
224
          delete[] paramValues[i];
2,228,030✔
225
        }
2,228,030✔
226
      }
2,390,968✔
227
    }
331,886✔
228
    delete[] paramValues;
454,221✔
229
    paramValues = nullptr;
454,221✔
230
    delete[] paramLengths;
454,221✔
231
    paramLengths = nullptr;
454,221✔
232
    return this;
454,221✔
233
  }
454,221✔
234

235
  const std::string& getQuery() override { return d_query; }
×
236

237
  ~SPgSQLStatement() override
238
  {
122,249✔
239
    releaseStatement();
122,249✔
240
  }
122,249✔
241

242
private:
243
  PGconn* d_db()
244
  {
346,171✔
245
    return d_parent->db();
346,171✔
246
  }
346,171✔
247

248
  void releaseStatement()
249
  {
122,396✔
250
    d_prepared = false;
122,396✔
251
    reset();
122,396✔
252
    if (!d_stmt.empty()) {
122,396✔
253
      string cmd = string("DEALLOCATE " + d_stmt);
6,931✔
254
      PGresult* res = PQexec(d_db(), cmd.c_str());
6,931✔
255
      PQclear(res);
6,931✔
256
      d_stmt.clear();
6,931✔
257
    }
6,931✔
258
  }
122,396✔
259

260
  void prepareStatement()
261
  {
2,722,888✔
262
    if (d_prepared)
2,722,888✔
263
      return;
2,715,550✔
264
    if (d_parent->usePrepared()) {
7,338✔
265
      // prepare a statement; name must be unique per session (using d_nstatement to ensure this).
266
      this->d_stmt = string("stmt") + std::to_string(d_nstatement);
7,334✔
267
      PGresult* res = PQprepare(d_db(), d_stmt.c_str(), d_query.c_str(), d_nparams, nullptr);
7,334✔
268
      ExecStatusType status = PQresultStatus(res);
7,334✔
269
      string errmsg(PQresultErrorMessage(res));
7,334✔
270
      PQclear(res);
7,334✔
271
      if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
7,334!
272
        releaseStatement();
×
273
        throw SSqlException("Fatal error during prePQpreparepare: " + d_query + string(": ") + errmsg);
×
274
      }
×
275
    }
7,334✔
276
    paramValues = nullptr;
7,338✔
277
    paramLengths = nullptr;
7,338✔
278
    d_cur_set = d_paridx = d_residx = d_resnum = 0;
7,338✔
279
    d_res = nullptr;
7,338✔
280
    d_res_set = nullptr;
7,338✔
281
    d_prepared = true;
7,338✔
282
  }
7,338✔
283

284
  void allocate()
285
  {
2,228,040✔
286
    if (paramValues != nullptr)
2,228,040✔
287
      return;
1,896,154✔
288
    paramValues = new char*[d_nparams];
331,886✔
289
    paramLengths = new int[d_nparams];
331,886✔
290
    memset(paramValues, 0, sizeof(char*) * d_nparams);
331,886✔
291
    memset(paramLengths, 0, sizeof(int) * d_nparams);
331,886✔
292
  }
331,886✔
293

294
  string d_query;
295
  string d_stmt;
296
  SPgSQL* d_parent;
297
  PGresult* d_res_set{nullptr};
298
  PGresult* d_res{nullptr};
299
  bool d_dolog;
300
  std::shared_ptr<Logr::Logger> d_slog;
301
  DTime d_dtime; // only used if d_dolog is set
302
  bool d_prepared{false};
303
  int d_nparams;
304
  int d_paridx{0};
305
  char** paramValues{nullptr};
306
  int* paramLengths{nullptr};
307
  int d_residx{0};
308
  int d_resnum{0};
309
  int d_cur_set{0};
310
  unsigned int d_nstatement;
311
};
312

313
bool SPgSQL::s_dolog;
314

315
static string escapeForPQparam(const string& v)
316
{
3,322✔
317
  string ret = v;
3,322✔
318
  boost::replace_all(ret, "\\", "\\\\");
3,322✔
319
  boost::replace_all(ret, "'", "\\'");
3,322✔
320

321
  return string("'") + ret + string("'");
3,322✔
322
}
3,322✔
323

324
SPgSQL::SPgSQL(std::shared_ptr<Logr::Logger> log, const string& database, const string& host, const string& port, const string& user,
325
               const string& password, const string& extra_connection_parameters, const bool use_prepared)
326
{
1,912✔
327
  d_slog = log;
1,912✔
328
  d_db = nullptr;
1,912✔
329
  d_in_trx = false;
1,912✔
330
  d_connectstr = "";
1,912✔
331
  d_nstatements = 0;
1,912✔
332

333
  if (!database.empty())
1,912!
334
    d_connectstr += "dbname=" + escapeForPQparam(database);
1,912✔
335

336
  if (!user.empty())
1,912✔
337
    d_connectstr += " user=" + escapeForPQparam(user);
1,410✔
338

339
  if (!host.empty())
1,912!
340
    d_connectstr += " host=" + escapeForPQparam(host);
×
341

342
  if (!port.empty())
1,912!
343
    d_connectstr += " port=" + escapeForPQparam(port);
×
344

345
  if (!extra_connection_parameters.empty())
1,912!
346
    d_connectstr += " " + extra_connection_parameters;
×
347

348
  d_connectlogstr = d_connectstr;
1,912✔
349

350
  if (!password.empty()) {
1,912!
351
    d_connectlogstr += " password=<HIDDEN>";
×
352
    d_connectstr += " password=" + escapeForPQparam(password);
×
353
  }
×
354

355
  d_use_prepared = use_prepared;
1,912✔
356

357
  d_db = PQconnectdb(d_connectstr.c_str());
1,912✔
358

359
  if (!d_db || PQstatus(d_db) == CONNECTION_BAD) {
1,914!
360
    try {
×
361
      throw sPerrorException("Unable to connect to database, connect string: " + d_connectlogstr);
×
362
    }
×
363
    catch (...) {
×
364
      if (d_db)
×
365
        PQfinish(d_db);
×
366
      d_db = 0;
×
367
      throw;
×
368
    }
×
369
  }
×
370
}
1,912✔
371

372
void SPgSQL::setLog(bool state)
373
{
1,914✔
374
  s_dolog = state;
1,914✔
375
}
1,914✔
376

377
SPgSQL::~SPgSQL()
378
{
1,801✔
379
  PQfinish(d_db);
1,801✔
380
}
1,801✔
381

382
SSqlException SPgSQL::sPerrorException(const string& reason)
383
{
×
384
  return SSqlException(reason + string(": ") + (d_db ? PQerrorMessage(d_db) : "no connection"));
×
385
}
×
386

387
void SPgSQL::execute(const string& query)
388
{
1,123✔
389
  PGresult* res = PQexec(d_db, query.c_str());
1,123✔
390
  ExecStatusType status = PQresultStatus(res);
1,123✔
391
  string errmsg(PQresultErrorMessage(res));
1,123✔
392
  PQclear(res);
1,123✔
393
  if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
1,123!
394
    throw sPerrorException("Fatal error during query: " + errmsg);
×
395
  }
×
396
}
1,123✔
397

398
std::unique_ptr<SSqlStatement> SPgSQL::prepare(const string& query, int nparams)
399
{
130,150✔
400
  d_nstatements++;
130,150✔
401
  return std::make_unique<SPgSQLStatement>(d_slog, query, s_dolog, nparams, this, d_nstatements);
130,150✔
402
}
130,150✔
403

404
void SPgSQL::startTransaction()
405
{
569✔
406
  execute("begin");
569✔
407
  d_in_trx = true;
569✔
408
}
569✔
409

410
void SPgSQL::commit()
411
{
531✔
412
  execute("commit");
531✔
413
  d_in_trx = false;
531✔
414
}
531✔
415

416
void SPgSQL::rollback()
417
{
23✔
418
  execute("rollback");
23✔
419
  d_in_trx = false;
23✔
420
}
23✔
421

422
bool SPgSQL::isConnectionUsable()
423
{
23,287✔
424
  if (PQstatus(d_db) != CONNECTION_OK) {
23,287!
425
    return false;
×
426
  }
×
427

428
  bool usable = false;
23,287✔
429
  int sd = PQsocket(d_db);
23,287✔
430
  bool wasNonBlocking = isNonBlocking(sd);
23,287✔
431

432
  if (!wasNonBlocking) {
23,287!
433
    if (!setNonBlocking(sd)) {
×
434
      return usable;
×
435
    }
×
436
  }
×
437

438
  usable = isTCPSocketUsable(sd);
23,287✔
439

440
  if (!wasNonBlocking) {
23,287!
441
    if (!setBlocking(sd)) {
×
442
      usable = false;
×
443
    }
×
444
  }
×
445

446
  return usable;
23,287✔
447
}
23,287✔
448

449
void SPgSQL::reconnect()
450
{
×
451
  PQreset(d_db);
×
452
}
×
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

© 2026 Coveralls, Inc