GNUnet  0.11.x
plugin_namestore_postgres.c
Go to the documentation of this file.
1 /*
2  * This file is part of GNUnet
3  * Copyright (C) 2009-2013, 2016-2018 GNUnet e.V.
4  *
5  * GNUnet is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Affero General Public License as published
7  * by the Free Software Foundation, either version 3 of the License,
8  * or (at your option) any later version.
9  *
10  * GNUnet is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 
18  SPDX-License-Identifier: AGPL3.0-or-later
19  */
20 
26 #include "platform.h"
29 #include "gnunet_gnsrecord_lib.h"
30 #include "gnunet_pq_lib.h"
31 #include "namestore.h"
32 
33 
34 #define LOG(kind, ...) GNUNET_log_from (kind, "namestore-postgres", __VA_ARGS__)
35 
36 
40 struct Plugin
41 {
45  const struct GNUNET_CONFIGURATION_Handle *cfg;
46 
50  struct GNUNET_PQ_Context *dbh;
51 };
52 
53 
62 static int
64 {
65  struct GNUNET_PQ_ExecuteStatement es_temporary =
67  "CREATE TEMPORARY TABLE IF NOT EXISTS ns098records ("
68  " seq BIGSERIAL PRIMARY KEY,"
69  " zone_private_key BYTEA NOT NULL DEFAULT '',"
70  " pkey BYTEA DEFAULT '',"
71  " rvalue BYTEA NOT NULL DEFAULT '',"
72  " record_count INTEGER NOT NULL DEFAULT 0,"
73  " record_data BYTEA NOT NULL DEFAULT '',"
74  " label TEXT NOT NULL DEFAULT '',"
75  " CONSTRAINT zl UNIQUE (zone_private_key,label)"
76  ")"
77  "WITH OIDS");
78  struct GNUNET_PQ_ExecuteStatement es_default =
79  GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns098records ("
80  " seq BIGSERIAL PRIMARY KEY,"
81  " zone_private_key BYTEA NOT NULL DEFAULT '',"
82  " pkey BYTEA DEFAULT '',"
83  " rvalue BYTEA NOT NULL DEFAULT '',"
84  " record_count INTEGER NOT NULL DEFAULT 0,"
85  " record_data BYTEA NOT NULL DEFAULT '',"
86  " label TEXT NOT NULL DEFAULT '',"
87  " CONSTRAINT zl UNIQUE (zone_private_key,label)"
88  ")"
89  "WITH OIDS");
90  const struct GNUNET_PQ_ExecuteStatement *cr;
92 
93  if (GNUNET_YES ==
95  "namestore-postgres",
96  "TEMPORARY_TABLE"))
97  {
98  cr = &es_temporary;
99  }
100  else
101  {
102  cr = &es_default;
103  }
104 
105  if (GNUNET_YES ==
107  "namestore-postgres",
108  "ASYNC_COMMIT"))
109  sc = GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off");
110 
111  {
112  struct GNUNET_PQ_ExecuteStatement es[] = {
113  *cr,
114  GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_reverse "
115  "ON ns098records (zone_private_key,pkey)"),
116  GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_iter "
117  "ON ns098records (zone_private_key,seq)"),
118  GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_label "
119  "ON ns098records (label)"),
120  GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS zone_label "
121  "ON ns098records (zone_private_key,label)"),
122  sc,
124  };
125  struct GNUNET_PQ_PreparedStatement ps[] = {
126  GNUNET_PQ_make_prepare ("store_records",
127  "INSERT INTO ns098records"
128  " (zone_private_key, pkey, rvalue, record_count, record_data, label)"
129  " VALUES ($1, $2, $3, $4, $5, $6)"
130  " ON CONFLICT ON CONSTRAINT zl"
131  " DO UPDATE"
132  " SET pkey=$2,rvalue=$3,record_count=$4,record_data=$5"
133  " WHERE ns098records.zone_private_key = $1"
134  " AND ns098records.label = $6",
135  6),
136  GNUNET_PQ_make_prepare ("delete_records",
137  "DELETE FROM ns098records "
138  "WHERE zone_private_key=$1 AND label=$2",
139  2),
140  GNUNET_PQ_make_prepare ("zone_to_name",
141  "SELECT seq,record_count,record_data,label FROM ns098records"
142  " WHERE zone_private_key=$1 AND pkey=$2",
143  2),
144  GNUNET_PQ_make_prepare ("iterate_zone",
145  "SELECT seq,record_count,record_data,label FROM ns098records "
146  "WHERE zone_private_key=$1 AND seq > $2 ORDER BY seq ASC LIMIT $3",
147  3),
148  GNUNET_PQ_make_prepare ("iterate_all_zones",
149  "SELECT seq,record_count,record_data,label,zone_private_key"
150  " FROM ns098records WHERE seq > $1 ORDER BY seq ASC LIMIT $2",
151  2),
152  GNUNET_PQ_make_prepare ("lookup_label",
153  "SELECT seq,record_count,record_data,label "
154  "FROM ns098records WHERE zone_private_key=$1 AND label=$2",
155  2),
157  };
158 
159  plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
160  "namestore-postgres",
161  NULL,
162  es,
163  ps);
164  }
165  if (NULL == plugin->dbh)
166  return GNUNET_SYSERR;
167  return GNUNET_OK;
168 }
169 
170 
182 static int
184  const struct
186  const char *label,
187  unsigned int rd_count,
188  const struct GNUNET_GNSRECORD_Data *rd)
189 {
190  struct Plugin *plugin = cls;
191  struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
192  uint64_t rvalue;
193  uint32_t rd_count32 = (uint32_t) rd_count;
194  ssize_t data_size;
195 
196  memset (&pkey,
197  0,
198  sizeof(pkey));
199  for (unsigned int i = 0; i < rd_count; i++)
200  if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
201  {
202  GNUNET_break (sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey) ==
203  rd[i].data_size);
204  GNUNET_memcpy (&pkey,
205  rd[i].data,
206  rd[i].data_size);
207  break;
208  }
210  UINT64_MAX);
211  data_size = GNUNET_GNSRECORD_records_get_size (rd_count,
212  rd);
213  if (data_size < 0)
214  {
215  GNUNET_break (0);
216  return GNUNET_SYSERR;
217  }
218  if (data_size >= UINT16_MAX)
219  {
220  GNUNET_break (0);
221  return GNUNET_SYSERR;
222  }
223  /* if record set is empty, delete existing records */
224  if (0 == rd_count)
225  {
226  struct GNUNET_PQ_QueryParam params[] = {
230  };
232 
234  "delete_records",
235  params);
238  {
239  GNUNET_break (0);
240  return GNUNET_SYSERR;
241  }
243  "postgres",
244  "Record deleted\n");
245  return GNUNET_OK;
246  }
247  /* otherwise, UPSERT (i.e. UPDATE if exists, otherwise INSERT) */
248  {
249  char data[data_size];
250  struct GNUNET_PQ_QueryParam params[] = {
254  GNUNET_PQ_query_param_uint32 (&rd_count32),
255  GNUNET_PQ_query_param_fixed_size (data, data_size),
258  };
260  ssize_t ret;
261 
262  ret = GNUNET_GNSRECORD_records_serialize (rd_count,
263  rd,
264  data_size,
265  data);
266  if ((ret < 0) ||
267  (data_size != ret))
268  {
269  GNUNET_break (0);
270  return GNUNET_SYSERR;
271  }
272 
274  "store_records",
275  params);
277  return GNUNET_SYSERR;
278  }
279  return GNUNET_OK;
280 }
281 
282 
287 {
292 
296  void *iter_cls;
297 
302 
307  uint64_t limit;
308 };
309 
310 
319 static void
321  PGresult *res,
322  unsigned int num_results)
323 {
324  struct ParserContext *pc = cls;
325 
326  if (NULL == pc->iter)
327  return; /* no need to do more work */
328  for (unsigned int i = 0; i < num_results; i++)
329  {
330  uint64_t serial;
331  void *data;
332  size_t data_size;
333  uint32_t record_count;
334  char *label;
336  struct GNUNET_PQ_ResultSpec rs_with_zone[] = {
337  GNUNET_PQ_result_spec_uint64 ("seq", &serial),
338  GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
339  GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
340  GNUNET_PQ_result_spec_string ("label", &label),
341  GNUNET_PQ_result_spec_auto_from_type ("zone_private_key", &zk),
343  };
344  struct GNUNET_PQ_ResultSpec rs_without_zone[] = {
345  GNUNET_PQ_result_spec_uint64 ("seq", &serial),
346  GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
347  GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
348  GNUNET_PQ_result_spec_string ("label", &label),
350  };
351  struct GNUNET_PQ_ResultSpec *rs;
352 
353  rs = (NULL == pc->zone_key) ? rs_with_zone : rs_without_zone;
354  if (GNUNET_YES !=
356  rs,
357  i))
358  {
359  GNUNET_break (0);
360  return;
361  }
362 
363  if (record_count > 64 * 1024)
364  {
365  /* sanity check, don't stack allocate far too much just
366  because database might contain a large value here */
367  GNUNET_break (0);
369  return;
370  }
371 
372  {
373  struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (record_count)];
374 
375  GNUNET_assert (0 != serial);
376  if (GNUNET_OK !=
378  data,
379  record_count,
380  rd))
381  {
382  GNUNET_break (0);
384  return;
385  }
386  pc->iter (pc->iter_cls,
387  serial,
388  (NULL == pc->zone_key) ? &zk : pc->zone_key,
389  label,
390  record_count,
391  rd);
392  }
394  }
395  pc->limit -= num_results;
396 }
397 
398 
409 static int
411  const struct
413  const char *label,
415  void *iter_cls)
416 {
417  struct Plugin *plugin = cls;
418  struct GNUNET_PQ_QueryParam params[] = {
422  };
423  struct ParserContext pc;
425 
426  if (NULL == zone)
427  {
428  GNUNET_break (0);
429  return GNUNET_SYSERR;
430  }
431  pc.iter = iter;
432  pc.iter_cls = iter_cls;
433  pc.zone_key = zone;
435  "lookup_label",
436  params,
438  &pc);
439  if (res < 0)
440  return GNUNET_SYSERR;
442  return GNUNET_NO;
443  return GNUNET_OK;
444 }
445 
446 
459 static int
461  const struct
463  uint64_t serial,
464  uint64_t limit,
466  void *iter_cls)
467 {
468  struct Plugin *plugin = cls;
470  struct ParserContext pc;
471 
472  pc.iter = iter;
473  pc.iter_cls = iter_cls;
474  pc.zone_key = zone;
475  pc.limit = limit;
476  if (NULL == zone)
477  {
478  struct GNUNET_PQ_QueryParam params_without_zone[] = {
482  };
483 
485  "iterate_all_zones",
486  params_without_zone,
488  &pc);
489  }
490  else
491  {
492  struct GNUNET_PQ_QueryParam params_with_zone[] = {
497  };
498 
500  "iterate_zone",
501  params_with_zone,
503  &pc);
504  }
505  if (res < 0)
506  return GNUNET_SYSERR;
507 
509  (pc.limit > 0))
510  return GNUNET_NO;
511  return GNUNET_OK;
512 }
513 
514 
526 static int
528  const struct
530  const struct
531  GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
533  void *iter_cls)
534 {
535  struct Plugin *plugin = cls;
536  struct GNUNET_PQ_QueryParam params[] = {
540  };
542  struct ParserContext pc;
543 
544  pc.iter = iter;
545  pc.iter_cls = iter_cls;
546  pc.zone_key = zone;
548  "zone_to_name",
549  params,
551  &pc);
552  if (res < 0)
553  return GNUNET_SYSERR;
554  return GNUNET_OK;
555 }
556 
557 
564 static void
566 {
567  GNUNET_PQ_disconnect (plugin->dbh);
568  plugin->dbh = NULL;
569 }
570 
571 
578 void *
580 {
581  static struct Plugin plugin;
582  const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
584 
585  if (NULL != plugin.cfg)
586  return NULL; /* can only initialize once! */
587  memset (&plugin, 0, sizeof(struct Plugin));
588  plugin.cfg = cfg;
589  if (GNUNET_OK != database_setup (&plugin))
590  {
591  database_shutdown (&plugin);
592  return NULL;
593  }
595  api->cls = &plugin;
601  "Postgres namestore plugin running\n");
602  return api;
603 }
604 
605 
612 void *
614 {
616  struct Plugin *plugin = api->cls;
617 
618  database_shutdown (plugin);
619  plugin->cfg = NULL;
620  GNUNET_free (api);
622  "Postgres namestore plugin is finished\n");
623  return NULL;
624 }
625 
626 
627 /* end of plugin_namestore_postgres.c */
void GNUNET_PQ_disconnect(struct GNUNET_PQ_Context *db)
Disconnect from the database, destroying the prepared statements and releasing other associated resou...
Definition: pq_connect.c:513
GNUNET_NETWORK_STRUCT_END ssize_t GNUNET_GNSRECORD_records_get_size(unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd)
Calculate how many bytes we will need to serialize the given records.
struct GNUNET_PQ_ExecuteStatement GNUNET_PQ_make_execute(const char *sql)
Create a struct GNUNET_PQ_ExecuteStatement where errors are fatal.
Definition: pq_exec.c:36
uint64_t GNUNET_CRYPTO_random_u64(enum GNUNET_CRYPTO_Quality mode, uint64_t max)
Random on unsigned 64-bit values.
ssize_t GNUNET_GNSRECORD_records_serialize(unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd, size_t dest_size, char *dest)
Serialize the given records to the given destination buffer.
struct GNUNET_PQ_QueryParam GNUNET_PQ_query_param_string(const char *ptr)
Generate query parameter for a string.
int(* zone_to_name)(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
Look for an existing PKEY delegation record for a given public key.
enum GNUNET_DB_QueryStatus GNUNET_PQ_eval_prepared_multi_select(struct GNUNET_PQ_Context *db, const char *statement_name, const struct GNUNET_PQ_QueryParam *params, GNUNET_PQ_PostgresResultHandler rh, void *rh_cls)
Execute a named prepared statement that is a SELECT statement which may return multiple results in co...
Definition: pq_eval.c:209
static size_t data_size
Number of bytes in data.
Definition: gnunet-abd.c:187
#define GNUNET_assert(cond)
Use this for fatal errors that cannot be handled.
Information needed to run a list of SQL statements using GNUNET_PQ_exec_statements().
Information needed to prepare a list of SQL statements using GNUNET_PQ_prepare_statements().
const struct GNUNET_CONFIGURATION_Handle * cfg
Our configuration.
Description of a DB result cell.
#define GNUNET_memcpy(dst, src, n)
Call memcpy() but check for n being 0 first.
static int ret
Return value of the commandline.
Definition: gnunet-abd.c:81
static void database_shutdown(struct Plugin *plugin)
Shutdown database connection and associate data structures.
#define GNUNET_new(type)
Allocate a struct or union of the given type.
Private ECC key encoded for transmission.
void(* GNUNET_NAMESTORE_RecordIterator)(void *cls, uint64_t serial, const struct GNUNET_CRYPTO_EcdsaPrivateKey *private_key, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd)
Function called for each matching record.
int GNUNET_GNSRECORD_records_deserialize(size_t len, const char *src, unsigned int rd_count, struct GNUNET_GNSRECORD_Data *dest)
Deserialize the given records to the given destination.
#define LOG(kind,...)
uint64_t limit
Number of results still to return (counted down by number of results given to iterator).
#define GNUNET_break(cond)
Use this for internal assertion violations that are not fatal (can be handled) but should not occur...
struct GNUNET_BLOCK_PluginFunctions * api
Plugin API.
Definition: block.c:47
common internal definitions for namestore service
static char * zone
Name of the zone we manage.
void * cls
Closure to pass to all plugin functions.
static uint64_t record_count
Record count.
struct GNUNET_PQ_ResultSpec GNUNET_PQ_result_spec_string(const char *name, char **dst)
0-terminated string expected.
#define GNUNET_PQ_result_spec_end
End of result parameter specification.
static int namestore_postgres_store_records(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd)
Store a record in the datastore.
#define GNUNET_PQ_query_param_end
End of query parameter specification.
Definition: gnunet_pq_lib.h:96
static int namestore_postgres_zone_to_name(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
Look for an existing PKEY delegation record for a given public key.
void * libgnunet_plugin_namestore_postgres_init(void *cls)
Entry point for the plugin.
struct GNUNET_PQ_ResultSpec GNUNET_PQ_result_spec_variable_size(const char *name, void **dst, size_t *sptr)
Variable-size result expected.
struct GNUNET_PQ_ResultSpec GNUNET_PQ_result_spec_uint32(const char *name, uint32_t *u32)
uint32_t expected.
#define GNUNET_GNSRECORD_TYPE_PKEY
Record type for GNS zone transfer ("PKEY").
#define GNUNET_PQ_query_param_auto_from_type(x)
Generate fixed-size query parameter with size determined by variable type.
struct GNUNET_PQ_QueryParam GNUNET_PQ_query_param_uint32(const uint32_t *x)
Generate query parameter for an uint32_t in host byte order.
static int database_setup(struct Plugin *plugin)
Initialize the database connections and associated data structures (create tables and indices as need...
static int res
static char * plugin
Solver plugin name as string.
static int namestore_postgres_iterate_records(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, uint64_t serial, uint64_t limit, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
Iterate over the results for a particular key and zone in the datastore.
struct GNUNET_PQ_Context * GNUNET_PQ_connect_with_cfg(const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section, const char *load_path_suffix, const struct GNUNET_PQ_ExecuteStatement *es, const struct GNUNET_PQ_PreparedStatement *ps)
Connect to a postgres database using the configuration option "CONFIG" in section.
Definition: pq_connect.c:466
#define GNUNET_PQ_result_spec_auto_from_type(name, dst)
We expect a fixed-size result, with size determined by the type of * dst
#define GNUNET_PQ_PREPARED_STATEMENT_END
Terminator for prepared statement list.
GNUNET_NAMESTORE_RecordIterator iter
Function to call for each result.
static void parse_result_call_iterator(void *cls, PGresult *res, unsigned int num_results)
A statement has been run.
helper functions for Postgres DB interactions
void * iter_cls
Iterator cls.
static struct GNUNET_FS_SearchContext * sc
Definition: gnunet-search.c:37
void * libgnunet_plugin_namestore_postgres_done(void *cls)
Exit point from the plugin.
Closure for parse_result_call_iterator.
static int namestore_postgres_lookup_records(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const char *label, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
Lookup records in the datastore for which we are the authority.
The transaction succeeded, and yielded one result.
Definition: gnunet_db_lib.h:58
enum GNUNET_DB_QueryStatus GNUNET_PQ_eval_prepared_non_select(struct GNUNET_PQ_Context *db, const char *statement_name, const struct GNUNET_PQ_QueryParam *params)
Execute a named prepared statement that is NOT a SELECT statement in connnection using the given para...
Definition: pq_eval.c:164
static struct GNUNET_FS_PublishContext * pc
Handle to FS-publishing operation.
int(* lookup_records)(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const char *label, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
Lookup records in the datastore for which we are the authority.
configuration data
Definition: configuration.c:84
Handle for a plugin.
Definition: block.c:37
struct GNUNET_PQ_Context * dbh
Native Postgres database handle.
struct GNUNET_PQ_QueryParam GNUNET_PQ_query_param_uint64(const uint64_t *x)
Generate query parameter for an uint16_t in host byte order.
void * iter_cls
Closure for iter.
Public ECC key (always for Curve25519) encoded in a format suitable for network transmission and ECDS...
Description of a DB query parameter.
Definition: gnunet_pq_lib.h:64
struct GNUNET_PQ_QueryParam GNUNET_PQ_query_param_fixed_size(const void *ptr, size_t ptr_size)
Generate query parameter for a buffer ptr of ptr_size bytes.
#define GNUNET_PQ_EXECUTE_STATEMENT_END
Terminator for executable statement list.
const struct GNUNET_CRYPTO_EcdsaPrivateKey * zone_key
Zone key, NULL if part of record.
GNUNET_DB_QueryStatus
Status code returned from functions running database commands.
Definition: gnunet_db_lib.h:34
struct GNUNET_PQ_ExecuteStatement GNUNET_PQ_make_try_execute(const char *sql)
Create a struct GNUNET_PQ_ExecuteStatement where errors should be tolerated.
Definition: pq_exec.c:55
struct GNUNET_PQ_ResultSpec GNUNET_PQ_result_spec_uint64(const char *name, uint64_t *u64)
uint64_t expected.
#define GNUNET_log_from(kind, comp,...)
The transaction succeeded, but yielded zero results.
Definition: gnunet_db_lib.h:53
int GNUNET_CONFIGURATION_get_value_yesno(const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section, const char *option)
Get a configuration value that should be in a set of "YES" or "NO".
uint32_t data
The data value.
int(* store_records)(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd)
Store a record in the datastore for which we are the authority.
GNUNET_PEERSTORE_Processor iter
Iterator.
struct returned by the initialization function of the plugin
Handle to Postgres database.
Definition: pq.h:34
struct GNUNET_PQ_PreparedStatement GNUNET_PQ_make_prepare(const char *name, const char *sql, unsigned int num_args)
Create a struct GNUNET_PQ_PreparedStatement.
Definition: pq_prepare.c:38
int(* iterate_records)(void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, uint64_t serial, uint64_t limit, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
Iterate over the results for a particular zone in the datastore.
No good quality of the operation is needed (i.e., random numbers can be pseudo-random).
int GNUNET_PQ_extract_result(PGresult *result, struct GNUNET_PQ_ResultSpec *rs, int row)
Extract results from a query result according to the given specification.
Definition: pq.c:151
#define GNUNET_free(ptr)
Wrapper around free.
void GNUNET_PQ_cleanup_result(struct GNUNET_PQ_ResultSpec *rs)
Free all memory that was allocated in rs during GNUNET_PQ_extract_result().
Definition: pq.c:130