aboutsummaryrefslogtreecommitdiff
path: root/src/lib/pq
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/pq')
-rw-r--r--src/lib/pq/.gitignore1
-rw-r--r--src/lib/pq/Makefile.am51
-rw-r--r--src/lib/pq/meson.build32
-rw-r--r--src/lib/pq/pq.c207
-rw-r--r--src/lib/pq/pq.h172
-rw-r--r--src/lib/pq/pq_connect.c705
-rw-r--r--src/lib/pq/pq_eval.c248
-rw-r--r--src/lib/pq/pq_event.c584
-rw-r--r--src/lib/pq/pq_exec.c95
-rw-r--r--src/lib/pq/pq_prepare.c125
-rw-r--r--src/lib/pq/pq_query_helper.c1585
-rw-r--r--src/lib/pq/pq_result_helper.c2067
-rw-r--r--src/lib/pq/test_pq.c630
-rw-r--r--src/lib/pq/versioning.sql298
14 files changed, 6800 insertions, 0 deletions
diff --git a/src/lib/pq/.gitignore b/src/lib/pq/.gitignore
new file mode 100644
index 000000000..8de68ddc9
--- /dev/null
+++ b/src/lib/pq/.gitignore
@@ -0,0 +1 @@
test_pq
diff --git a/src/lib/pq/Makefile.am b/src/lib/pq/Makefile.am
new file mode 100644
index 000000000..91014dbfe
--- /dev/null
+++ b/src/lib/pq/Makefile.am
@@ -0,0 +1,51 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS)
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage
6endif
7
8sqldir = $(prefix)/share/gnunet/sql/
9
10sql_DATA = \
11 versioning.sql
12
13EXTRA_DIST = \
14 $(sql_DATA)
15
16if HAVE_POSTGRESQL
17lib_LTLIBRARIES = libgnunetpq.la
18endif
19
20libgnunetpq_la_SOURCES = \
21 pq.c \
22 pq.h \
23 pq_connect.c \
24 pq_eval.c \
25 pq_event.c \
26 pq_exec.c \
27 pq_prepare.c \
28 pq_query_helper.c \
29 pq_result_helper.c
30libgnunetpq_la_LIBADD = -lpq \
31 $(top_builddir)/src/lib/util/libgnunetutil.la
32libgnunetpq_la_LDFLAGS = \
33 $(POSTGRESQL_LDFLAGS) \
34 $(GN_LIB_LDFLAGS) \
35 -version-info 7:0:2
36
37if ENABLE_TEST_RUN
38AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
39TESTS = \
40 test_pq
41endif
42
43check_PROGRAMS= \
44 test_pq
45
46test_pq_SOURCES = \
47 test_pq.c
48test_pq_LDADD = \
49 libgnunetpq.la \
50 $(top_builddir)/src/lib/util/libgnunetutil.la \
51 -lpq $(XLIB)
diff --git a/src/lib/pq/meson.build b/src/lib/pq/meson.build
new file mode 100644
index 000000000..ea73f45a1
--- /dev/null
+++ b/src/lib/pq/meson.build
@@ -0,0 +1,32 @@
1libgnunetpq_src = ['pq.c',
2 'pq_connect.c',
3 'pq_eval.c',
4 'pq_event.c',
5 'pq_exec.c',
6 'pq_prepare.c',
7 'pq_query_helper.c',
8 'pq_result_helper.c']
9
10libgnunetpq = library('gnunetpq',
11 libgnunetpq_src,
12 soversion: '5',
13 version: '5.0.0',
14 dependencies: [libgnunetutil_dep, pq_dep],
15 include_directories: [incdir, configuration_inc],
16 install: true,
17 install_dir: get_option('libdir'))
18libgnunetpq_dep = declare_dependency(link_with : libgnunetpq)
19
20testpq = executable ('test_pq',
21 ['test_pq.c'],
22 dependencies: [libgnunetutil_dep,
23 pq_dep,
24 libgnunetpq_dep],
25 include_directories: [incdir, configuration_inc],
26 build_by_default: false,
27 install: false)
28test('test_pq', testpq,
29 workdir: meson.current_build_dir(),
30 suite: ['pq'])
31
32
diff --git a/src/lib/pq/pq.c b/src/lib/pq/pq.c
new file mode 100644
index 000000000..65e051bfc
--- /dev/null
+++ b/src/lib/pq/pq.c
@@ -0,0 +1,207 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2014, 2015, 2016, 2017, 2019, 2020 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/**
21 * @file pq/pq.c
22 * @brief helper functions for libpq (PostGres) interactions
23 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
24 * @author Florian Dold
25 * @author Christian Grothoff
26 */
27#include "platform.h"
28#include "gnunet_pq_lib.h"
29#include "pq.h"
30
31
32PGresult *
33GNUNET_PQ_exec_prepared (struct GNUNET_PQ_Context *db,
34 const char *name,
35 const struct GNUNET_PQ_QueryParam *params)
36{
37 unsigned int len;
38
39 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
40 "Running prepared statement `%s' on %p\n",
41 name,
42 db);
43 /* count the number of parameters */
44 len = 0;
45 for (unsigned int i = 0; 0 != params[i].num_params; i++)
46 len += params[i].num_params;
47
48 /* new scope to allow stack allocation without alloca */
49 {
50 /* Scratch buffer for temporary storage */
51 void *scratch[GNUNET_NZL (len)];
52 /* Parameter array we are building for the query */
53 void *param_values[GNUNET_NZL (len)];
54 int param_lengths[GNUNET_NZL (len)];
55 int param_formats[GNUNET_NZL (len)];
56 unsigned int off;
57 /* How many entries in the scratch buffer are in use? */
58 unsigned int soff;
59 PGresult *res;
60 int ret;
61 ConnStatusType status;
62
63 off = 0;
64 soff = 0;
65 for (unsigned int i = 0; 0 != params[i].num_params; i++)
66 {
67 const struct GNUNET_PQ_QueryParam *x = &params[i];
68
69 ret = x->conv (x->conv_cls,
70 x->data,
71 x->size,
72 &param_values[off],
73 &param_lengths[off],
74 &param_formats[off],
75 x->num_params,
76 &scratch[soff],
77 len - soff);
78 if (ret < 0)
79 {
80 for (off = 0; off < soff; off++)
81 GNUNET_free (scratch[off]);
82 return NULL;
83 }
84 soff += ret;
85 off += x->num_params;
86 }
87 GNUNET_assert (off == len);
88 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
89 "pq",
90 "Executing prepared SQL statement `%s'\n",
91 name);
92 res = PQexecPrepared (db->conn,
93 name,
94 len,
95 (const char **) param_values,
96 param_lengths,
97 param_formats,
98 1);
99 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
100 "pq",
101 "Execution of prepared SQL statement `%s' finished (%s)\n",
102 name,
103 PQresStatus (PQresultStatus (res)));
104 if ( (PGRES_COMMAND_OK != PQresultStatus (res)) &&
105 (CONNECTION_OK != (status = PQstatus (db->conn))) )
106 {
107 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
108 "pq",
109 "Database disconnected on SQL statement `%s' (reconnecting)\n",
110 name);
111 GNUNET_PQ_reconnect (db);
112 res = NULL;
113 }
114
115 for (off = 0; off < soff; off++)
116 GNUNET_free (scratch[off]);
117 return res;
118 }
119}
120
121
122void
123GNUNET_PQ_cleanup_query_params_closures (
124 const struct GNUNET_PQ_QueryParam *params)
125{
126 for (unsigned int i = 0; 0 != params[i].num_params; i++)
127 {
128 const struct GNUNET_PQ_QueryParam *x = &params[i];
129
130 if ((NULL != x->conv_cls) &&
131 (NULL != x->conv_cls_cleanup))
132 x->conv_cls_cleanup (x->conv_cls);
133 }
134
135}
136
137
138void
139GNUNET_PQ_cleanup_result (struct GNUNET_PQ_ResultSpec *rs)
140{
141 for (unsigned int i = 0; NULL != rs[i].conv; i++)
142 if (NULL != rs[i].cleaner)
143 rs[i].cleaner (rs[i].cls,
144 rs[i].dst);
145}
146
147
148enum GNUNET_GenericReturnValue
149GNUNET_PQ_extract_result (PGresult *result,
150 struct GNUNET_PQ_ResultSpec *rs,
151 int row)
152{
153 unsigned int i;
154
155 if (NULL == result)
156 return GNUNET_SYSERR;
157 for (i = 0; NULL != rs[i].conv; i++)
158 {
159 struct GNUNET_PQ_ResultSpec *spec;
160 enum GNUNET_GenericReturnValue ret;
161
162 spec = &rs[i];
163 ret = spec->conv (spec->cls,
164 result,
165 row,
166 spec->fname,
167 &spec->dst_size,
168 spec->dst);
169 switch (ret)
170 {
171 case GNUNET_OK:
172 /* canonical case, continue below */
173 if (NULL != spec->is_null)
174 *spec->is_null = false;
175 break;
176 case GNUNET_NO:
177 if (spec->is_nullable)
178 {
179 if (NULL != spec->is_null)
180 *spec->is_null = true;
181 continue;
182 }
183 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
184 "NULL field encountered for `%s' where non-NULL was required\n",
185 spec->fname);
186 goto cleanup;
187 case GNUNET_SYSERR:
188 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
189 "Failed to extract field `%s'\n",
190 spec->fname);
191 GNUNET_break (0);
192 goto cleanup;
193 }
194 if (NULL != spec->result_size)
195 *spec->result_size = spec->dst_size;
196 }
197 return GNUNET_OK;
198cleanup:
199 for (unsigned int j = 0; j < i; j++)
200 if (NULL != rs[j].cleaner)
201 rs[j].cleaner (rs[j].cls,
202 rs[j].dst);
203 return GNUNET_SYSERR;
204}
205
206
207/* end of pq/pq.c */
diff --git a/src/lib/pq/pq.h b/src/lib/pq/pq.h
new file mode 100644
index 000000000..65b72cedd
--- /dev/null
+++ b/src/lib/pq/pq.h
@@ -0,0 +1,172 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017, 2019 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/**
21 * @file pq/pq.h
22 * @brief shared internal data structures of libgnunetpq
23 * @author Christian Grothoff
24 */
25#ifndef PQ_H
26#define PQ_H
27
28#include "gnunet_util_lib.h"
29#include "gnunet_pq_lib.h"
30
31
32/**
33 * Handle to Postgres database.
34 */
35struct GNUNET_PQ_Context
36{
37 /**
38 * Actual connection.
39 */
40 PGconn *conn;
41
42 /**
43 * Statements to execute upon connection.
44 */
45 struct GNUNET_PQ_ExecuteStatement *es;
46
47 /**
48 * Prepared statements.
49 */
50 struct GNUNET_PQ_PreparedStatement *ps;
51
52 /**
53 * Length of the @e ps array.
54 */
55 unsigned int ps_len;
56
57 /**
58 * Last used offset in the @e ps array.
59 */
60 unsigned int ps_off;
61
62 /**
63 * Configuration to use to connect to the DB.
64 */
65 char *config_str;
66
67 /**
68 * Path to load SQL files from.
69 */
70 char *load_path;
71
72 /**
73 * Suffix to append to path to load on startup.
74 */
75 char *auto_suffix;
76
77 /**
78 * Map managing event subscriptions.
79 */
80 struct GNUNET_CONTAINER_MultiShortmap *channel_map;
81
82 /**
83 * Task responsible for processing events.
84 */
85 struct GNUNET_SCHEDULER_Task *event_task;
86
87 /**
88 * File descriptor wrapper for @e event_task.
89 */
90 struct GNUNET_NETWORK_Handle *rfd;
91
92 /**
93 * How fast should we resubscribe again?
94 */
95 struct GNUNET_TIME_Relative resubscribe_backoff;
96
97 /**
98 * Flags controlling the connection.
99 */
100 enum GNUNET_PQ_Options flags;
101
102 /**
103 * Mapping between array types and Oid's, pre-filled at reconnect.
104 * More entries are captured in via GNUNET_PQ_get_oid_by_name.
105 */
106 struct
107 {
108 /* Allocated number of elements array the table */
109 unsigned int cap;
110
111 /* Number of entries in the table */
112 unsigned int num;
113
114 /* The table of (name, oid) pairs.
115 * Note that the names are 'const char *' and the pointers should be point
116 * to the same string throughout the lifetime of the program.*/
117 struct name2oid
118 {
119 const char *name;
120 Oid oid;
121 } *table;
122
123 } oids;
124};
125
126
127/**
128 * Internal types that are supported as array types.
129 */
130
131enum array_types
132{
133 array_of_bool,
134 array_of_uint16,
135 array_of_uint32,
136 array_of_uint64,
137 array_of_byte, /* buffers of (char *), (void *), ... */
138 array_of_string, /* NULL-terminated (char *) */
139 array_of_abs_time,
140 array_of_rel_time,
141 array_of_timestamp,
142 array_of_MAX, /* must be last */
143};
144
145/**
146 * the header for a postgresql array in binary format. note that this a
147 * simplified special case of the general structure (which contains pointers),
148 * as we only support one-dimensional arrays.
149 */
150struct pq_array_header
151{
152 uint32_t ndim; /* number of dimensions. we only support ndim = 1 */
153 uint32_t has_null;
154 uint32_t oid;
155 uint32_t dim; /* size of the array */
156 uint32_t lbound; /* index value of first element in the db (default: 1). */
157} __attribute__((packed));
158
159
160/**
161 * Internal API. Reconnect should re-register notifications
162 * after a disconnect.
163 *
164 * @param db the DB handle
165 * @param fd socket to listen on
166 */
167void
168GNUNET_PQ_event_reconnect_ (struct GNUNET_PQ_Context *db,
169 int fd);
170
171
172#endif
diff --git a/src/lib/pq/pq_connect.c b/src/lib/pq/pq_connect.c
new file mode 100644
index 000000000..4ad88ef53
--- /dev/null
+++ b/src/lib/pq/pq_connect.c
@@ -0,0 +1,705 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017, 2019, 2020, 2021, 2023 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/**
21 * @file pq/pq_connect.c
22 * @brief functions to connect to libpq (PostGres)
23 * @author Christian Grothoff
24 * @author Özgür Kesim
25 */
26#include "platform.h"
27#include "pq.h"
28#include <pthread.h>
29
30
31/**
32 * Function called by libpq whenever it wants to log something.
33 * We already log whenever we care, so this function does nothing
34 * and merely exists to silence the libpq logging.
35 *
36 * @param arg the SQL connection that was used
37 * @param res information about some libpq event
38 */
39static void
40pq_notice_receiver_cb (void *arg,
41 const PGresult *res)
42{
43 /* do nothing, intentionally */
44 (void) arg;
45 (void) res;
46}
47
48
49/**
50 * Function called by libpq whenever it wants to log something.
51 * We log those using the GNUnet logger.
52 *
53 * @param arg the SQL connection that was used
54 * @param message information about some libpq event
55 */
56static void
57pq_notice_processor_cb (void *arg,
58 const char *message)
59{
60 (void) arg;
61 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
62 "pq",
63 "%s",
64 message);
65}
66
67
68struct GNUNET_PQ_Context *
69GNUNET_PQ_connect (const char *config_str,
70 const char *load_path,
71 const struct GNUNET_PQ_ExecuteStatement *es,
72 const struct GNUNET_PQ_PreparedStatement *ps)
73{
74 return GNUNET_PQ_connect2 (config_str,
75 load_path,
76 NULL == load_path
77 ? NULL
78 : "",
79 es,
80 ps,
81 GNUNET_PQ_FLAG_NONE);
82}
83
84
85struct GNUNET_PQ_Context *
86GNUNET_PQ_connect2 (const char *config_str,
87 const char *load_path,
88 const char *auto_suffix,
89 const struct GNUNET_PQ_ExecuteStatement *es,
90 const struct GNUNET_PQ_PreparedStatement *ps,
91 enum GNUNET_PQ_Options flags)
92{
93 struct GNUNET_PQ_Context *db;
94 unsigned int elen = 0;
95 unsigned int plen = 0;
96
97 if (NULL != es)
98 while (NULL != es[elen].sql)
99 elen++;
100 if (NULL != ps)
101 while (NULL != ps[plen].name)
102 plen++;
103
104 db = GNUNET_new (struct GNUNET_PQ_Context);
105 db->flags = flags;
106 db->config_str = GNUNET_strdup (config_str);
107 if (NULL != load_path)
108 db->load_path = GNUNET_strdup (load_path);
109 if (NULL != auto_suffix)
110 db->auto_suffix = GNUNET_strdup (auto_suffix);
111 if (0 != elen)
112 {
113 db->es = GNUNET_new_array (elen + 1,
114 struct GNUNET_PQ_ExecuteStatement);
115 memcpy (db->es,
116 es,
117 elen * sizeof (struct GNUNET_PQ_ExecuteStatement));
118 }
119 if (0 != plen)
120 {
121 db->ps = GNUNET_new_array (plen + 1,
122 struct GNUNET_PQ_PreparedStatement);
123 memcpy (db->ps,
124 ps,
125 plen * sizeof (struct GNUNET_PQ_PreparedStatement));
126 }
127 db->channel_map = GNUNET_CONTAINER_multishortmap_create (16,
128 GNUNET_YES);
129 GNUNET_PQ_reconnect (db);
130 if (NULL == db->conn)
131 {
132 GNUNET_CONTAINER_multishortmap_destroy (db->channel_map);
133 GNUNET_free (db->load_path);
134 GNUNET_free (db->auto_suffix);
135 GNUNET_free (db->config_str);
136 GNUNET_free (db);
137 return NULL;
138 }
139 return db;
140}
141
142
143enum GNUNET_GenericReturnValue
144GNUNET_PQ_exec_sql (struct GNUNET_PQ_Context *db,
145 const char *buf)
146{
147 struct GNUNET_OS_Process *psql;
148 enum GNUNET_OS_ProcessStatusType type;
149 unsigned long code;
150 enum GNUNET_GenericReturnValue ret;
151 char *fn;
152
153 GNUNET_asprintf (&fn,
154 "%s%s.sql",
155 db->load_path,
156 buf);
157 if (GNUNET_YES !=
158 GNUNET_DISK_file_test (fn))
159 {
160 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
161 "SQL resource `%s' does not exist\n",
162 fn);
163 GNUNET_free (fn);
164 return GNUNET_NO;
165 }
166 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
167 "Applying SQL file `%s' on database %s\n",
168 fn,
169 db->config_str);
170 psql = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_NONE,
171 NULL,
172 NULL,
173 NULL,
174 "psql",
175 "psql",
176 db->config_str,
177 "-f",
178 fn,
179 "-q",
180 "--set",
181 "ON_ERROR_STOP=1",
182 NULL);
183 if (NULL == psql)
184 {
185 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
186 "exec",
187 "psql");
188 GNUNET_free (fn);
189 return GNUNET_SYSERR;
190 }
191 ret = GNUNET_OS_process_wait_status (psql,
192 &type,
193 &code);
194 if (GNUNET_OK != ret)
195 {
196 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
197 "psql on file %s did not finish, killed it!\n",
198 fn);
199 /* can happen if we got a signal, like CTRL-C, before
200 psql was complete */
201 (void) GNUNET_OS_process_kill (psql,
202 SIGKILL);
203 GNUNET_OS_process_destroy (psql);
204 GNUNET_free (fn);
205 return GNUNET_SYSERR;
206 }
207 GNUNET_OS_process_destroy (psql);
208 if ( (GNUNET_OS_PROCESS_EXITED != type) ||
209 (0 != code) )
210 {
211 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
212 "Could not run PSQL on file %s: psql exit code was %d\n",
213 fn,
214 (int) code);
215 GNUNET_free (fn);
216 return GNUNET_SYSERR;
217 }
218 GNUNET_free (fn);
219 return GNUNET_OK;
220}
221
222
223enum GNUNET_GenericReturnValue
224GNUNET_PQ_run_sql (struct GNUNET_PQ_Context *db,
225 const char *load_path)
226{
227 const char *load_path_suffix;
228 size_t slen = strlen (load_path) + 10;
229
230 load_path_suffix = strrchr (load_path, '/');
231 if (NULL == load_path_suffix)
232 load_path_suffix = load_path;
233 else
234 load_path_suffix++; /* skip '/' */
235 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
236 "Loading SQL resources from `%s'\n",
237 load_path);
238 for (unsigned int i = 1; i<10000; i++)
239 {
240 char patch_name[slen];
241 enum GNUNET_DB_QueryStatus qs;
242
243 /* Check with DB versioning schema if this patch was already applied,
244 if so, skip it. */
245 GNUNET_snprintf (patch_name,
246 sizeof (patch_name),
247 "%s%04u",
248 load_path_suffix,
249 i);
250 {
251 char *applied_by;
252 struct GNUNET_PQ_QueryParam params[] = {
253 GNUNET_PQ_query_param_string (patch_name),
254 GNUNET_PQ_query_param_end
255 };
256 struct GNUNET_PQ_ResultSpec rs[] = {
257 GNUNET_PQ_result_spec_string ("applied_by",
258 &applied_by),
259 GNUNET_PQ_result_spec_end
260 };
261
262 qs = GNUNET_PQ_eval_prepared_singleton_select (db,
263 "gnunet_pq_check_patch",
264 params,
265 rs);
266 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
267 {
268 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
269 "Database version %s already applied by %s, skipping\n",
270 patch_name,
271 applied_by);
272 GNUNET_PQ_cleanup_result (rs);
273 }
274 if (GNUNET_DB_STATUS_HARD_ERROR == qs)
275 {
276 GNUNET_break (0);
277 return GNUNET_SYSERR;
278 }
279 }
280 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
281 continue; /* patch already applied, skip it */
282
283 if (0 != (GNUNET_PQ_FLAG_CHECK_CURRENT & db->flags))
284 {
285 /* We are only checking, found unapplied patch, bad! */
286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
287 "Database outdated, patch %s missing. Aborting!\n",
288 patch_name);
289 return GNUNET_SYSERR;
290 }
291 else
292 {
293 /* patch not yet applied, run it! */
294 enum GNUNET_GenericReturnValue ret;
295
296 GNUNET_snprintf (patch_name,
297 sizeof (patch_name),
298 "%s%04u",
299 load_path,
300 i);
301 ret = GNUNET_PQ_exec_sql (db,
302 patch_name);
303 if (GNUNET_NO == ret)
304 break;
305 if (GNUNET_SYSERR == ret)
306 return GNUNET_SYSERR;
307 }
308 }
309 return GNUNET_OK;
310}
311
312
313void
314GNUNET_PQ_reconnect_if_down (struct GNUNET_PQ_Context *db)
315{
316 if (1 ==
317 PQconsumeInput (db->conn))
318 return;
319 if (CONNECTION_BAD != PQstatus (db->conn))
320 return;
321 GNUNET_PQ_reconnect (db);
322}
323
324
325enum GNUNET_GenericReturnValue
326GNUNET_PQ_get_oid_by_name (
327 struct GNUNET_PQ_Context *db,
328 const char *name,
329 Oid *oid)
330{
331 /* Check if the entry is in the cache already */
332 for (unsigned int i = 0; i < db->oids.num; i++)
333 {
334 /* Pointer comparison */
335 if (name == db->oids.table[i].name)
336 {
337 *oid = db->oids.table[i].oid;
338 return GNUNET_OK;
339 }
340 }
341
342 /* No entry found in cache, ask database */
343 {
344 enum GNUNET_DB_QueryStatus qs;
345 struct GNUNET_PQ_QueryParam params[] = {
346 GNUNET_PQ_query_param_string (name),
347 GNUNET_PQ_query_param_end
348 };
349 struct GNUNET_PQ_ResultSpec spec[] = {
350 GNUNET_PQ_result_spec_uint32 ("oid",
351 oid),
352 GNUNET_PQ_result_spec_end
353 };
354
355 GNUNET_assert (NULL != db);
356
357 qs = GNUNET_PQ_eval_prepared_singleton_select (db,
358 "gnunet_pq_get_oid_by_name",
359 params,
360 spec);
361 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
362 return GNUNET_SYSERR;
363 }
364
365 /* Add the entry to the cache */
366 if (NULL == db->oids.table)
367 {
368 db->oids.table = GNUNET_new_array (8,
369 typeof(*db->oids.table));
370 db->oids.cap = 8;
371 db->oids.num = 0;
372 }
373
374 if (db->oids.cap <= db->oids.num)
375 GNUNET_array_grow (db->oids.table,
376 db->oids.cap,
377 db->oids.cap + 8);
378
379 db->oids.table[db->oids.num].name = name;
380 db->oids.table[db->oids.num].oid = *oid;
381 db->oids.num++;
382
383 return GNUNET_OK;
384}
385
386
387/**
388 * Load the initial set of OIDs for the supported
389 * array-datatypes
390 *
391 * @param db The database context
392 * @return GNUNET_OK on success, GNUNET_SYSERR if any of the types couldn't be found
393 */
394static
395enum GNUNET_GenericReturnValue
396load_initial_oids (struct GNUNET_PQ_Context *db)
397{
398 static const char *typnames[] = {
399 "bool",
400 "int2",
401 "int4",
402 "int8",
403 "bytea",
404 "varchar"
405 };
406 Oid oid;
407
408 for (size_t i = 0; i< sizeof(typnames) / sizeof(*typnames); i++)
409 {
410 if (GNUNET_OK !=
411 GNUNET_PQ_get_oid_by_name (db,
412 typnames[i],
413 &oid))
414 {
415 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
416 "pq",
417 "Couldn't retrieve OID for type %s\n",
418 typnames[i]);
419 return GNUNET_SYSERR;
420 }
421 }
422 return GNUNET_OK;
423}
424
425
426void
427GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
428{
429 GNUNET_PQ_event_reconnect_ (db,
430 -1);
431 if (NULL != db->conn)
432 PQfinish (db->conn);
433 db->conn = PQconnectdb (db->config_str);
434 if ( (NULL == db->conn) ||
435 (CONNECTION_OK != PQstatus (db->conn)) )
436 {
437 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
438 "pq",
439 "Database connection to '%s' failed: %s\n",
440 db->config_str,
441 (NULL != db->conn)
442 ? PQerrorMessage (db->conn)
443 : "PQconnectdb returned NULL");
444 if (NULL != db->conn)
445 {
446 PQfinish (db->conn);
447 db->conn = NULL;
448 }
449 return;
450 }
451 PQsetNoticeReceiver (db->conn,
452 &pq_notice_receiver_cb,
453 db);
454 PQsetNoticeProcessor (db->conn,
455 &pq_notice_processor_cb,
456 db);
457 if ( (NULL != db->load_path) &&
458 (NULL != db->auto_suffix) )
459 {
460 PGresult *res;
461 ExecStatusType est;
462
463 res = PQexec (db->conn,
464 "SELECT"
465 " schema_name"
466 " FROM information_schema.schemata"
467 " WHERE schema_name='_v';");
468 est = PQresultStatus (res);
469 if ( (PGRES_COMMAND_OK != est) &&
470 (PGRES_TUPLES_OK != est) )
471 {
472 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
473 "Failed to run statement to check versioning schema. Bad!\n");
474 PQclear (res);
475 PQfinish (db->conn);
476 db->conn = NULL;
477 return;
478 }
479 if (0 == PQntuples (res))
480 {
481 enum GNUNET_GenericReturnValue ret;
482
483 PQclear (res);
484 if (0 != (db->flags & GNUNET_PQ_FLAG_DROP))
485 {
486 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
487 "Versioning schema does not exist yet. Not attempting drop!\n");
488 PQfinish (db->conn);
489 db->conn = NULL;
490 return;
491 }
492 ret = GNUNET_PQ_exec_sql (db,
493 "versioning");
494 if (GNUNET_NO == ret)
495 {
496 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
497 "Failed to find SQL file to load database versioning logic\n");
498 PQfinish (db->conn);
499 db->conn = NULL;
500 return;
501 }
502 if (GNUNET_SYSERR == ret)
503 {
504 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
505 "Failed to run SQL logic to setup database versioning logic\n");
506 PQfinish (db->conn);
507 db->conn = NULL;
508 return;
509 }
510 }
511 else
512 {
513 PQclear (res);
514 }
515 }
516
517 /* Prepare statement for OID lookup by name */
518 {
519 PGresult *res;
520
521 res = PQprepare (db->conn,
522 "gnunet_pq_get_oid_by_name",
523 "SELECT"
524 " typname, oid"
525 " FROM pg_type"
526 " WHERE typname = $1"
527 " LIMIT 1",
528 1,
529 NULL);
530 if (PGRES_COMMAND_OK != PQresultStatus (res))
531 {
532 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
533 "Failed to run SQL statement prepare OID lookups: %s/%s\n",
534 PQresultErrorMessage (res),
535 PQerrorMessage (db->conn));
536 PQclear (res);
537 PQfinish (db->conn);
538 db->conn = NULL;
539 return;
540 }
541 PQclear (res);
542 }
543
544 /* Reset the OID-cache and retrieve the OIDs for the supported Array types */
545 db->oids.num = 0;
546 if (GNUNET_SYSERR == load_initial_oids (db))
547 {
548 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
549 "Failed to retrieve OID information for array types!\n");
550 PQfinish (db->conn);
551 db->conn = NULL;
552 return;
553 }
554
555 if (NULL != db->auto_suffix)
556 {
557 PGresult *res;
558
559 GNUNET_assert (NULL != db->load_path);
560 res = PQprepare (db->conn,
561 "gnunet_pq_check_patch",
562 "SELECT"
563 " applied_by"
564 " FROM _v.patches"
565 " WHERE patch_name = $1"
566 " LIMIT 1",
567 1,
568 NULL);
569 if (PGRES_COMMAND_OK != PQresultStatus (res))
570 {
571 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
572 "Failed to run SQL logic to setup database versioning logic: %s/%s\n",
573 PQresultErrorMessage (res),
574 PQerrorMessage (db->conn));
575 PQclear (res);
576 PQfinish (db->conn);
577 db->conn = NULL;
578 return;
579 }
580 PQclear (res);
581
582 if (GNUNET_SYSERR ==
583 GNUNET_PQ_run_sql (db,
584 db->auto_suffix))
585 {
586 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
587 "Failed to load SQL statements from `%s*'\n",
588 db->auto_suffix);
589 PQfinish (db->conn);
590 db->conn = NULL;
591 return;
592 }
593 }
594
595 if ( (NULL != db->es) &&
596 (GNUNET_OK !=
597 GNUNET_PQ_exec_statements (db,
598 db->es)) )
599 {
600 PQfinish (db->conn);
601 db->conn = NULL;
602 return;
603 }
604 if ( (NULL != db->ps) &&
605 (GNUNET_OK !=
606 GNUNET_PQ_prepare_statements (db,
607 db->ps)) )
608 {
609 PQfinish (db->conn);
610 db->conn = NULL;
611 return;
612 }
613 GNUNET_PQ_event_reconnect_ (db,
614 PQsocket (db->conn));
615}
616
617
618struct GNUNET_PQ_Context *
619GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
620 const char *section,
621 const char *load_path_suffix,
622 const struct GNUNET_PQ_ExecuteStatement *es,
623 const struct GNUNET_PQ_PreparedStatement *ps)
624{
625 return GNUNET_PQ_connect_with_cfg2 (cfg,
626 section,
627 load_path_suffix,
628 es,
629 ps,
630 GNUNET_PQ_FLAG_NONE);
631}
632
633
634struct GNUNET_PQ_Context *
635GNUNET_PQ_connect_with_cfg2 (const struct GNUNET_CONFIGURATION_Handle *cfg,
636 const char *section,
637 const char *load_path_suffix,
638 const struct GNUNET_PQ_ExecuteStatement *es,
639 const struct GNUNET_PQ_PreparedStatement *ps,
640 enum GNUNET_PQ_Options flags)
641{
642 struct GNUNET_PQ_Context *db;
643 char *conninfo;
644 char *load_path;
645
646 if (GNUNET_OK !=
647 GNUNET_CONFIGURATION_get_value_string (cfg,
648 section,
649 "CONFIG",
650 &conninfo))
651 conninfo = NULL;
652 load_path = NULL;
653 if (GNUNET_OK !=
654 GNUNET_CONFIGURATION_get_value_filename (cfg,
655 section,
656 "SQL_DIR",
657 &load_path))
658 {
659 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
660 section,
661 "SQL_DIR");
662 }
663 if ( (NULL != load_path_suffix) &&
664 (NULL == load_path) )
665 {
666 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
667 section,
668 "SQL_DIR");
669 return NULL;
670 }
671 db = GNUNET_PQ_connect2 (conninfo == NULL ? "" : conninfo,
672 load_path,
673 load_path_suffix,
674 es,
675 ps,
676 flags);
677 GNUNET_free (load_path);
678 GNUNET_free (conninfo);
679 return db;
680}
681
682
683void
684GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
685{
686 if (NULL == db)
687 return;
688 GNUNET_assert (0 ==
689 GNUNET_CONTAINER_multishortmap_size (db->channel_map));
690 GNUNET_CONTAINER_multishortmap_destroy (db->channel_map);
691 GNUNET_free (db->es);
692 GNUNET_free (db->ps);
693 GNUNET_free (db->load_path);
694 GNUNET_free (db->auto_suffix);
695 GNUNET_free (db->config_str);
696 GNUNET_free (db->oids.table);
697 db->oids.table = NULL;
698 db->oids.num = 0;
699 db->oids.cap = 0;
700 PQfinish (db->conn);
701 GNUNET_free (db);
702}
703
704
705/* end of pq/pq_connect.c */
diff --git a/src/lib/pq/pq_eval.c b/src/lib/pq/pq_eval.c
new file mode 100644
index 000000000..e31475e13
--- /dev/null
+++ b/src/lib/pq/pq_eval.c
@@ -0,0 +1,248 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017, 2019 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/**
21 * @file pq/pq_eval.c
22 * @brief functions to execute SQL statements with arguments and/or results (PostGres)
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "pq.h"
27
28
29/**
30 * Error code returned by Postgres for deadlock.
31 */
32#define PQ_DIAG_SQLSTATE_DEADLOCK "40P01"
33
34/**
35 * Error code returned by Postgres for uniqueness violation.
36 */
37#define PQ_DIAG_SQLSTATE_UNIQUE_VIOLATION "23505"
38
39/**
40 * Error code returned by Postgres on serialization failure.
41 */
42#define PQ_DIAG_SQLSTATE_SERIALIZATION_FAILURE "40001"
43
44
45enum GNUNET_DB_QueryStatus
46GNUNET_PQ_eval_result (struct GNUNET_PQ_Context *db,
47 const char *statement_name,
48 PGresult *result)
49{
50 ExecStatusType est;
51
52 if (NULL == result)
53 return GNUNET_DB_STATUS_SOFT_ERROR;
54 est = PQresultStatus (result);
55 if ((PGRES_COMMAND_OK != est) &&
56 (PGRES_TUPLES_OK != est))
57 {
58 const char *sqlstate;
59 ConnStatusType status;
60
61 if (CONNECTION_OK != (status = PQstatus (db->conn)))
62 {
63 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
64 "pq",
65 "Database connection failed during query `%s': %d (reconnecting)\n",
66 statement_name,
67 status);
68 GNUNET_PQ_reconnect (db);
69 return GNUNET_DB_STATUS_SOFT_ERROR;
70 }
71
72 sqlstate = PQresultErrorField (result,
73 PG_DIAG_SQLSTATE);
74 if (NULL == sqlstate)
75 {
76 /* very unexpected... */
77 GNUNET_break (0);
78 return GNUNET_DB_STATUS_HARD_ERROR;
79 }
80 if ((0 == strcmp (sqlstate,
81 PQ_DIAG_SQLSTATE_DEADLOCK)) ||
82 (0 == strcmp (sqlstate,
83 PQ_DIAG_SQLSTATE_SERIALIZATION_FAILURE)))
84 {
85 /* These two can be retried and have a fair chance of working
86 the next time */
87 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
88 "pq",
89 "Query `%s' failed with result: %s/%s/%s/%s/%s\n",
90 statement_name,
91 PQresultErrorField (result,
92 PG_DIAG_MESSAGE_PRIMARY),
93 PQresultErrorField (result,
94 PG_DIAG_MESSAGE_DETAIL),
95 PQresultErrorMessage (result),
96 PQresStatus (PQresultStatus (result)),
97 PQerrorMessage (db->conn));
98 return GNUNET_DB_STATUS_SOFT_ERROR;
99 }
100 if (0 == strcmp (sqlstate,
101 PQ_DIAG_SQLSTATE_UNIQUE_VIOLATION))
102 {
103 /* Likely no need to retry, INSERT of "same" data. */
104 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
105 "pq",
106 "Query `%s' failed with unique violation: %s/%s/%s/%s/%s\n",
107 statement_name,
108 PQresultErrorField (result,
109 PG_DIAG_MESSAGE_PRIMARY),
110 PQresultErrorField (result,
111 PG_DIAG_MESSAGE_DETAIL),
112 PQresultErrorMessage (result),
113 PQresStatus (PQresultStatus (result)),
114 PQerrorMessage (db->conn));
115 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
116 }
117 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
118 "pq",
119 "Query `%s' failed with result: %s/%s/%s/%s/%s\n",
120 statement_name,
121 PQresultErrorField (result,
122 PG_DIAG_MESSAGE_PRIMARY),
123 PQresultErrorField (result,
124 PG_DIAG_MESSAGE_DETAIL),
125 PQresultErrorMessage (result),
126 PQresStatus (PQresultStatus (result)),
127 PQerrorMessage (db->conn));
128 return GNUNET_DB_STATUS_HARD_ERROR;
129 }
130 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
131}
132
133
134enum GNUNET_DB_QueryStatus
135GNUNET_PQ_eval_prepared_non_select (struct GNUNET_PQ_Context *db,
136 const char *statement_name,
137 const struct GNUNET_PQ_QueryParam *params)
138{
139 PGresult *result;
140 enum GNUNET_DB_QueryStatus qs;
141
142 result = GNUNET_PQ_exec_prepared (db,
143 statement_name,
144 params);
145 if (NULL == result)
146 return GNUNET_DB_STATUS_SOFT_ERROR;
147 qs = GNUNET_PQ_eval_result (db,
148 statement_name,
149 result);
150 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
151 {
152 const char *tuples;
153
154 /* What an awful API, this function really does return a string */
155 tuples = PQcmdTuples (result);
156 if (NULL != tuples)
157 qs = strtol (tuples, NULL, 10);
158 }
159 PQclear (result);
160 return qs;
161}
162
163
164enum GNUNET_DB_QueryStatus
165GNUNET_PQ_eval_prepared_multi_select (struct GNUNET_PQ_Context *db,
166 const char *statement_name,
167 const struct GNUNET_PQ_QueryParam *params,
168 GNUNET_PQ_PostgresResultHandler rh,
169 void *rh_cls)
170{
171 PGresult *result;
172 enum GNUNET_DB_QueryStatus qs;
173 unsigned int ret;
174
175 result = GNUNET_PQ_exec_prepared (db,
176 statement_name,
177 params);
178 if (NULL == result)
179 return GNUNET_DB_STATUS_SOFT_ERROR;
180 qs = GNUNET_PQ_eval_result (db,
181 statement_name,
182 result);
183 if (qs < 0)
184 {
185 PQclear (result);
186 return qs;
187 }
188 ret = PQntuples (result);
189 if (NULL != rh)
190 rh (rh_cls,
191 result,
192 ret);
193 PQclear (result);
194 return ret;
195}
196
197
198enum GNUNET_DB_QueryStatus
199GNUNET_PQ_eval_prepared_singleton_select (
200 struct GNUNET_PQ_Context *db,
201 const char *statement_name,
202 const struct GNUNET_PQ_QueryParam *params,
203 struct GNUNET_PQ_ResultSpec *rs)
204{
205 PGresult *result;
206 enum GNUNET_DB_QueryStatus qs;
207 int ntuples;
208
209 result = GNUNET_PQ_exec_prepared (db,
210 statement_name,
211 params);
212 if (NULL == result)
213 return GNUNET_DB_STATUS_SOFT_ERROR;
214 qs = GNUNET_PQ_eval_result (db,
215 statement_name,
216 result);
217 if (qs < 0)
218 {
219 PQclear (result);
220 return qs;
221 }
222 ntuples = PQntuples (result);
223 if (0 == ntuples)
224 {
225 PQclear (result);
226 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
227 }
228 if (1 != ntuples)
229 {
230 /* more than one result, but there must be at most one */
231 GNUNET_break (0);
232 PQclear (result);
233 return GNUNET_DB_STATUS_HARD_ERROR;
234 }
235 if (GNUNET_OK !=
236 GNUNET_PQ_extract_result (result,
237 rs,
238 0))
239 {
240 PQclear (result);
241 return GNUNET_DB_STATUS_HARD_ERROR;
242 }
243 PQclear (result);
244 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
245}
246
247
248/* end of pq/pq_eval.c */
diff --git a/src/lib/pq/pq_event.c b/src/lib/pq/pq_event.c
new file mode 100644
index 000000000..7506ff2f0
--- /dev/null
+++ b/src/lib/pq/pq_event.c
@@ -0,0 +1,584 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2021, 2023 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/**
21 * @file pq/pq_event.c
22 * @brief event notifications via Postgres
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "pq.h"
27#include <pthread.h>
28
29
30/**
31 * Handle for an active LISTENer to the database.
32 */
33struct GNUNET_DB_EventHandler
34{
35 /**
36 * Channel name.
37 */
38 struct GNUNET_ShortHashCode sh;
39
40 /**
41 * Function to call on events.
42 */
43 GNUNET_DB_EventCallback cb;
44
45 /**
46 * Closure for @e cb.
47 */
48 void *cb_cls;
49
50 /**
51 * Database context this event handler is with.
52 */
53 struct GNUNET_PQ_Context *db;
54
55 /**
56 * Task to run on timeout.
57 */
58 struct GNUNET_SCHEDULER_Task *timeout_task;
59};
60
61
62/**
63 * Convert @a es to a short hash.
64 *
65 * @param es spec to hash to an identifier
66 * @param[out] sh short hash to set
67 */
68static void
69es_to_sh (const struct GNUNET_DB_EventHeaderP *es,
70 struct GNUNET_ShortHashCode *sh)
71{
72 struct GNUNET_HashCode h_channel;
73
74 GNUNET_CRYPTO_hash (es,
75 ntohs (es->size),
76 &h_channel);
77 GNUNET_static_assert (sizeof (*sh) <= sizeof (h_channel));
78 memcpy (sh,
79 &h_channel,
80 sizeof (*sh));
81}
82
83
84/**
85 * Convert @a sh to a Postgres identifier.
86 *
87 * @param sh short hash to convert to an identifier
88 * @param[out] identifier by default, Postgres supports
89 * NAMEDATALEN=64 character identifiers
90 * @return end position of the identifier
91 */
92static char *
93sh_to_channel (struct GNUNET_ShortHashCode *sh,
94 char identifier[64])
95{
96 char *end;
97
98 end = GNUNET_STRINGS_data_to_string (sh,
99 sizeof (*sh),
100 identifier,
101 63);
102 GNUNET_assert (NULL != end);
103 *end = '\0';
104 return end;
105}
106
107
108/**
109 * Convert @a sh to a Postgres identifier.
110 *
111 * @param identifier to convert
112 * @param[out] sh set to short hash
113 * @return #GNUNET_OK on success
114 */
115static enum GNUNET_GenericReturnValue
116channel_to_sh (const char *identifier,
117 struct GNUNET_ShortHashCode *sh)
118{
119 return GNUNET_STRINGS_string_to_data (identifier,
120 strlen (identifier),
121 sh,
122 sizeof (*sh));
123}
124
125
126/**
127 * Convert @a es to a Postgres identifier.
128 *
129 * @param es spec to hash to an identifier
130 * @param[out] identifier by default, Postgres supports
131 * NAMEDATALEN=64 character identifiers
132 * @return end position of the identifier
133 */
134static char *
135es_to_channel (const struct GNUNET_DB_EventHeaderP *es,
136 char identifier[64])
137{
138 struct GNUNET_ShortHashCode sh;
139
140 es_to_sh (es,
141 &sh);
142 return sh_to_channel (&sh,
143 identifier);
144}
145
146
147/**
148 * Closure for #do_notify().
149 */
150struct NotifyContext
151{
152 /**
153 * Extra argument of the notification, or NULL.
154 */
155 void *extra;
156
157 /**
158 * Number of bytes in @e extra.
159 */
160 size_t extra_size;
161};
162
163
164/**
165 * Function called on every event handler that
166 * needs to be triggered.
167 *
168 * @param cls a `struct NotifyContext`
169 * @param sh channel name
170 * @param value a `struct GNUNET_DB_EventHandler`
171 * @return #GNUNET_OK continue to iterate
172 */
173static enum GNUNET_GenericReturnValue
174do_notify (void *cls,
175 const struct GNUNET_ShortHashCode *sh,
176 void *value)
177{
178 struct NotifyContext *ctx = cls;
179 struct GNUNET_DB_EventHandler *eh = value;
180
181 eh->cb (eh->cb_cls,
182 ctx->extra,
183 ctx->extra_size);
184 return GNUNET_OK;
185}
186
187
188void
189GNUNET_PQ_event_do_poll (struct GNUNET_PQ_Context *db)
190{
191 PGnotify *n;
192 unsigned int cnt = 0;
193
194 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
195 "PG poll job active\n");
196 if (1 !=
197 PQconsumeInput (db->conn))
198 {
199 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
200 "Failed to read from Postgres: %s\n",
201 PQerrorMessage (db->conn));
202 if (CONNECTION_BAD != PQstatus (db->conn))
203 return;
204 GNUNET_PQ_reconnect (db);
205 return;
206 }
207 while (NULL != (n = PQnotifies (db->conn)))
208 {
209 struct GNUNET_ShortHashCode sh;
210 struct NotifyContext ctx = {
211 .extra = NULL
212 };
213
214 cnt++;
215 if ('X' != toupper ((int) n->relname[0]))
216 {
217 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
218 "Ignoring notification for unsupported channel identifier `%s'\n",
219 n->relname);
220 PQfreemem (n);
221 continue;
222 }
223 if (GNUNET_OK !=
224 channel_to_sh (&n->relname[1],
225 &sh))
226 {
227 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
228 "Ignoring notification for unsupported channel identifier `%s'\n",
229 n->relname);
230 PQfreemem (n);
231 continue;
232 }
233 if ( (NULL != n->extra) &&
234 (GNUNET_OK !=
235 GNUNET_STRINGS_string_to_data_alloc (n->extra,
236 strlen (n->extra),
237 &ctx.extra,
238 &ctx.extra_size)))
239 {
240 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
241 "Ignoring notification for unsupported extra data `%s' on channel `%s'\n",
242 n->extra,
243 n->relname);
244 PQfreemem (n);
245 continue;
246 }
247 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
248 "Received notification %s with extra data `%.*s'\n",
249 n->relname,
250 (int) ctx.extra_size,
251 (const char *) ctx.extra);
252 GNUNET_CONTAINER_multishortmap_get_multiple (db->channel_map,
253 &sh,
254 &do_notify,
255 &ctx);
256 GNUNET_free (ctx.extra);
257 PQfreemem (n);
258 }
259 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
260 "PG poll job finishes after %u events\n",
261 cnt);
262}
263
264
265/**
266 * The GNUnet scheduler notifies us that we need to
267 * trigger the DB event poller.
268 *
269 * @param cls a `struct GNUNET_PQ_Context *`
270 */
271static void
272do_scheduler_notify (void *cls)
273{
274 struct GNUNET_PQ_Context *db = cls;
275
276 db->event_task = NULL;
277 if (NULL == db->rfd)
278 GNUNET_PQ_reconnect (db);
279 GNUNET_PQ_event_do_poll (db);
280 if (NULL != db->event_task)
281 return;
282 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
283 "Resubscribing\n");
284 if (NULL == db->rfd)
285 {
286 db->resubscribe_backoff
287 = GNUNET_TIME_relative_max (db->resubscribe_backoff,
288 GNUNET_TIME_UNIT_SECONDS);
289 db->resubscribe_backoff
290 = GNUNET_TIME_STD_BACKOFF (db->resubscribe_backoff);
291 db->event_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
292 &do_scheduler_notify,
293 db);
294 return;
295 }
296 db->resubscribe_backoff = GNUNET_TIME_UNIT_SECONDS;
297 db->event_task
298 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
299 db->rfd,
300 &do_scheduler_notify,
301 db);
302}
303
304
305/**
306 * Function called when the Postgres FD changes and we need
307 * to update the scheduler event loop task.
308 *
309 * @param cls a `struct GNUNET_PQ_Context *`
310 * @param fd the file descriptor, possibly -1
311 */
312static void
313scheduler_fd_cb (void *cls,
314 int fd)
315{
316 struct GNUNET_PQ_Context *db = cls;
317
318 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
319 "New poll FD is %d\n",
320 fd);
321 if (NULL != db->event_task)
322 {
323 GNUNET_SCHEDULER_cancel (db->event_task);
324 db->event_task = NULL;
325 }
326 GNUNET_free (db->rfd);
327 if (-1 == fd)
328 return;
329 if (0 == GNUNET_CONTAINER_multishortmap_size (db->channel_map))
330 return;
331 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
332 "Activating poll job on %d\n",
333 fd);
334 db->rfd = GNUNET_NETWORK_socket_box_native (fd);
335 db->event_task
336 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_ZERO,
337 db->rfd,
338 &do_scheduler_notify,
339 db);
340}
341
342
343/**
344 * Helper function to trigger an SQL @a cmd on @a db
345 *
346 * @param db database to send command to
347 * @param cmd prefix of the command to send
348 * @param eh details about the event
349 */
350static void
351manage_subscribe (struct GNUNET_PQ_Context *db,
352 const char *cmd,
353 struct GNUNET_DB_EventHandler *eh)
354{
355 char sql[16 + 64];
356 char *end;
357 PGresult *result;
358
359 if (NULL == db->conn)
360 return;
361 end = stpcpy (sql,
362 cmd);
363 end = sh_to_channel (&eh->sh,
364 end);
365 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
366 "Executing PQ command `%s'\n",
367 sql);
368 result = PQexec (db->conn,
369 sql);
370 if (PGRES_COMMAND_OK != PQresultStatus (result))
371 {
372 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
373 "pq",
374 "Failed to execute `%s': %s/%s/%s/%s/%s",
375 sql,
376 PQresultErrorField (result,
377 PG_DIAG_MESSAGE_PRIMARY),
378 PQresultErrorField (result,
379 PG_DIAG_MESSAGE_DETAIL),
380 PQresultErrorMessage (result),
381 PQresStatus (PQresultStatus (result)),
382 PQerrorMessage (db->conn));
383 }
384 PQclear (result);
385}
386
387
388/**
389 * Re-subscribe to notifications after disconnect.
390 *
391 * @param cls the DB context
392 * @param sh the short hash of the channel
393 * @param value the event handler
394 * @return #GNUNET_OK to continue to iterate
395 */
396static enum GNUNET_GenericReturnValue
397register_notify (void *cls,
398 const struct GNUNET_ShortHashCode *sh,
399 void *value)
400{
401 struct GNUNET_PQ_Context *db = cls;
402 struct GNUNET_DB_EventHandler *eh = value;
403
404 manage_subscribe (db,
405 "LISTEN X",
406 eh);
407 return GNUNET_OK;
408}
409
410
411void
412GNUNET_PQ_event_reconnect_ (struct GNUNET_PQ_Context *db,
413 int fd)
414{
415 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
416 "Change in PQ event FD to %d\n",
417 fd);
418 scheduler_fd_cb (db,
419 fd);
420 GNUNET_CONTAINER_multishortmap_iterate (db->channel_map,
421 &register_notify,
422 db);
423}
424
425
426/**
427 * Function run on timeout for an event. Triggers
428 * the notification, but does NOT clear the handler.
429 *
430 * @param cls a `struct GNUNET_DB_EventHandler *`
431 */
432static void
433event_timeout (void *cls)
434{
435 struct GNUNET_DB_EventHandler *eh = cls;
436
437 eh->timeout_task = NULL;
438 eh->cb (eh->cb_cls,
439 NULL,
440 0);
441}
442
443
444struct GNUNET_DB_EventHandler *
445GNUNET_PQ_event_listen (struct GNUNET_PQ_Context *db,
446 const struct GNUNET_DB_EventHeaderP *es,
447 struct GNUNET_TIME_Relative timeout,
448 GNUNET_DB_EventCallback cb,
449 void *cb_cls)
450{
451 struct GNUNET_DB_EventHandler *eh;
452 bool sub;
453
454 eh = GNUNET_new (struct GNUNET_DB_EventHandler);
455 eh->db = db;
456 es_to_sh (es,
457 &eh->sh);
458 eh->cb = cb;
459 eh->cb_cls = cb_cls;
460 sub = (NULL ==
461 GNUNET_CONTAINER_multishortmap_get (db->channel_map,
462 &eh->sh));
463 GNUNET_assert (GNUNET_OK ==
464 GNUNET_CONTAINER_multishortmap_put (db->channel_map,
465 &eh->sh,
466 eh,
467 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
468 if (NULL == db->event_task)
469 {
470 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
471 "Starting event scheduler\n");
472 scheduler_fd_cb (db,
473 PQsocket (db->conn));
474 }
475 if (sub)
476 manage_subscribe (db,
477 "LISTEN X",
478 eh);
479 eh->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout,
480 &event_timeout,
481 eh);
482 return eh;
483}
484
485
486void
487GNUNET_PQ_event_listen_cancel (struct GNUNET_DB_EventHandler *eh)
488{
489 struct GNUNET_PQ_Context *db = eh->db;
490
491 GNUNET_assert (GNUNET_OK ==
492 GNUNET_CONTAINER_multishortmap_remove (db->channel_map,
493 &eh->sh,
494 eh));
495 if (NULL ==
496 GNUNET_CONTAINER_multishortmap_get (db->channel_map,
497 &eh->sh))
498 manage_subscribe (db,
499 "UNLISTEN X",
500 eh);
501 if (0 == GNUNET_CONTAINER_multishortmap_size (db->channel_map))
502 {
503 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
504 "Stopping PQ event scheduler job\n");
505 GNUNET_free (db->rfd);
506 if (NULL != db->event_task)
507 {
508 GNUNET_SCHEDULER_cancel (db->event_task);
509 db->event_task = NULL;
510 }
511 }
512 if (NULL != eh->timeout_task)
513 {
514 GNUNET_SCHEDULER_cancel (eh->timeout_task);
515 eh->timeout_task = NULL;
516 }
517 GNUNET_free (eh);
518}
519
520
521char *
522GNUNET_PQ_get_event_notify_channel (const struct GNUNET_DB_EventHeaderP *es)
523{
524 char sql[16 + 64 + 8];
525 char *end;
526
527 end = stpcpy (sql,
528 "X");
529 end = es_to_channel (es,
530 end);
531 GNUNET_assert (NULL != end);
532 return GNUNET_strdup (sql);
533}
534
535
536void
537GNUNET_PQ_event_notify (struct GNUNET_PQ_Context *db,
538 const struct GNUNET_DB_EventHeaderP *es,
539 const void *extra,
540 size_t extra_size)
541{
542 char sql[16 + 64 + extra_size * 8 / 5 + 8];
543 char *end;
544 PGresult *result;
545
546 end = stpcpy (sql,
547 "NOTIFY X");
548 end = es_to_channel (es,
549 end);
550 end = stpcpy (end,
551 ", '");
552 end = GNUNET_STRINGS_data_to_string (extra,
553 extra_size,
554 end,
555 sizeof (sql) - (end - sql) - 1);
556 GNUNET_assert (NULL != end);
557 *end = '\0';
558 end = stpcpy (end,
559 "'");
560 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
561 "Executing command `%s'\n",
562 sql);
563 result = PQexec (db->conn,
564 sql);
565 if (PGRES_COMMAND_OK != PQresultStatus (result))
566 {
567 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
568 "pq",
569 "Failed to execute `%s': %s/%s/%s/%s/%s",
570 sql,
571 PQresultErrorField (result,
572 PG_DIAG_MESSAGE_PRIMARY),
573 PQresultErrorField (result,
574 PG_DIAG_MESSAGE_DETAIL),
575 PQresultErrorMessage (result),
576 PQresStatus (PQresultStatus (result)),
577 PQerrorMessage (db->conn));
578 }
579 PQclear (result);
580 GNUNET_PQ_event_do_poll (db);
581}
582
583
584/* end of pq_event.c */
diff --git a/src/lib/pq/pq_exec.c b/src/lib/pq/pq_exec.c
new file mode 100644
index 000000000..1fd8c5068
--- /dev/null
+++ b/src/lib/pq/pq_exec.c
@@ -0,0 +1,95 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017, 2019 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/**
21 * @file pq/pq_exec.c
22 * @brief functions to execute plain SQL statements (PostGres)
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "pq.h"
27
28
29struct GNUNET_PQ_ExecuteStatement
30GNUNET_PQ_make_execute (const char *sql)
31{
32 struct GNUNET_PQ_ExecuteStatement es = {
33 .sql = sql,
34 .ignore_errors = GNUNET_NO
35 };
36
37 return es;
38}
39
40
41struct GNUNET_PQ_ExecuteStatement
42GNUNET_PQ_make_try_execute (const char *sql)
43{
44 struct GNUNET_PQ_ExecuteStatement es = {
45 .sql = sql,
46 .ignore_errors = GNUNET_YES
47 };
48
49 return es;
50}
51
52
53enum GNUNET_GenericReturnValue
54GNUNET_PQ_exec_statements (struct GNUNET_PQ_Context *db,
55 const struct GNUNET_PQ_ExecuteStatement *es)
56{
57 for (unsigned int i = 0; NULL != es[i].sql; i++)
58 {
59 PGresult *result;
60
61 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
62 "Running statement `%s' on %p\n",
63 es[i].sql,
64 db);
65 result = PQexec (db->conn,
66 es[i].sql);
67 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
68 "Running statement `%s' on %p finished (%s)\n",
69 es[i].sql,
70 db,
71 PQresStatus (PQresultStatus (result)));
72 if ((GNUNET_NO == es[i].ignore_errors) &&
73 (PGRES_COMMAND_OK != PQresultStatus (result)))
74 {
75 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
76 "pq",
77 "Failed to execute `%s': %s/%s/%s/%s/%s",
78 es[i].sql,
79 PQresultErrorField (result,
80 PG_DIAG_MESSAGE_PRIMARY),
81 PQresultErrorField (result,
82 PG_DIAG_MESSAGE_DETAIL),
83 PQresultErrorMessage (result),
84 PQresStatus (PQresultStatus (result)),
85 PQerrorMessage (db->conn));
86 PQclear (result);
87 return GNUNET_SYSERR;
88 }
89 PQclear (result);
90 }
91 return GNUNET_OK;
92}
93
94
95/* end of pq/pq_exec.c */
diff --git a/src/lib/pq/pq_prepare.c b/src/lib/pq/pq_prepare.c
new file mode 100644
index 000000000..b4292dea3
--- /dev/null
+++ b/src/lib/pq/pq_prepare.c
@@ -0,0 +1,125 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017, 2019 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/**
21 * @file pq/pq_prepare.c
22 * @brief functions to connect to libpq (PostGres)
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "pq.h"
27
28
29struct GNUNET_PQ_PreparedStatement
30GNUNET_PQ_make_prepare (const char *name,
31 const char *sql)
32{
33 struct GNUNET_PQ_PreparedStatement ps = {
34 .name = name,
35 .sql = sql
36 };
37
38 return ps;
39}
40
41
42enum GNUNET_GenericReturnValue
43GNUNET_PQ_prepare_once (struct GNUNET_PQ_Context *db,
44 const struct GNUNET_PQ_PreparedStatement *ps)
45{
46 for (unsigned int i = 0; NULL != ps[i].name; i++)
47 {
48 PGresult *ret;
49
50 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
51 "pq",
52 "Preparing SQL statement `%s' as `%s'\n",
53 ps[i].sql,
54 ps[i].name);
55 ret = PQprepare (db->conn,
56 ps[i].name,
57 ps[i].sql,
58 0,
59 NULL);
60 if (PGRES_COMMAND_OK != PQresultStatus (ret))
61 {
62 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
63 "pq",
64 "PQprepare (`%s' as `%s') failed with error: %s\n",
65 ps[i].sql,
66 ps[i].name,
67 PQerrorMessage (db->conn));
68 PQclear (ret);
69 ret = PQdescribePrepared (db->conn,
70 ps[i].name);
71 if (PGRES_COMMAND_OK != PQresultStatus (ret))
72 {
73 PQclear (ret);
74 return GNUNET_SYSERR;
75 }
76 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
77 "pq",
78 "Statement `%s' already known. Ignoring the issue in the hope that you are using connection pooling...\n",
79 ps[i].name);
80 }
81 PQclear (ret);
82 }
83 return GNUNET_OK;
84}
85
86
87enum GNUNET_GenericReturnValue
88GNUNET_PQ_prepare_statements (struct GNUNET_PQ_Context *db,
89 const struct GNUNET_PQ_PreparedStatement *ps)
90{
91 if (db->ps != ps)
92 {
93 /* add 'ps' to list db->ps of prepared statements to run on reconnect! */
94 unsigned int nlen = 0; /* length of 'ps' array */
95 unsigned int xlen;
96 struct GNUNET_PQ_PreparedStatement *rps; /* combined array */
97
98 while (NULL != ps[nlen].name)
99 nlen++;
100 xlen = nlen + db->ps_off;
101 if (xlen > db->ps_len)
102 {
103 xlen = 2 * xlen + 1;
104 rps = GNUNET_new_array (xlen,
105 struct GNUNET_PQ_PreparedStatement);
106 if (NULL != db->ps)
107 memcpy (rps,
108 db->ps,
109 db->ps_off * sizeof (struct GNUNET_PQ_PreparedStatement));
110 GNUNET_free (db->ps);
111 db->ps_len = xlen;
112 db->ps = rps;
113 }
114 memcpy (&db->ps[db->ps_off],
115 ps,
116 nlen * sizeof (struct GNUNET_PQ_PreparedStatement));
117 db->ps_off += nlen;
118 }
119
120 return GNUNET_PQ_prepare_once (db,
121 ps);
122}
123
124
125/* end of pq/pq_prepare.c */
diff --git a/src/lib/pq/pq_query_helper.c b/src/lib/pq/pq_query_helper.c
new file mode 100644
index 000000000..913ce9235
--- /dev/null
+++ b/src/lib/pq/pq_query_helper.c
@@ -0,0 +1,1585 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2014, 2015, 2016, 2020 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/**
21 * @file pq/pq_query_helper.c
22 * @brief functions to initialize parameter arrays
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_common.h"
27#include "gnunet_pq_lib.h"
28#include "gnunet_time_lib.h"
29#include "pq.h"
30
31
32/**
33 * Function called to convert input argument into SQL parameters.
34 *
35 * @param cls closure
36 * @param data pointer to input argument
37 * @param data_len number of bytes in @a data (if applicable)
38 * @param[out] param_values SQL data to set
39 * @param[out] param_lengths SQL length data to set
40 * @param[out] param_formats SQL format data to set
41 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
42 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
43 * @param scratch_length number of entries left in @a scratch
44 * @return -1 on error, number of offsets used in @a scratch otherwise
45 */
46static int
47qconv_null (void *cls,
48 const void *data,
49 size_t data_len,
50 void *param_values[],
51 int param_lengths[],
52 int param_formats[],
53 unsigned int param_length,
54 void *scratch[],
55 unsigned int scratch_length)
56{
57 (void) scratch;
58 (void) scratch_length;
59 (void) data;
60 (void) data_len;
61 GNUNET_break (NULL == cls);
62 if (1 != param_length)
63 return -1;
64 param_values[0] = NULL;
65 param_lengths[0] = 0;
66 param_formats[0] = 1;
67 return 0;
68}
69
70
71struct GNUNET_PQ_QueryParam
72GNUNET_PQ_query_param_null (void)
73{
74 struct GNUNET_PQ_QueryParam res = {
75 .conv = &qconv_null,
76 .num_params = 1
77 };
78
79 return res;
80}
81
82
83/**
84 * Function called to convert input argument into SQL parameters.
85 *
86 * @param cls closure
87 * @param data pointer to input argument
88 * @param data_len number of bytes in @a data (if applicable)
89 * @param[out] param_values SQL data to set
90 * @param[out] param_lengths SQL length data to set
91 * @param[out] param_formats SQL format data to set
92 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
93 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
94 * @param scratch_length number of entries left in @a scratch
95 * @return -1 on error, number of offsets used in @a scratch otherwise
96 */
97static int
98qconv_fixed (void *cls,
99 const void *data,
100 size_t data_len,
101 void *param_values[],
102 int param_lengths[],
103 int param_formats[],
104 unsigned int param_length,
105 void *scratch[],
106 unsigned int scratch_length)
107{
108 (void) scratch;
109 (void) scratch_length;
110 GNUNET_break (NULL == cls);
111 if (1 != param_length)
112 return -1;
113 param_values[0] = (void *) data;
114 param_lengths[0] = data_len;
115 param_formats[0] = 1;
116 return 0;
117}
118
119
120struct GNUNET_PQ_QueryParam
121GNUNET_PQ_query_param_fixed_size (const void *ptr,
122 size_t ptr_size)
123{
124 struct GNUNET_PQ_QueryParam res = {
125 .conv = &qconv_fixed,
126 .conv_cls = NULL,
127 .data = ptr,
128 .size = ptr_size,
129 .num_params = 1
130 };
131
132 return res;
133}
134
135
136struct GNUNET_PQ_QueryParam
137GNUNET_PQ_query_param_string (const char *ptr)
138{
139 return GNUNET_PQ_query_param_fixed_size (ptr,
140 strlen (ptr));
141}
142
143
144struct GNUNET_PQ_QueryParam
145GNUNET_PQ_query_param_bool (bool b)
146{
147 static uint8_t bt = 1;
148 static uint8_t bf = 0;
149
150 return GNUNET_PQ_query_param_fixed_size (b ? &bt : &bf,
151 sizeof (uint8_t));
152}
153
154
155/**
156 * Function called to convert input argument into SQL parameters.
157 *
158 * @param cls closure
159 * @param data pointer to input argument
160 * @param data_len number of bytes in @a data (if applicable)
161 * @param[out] param_values SQL data to set
162 * @param[out] param_lengths SQL length data to set
163 * @param[out] param_formats SQL format data to set
164 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
165 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
166 * @param scratch_length number of entries left in @a scratch
167 * @return -1 on error, number of offsets used in @a scratch otherwise
168 */
169static int
170qconv_uint16 (void *cls,
171 const void *data,
172 size_t data_len,
173 void *param_values[],
174 int param_lengths[],
175 int param_formats[],
176 unsigned int param_length,
177 void *scratch[],
178 unsigned int scratch_length)
179{
180 const uint16_t *u_hbo = data;
181 uint16_t *u_nbo;
182
183 (void) scratch;
184 (void) scratch_length;
185 GNUNET_break (NULL == cls);
186 if (1 != param_length)
187 return -1;
188 u_nbo = GNUNET_new (uint16_t);
189 scratch[0] = u_nbo;
190 *u_nbo = htons (*u_hbo);
191 param_values[0] = (void *) u_nbo;
192 param_lengths[0] = sizeof(uint16_t);
193 param_formats[0] = 1;
194 return 1;
195}
196
197
198struct GNUNET_PQ_QueryParam
199GNUNET_PQ_query_param_uint16 (const uint16_t *x)
200{
201 struct GNUNET_PQ_QueryParam res = {
202 .conv = &qconv_uint16,
203 .data = x,
204 .size = sizeof(*x),
205 .num_params = 1
206 };
207
208 return res;
209}
210
211
212/**
213 * Function called to convert input argument into SQL parameters.
214 *
215 * @param cls closure
216 * @param data pointer to input argument
217 * @param data_len number of bytes in @a data (if applicable)
218 * @param[out] param_values SQL data to set
219 * @param[out] param_lengths SQL length data to set
220 * @param[out] param_formats SQL format data to set
221 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
222 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
223 * @param scratch_length number of entries left in @a scratch
224 * @return -1 on error, number of offsets used in @a scratch otherwise
225 */
226static int
227qconv_uint32 (void *cls,
228 const void *data,
229 size_t data_len,
230 void *param_values[],
231 int param_lengths[],
232 int param_formats[],
233 unsigned int param_length,
234 void *scratch[],
235 unsigned int scratch_length)
236{
237 const uint32_t *u_hbo = data;
238 uint32_t *u_nbo;
239
240 (void) scratch;
241 (void) scratch_length;
242 GNUNET_break (NULL == cls);
243 if (1 != param_length)
244 return -1;
245 u_nbo = GNUNET_new (uint32_t);
246 scratch[0] = u_nbo;
247 *u_nbo = htonl (*u_hbo);
248 param_values[0] = (void *) u_nbo;
249 param_lengths[0] = sizeof(uint32_t);
250 param_formats[0] = 1;
251 return 1;
252}
253
254
255struct GNUNET_PQ_QueryParam
256GNUNET_PQ_query_param_uint32 (const uint32_t *x)
257{
258 struct GNUNET_PQ_QueryParam res = {
259 .conv = &qconv_uint32,
260 .data = x,
261 .size = sizeof(*x),
262 .num_params = 1
263 };
264
265 return res;
266}
267
268
269/**
270 * Function called to convert input argument into SQL parameters.
271 *
272 * @param cls closure
273 * @param data pointer to input argument
274 * @param data_len number of bytes in @a data (if applicable)
275 * @param[out] param_values SQL data to set
276 * @param[out] param_lengths SQL length data to set
277 * @param[out] param_formats SQL format data to set
278 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
279 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
280 * @param scratch_length number of entries left in @a scratch
281 * @return -1 on error, number of offsets used in @a scratch otherwise
282 */
283static int
284qconv_uint64 (void *cls,
285 const void *data,
286 size_t data_len,
287 void *param_values[],
288 int param_lengths[],
289 int param_formats[],
290 unsigned int param_length,
291 void *scratch[],
292 unsigned int scratch_length)
293{
294 const uint64_t *u_hbo = data;
295 uint64_t *u_nbo;
296
297 (void) scratch;
298 (void) scratch_length;
299 GNUNET_break (NULL == cls);
300 if (1 != param_length)
301 return -1;
302 u_nbo = GNUNET_new (uint64_t);
303 scratch[0] = u_nbo;
304 *u_nbo = GNUNET_htonll (*u_hbo);
305 param_values[0] = (void *) u_nbo;
306 param_lengths[0] = sizeof(uint64_t);
307 param_formats[0] = 1;
308 return 1;
309}
310
311
312struct GNUNET_PQ_QueryParam
313GNUNET_PQ_query_param_uint64 (const uint64_t *x)
314{
315 struct GNUNET_PQ_QueryParam res = {
316 .conv = &qconv_uint64,
317 .data = x,
318 .size = sizeof(*x),
319 .num_params = 1
320 };
321
322 return res;
323}
324
325
326/**
327 * Function called to convert input argument into SQL parameters.
328 *
329 * @param cls closure
330 * @param data pointer to input argument
331 * @param data_len number of bytes in @a data (if applicable)
332 * @param[out] param_values SQL data to set
333 * @param[out] param_lengths SQL length data to set
334 * @param[out] param_formats SQL format data to set
335 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
336 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
337 * @param scratch_length number of entries left in @a scratch
338 * @return -1 on error, number of offsets used in @a scratch otherwise
339 */
340static int
341qconv_int64 (void *cls,
342 const void *data,
343 size_t data_len,
344 void *param_values[],
345 int param_lengths[],
346 int param_formats[],
347 unsigned int param_length,
348 void *scratch[],
349 unsigned int scratch_length)
350{
351 const int64_t *u_hbo = data;
352 int64_t *u_nbo;
353
354 (void) scratch;
355 (void) scratch_length;
356 GNUNET_break (NULL == cls);
357 if (1 != param_length)
358 return -1;
359 u_nbo = GNUNET_new (int64_t);
360 scratch[0] = u_nbo;
361 *u_nbo = GNUNET_htonll (*u_hbo);
362 param_values[0] = (void *) u_nbo;
363 param_lengths[0] = sizeof(int64_t);
364 param_formats[0] = 1;
365 return 1;
366}
367
368
369struct GNUNET_PQ_QueryParam
370GNUNET_PQ_query_param_int64 (const int64_t *x)
371{
372 struct GNUNET_PQ_QueryParam res = {
373 .conv = &qconv_int64,
374 .data = x,
375 .size = sizeof(*x),
376 .num_params = 1
377 };
378
379 return res;
380}
381
382
383/**
384 * Function called to convert input argument into SQL parameters.
385 *
386 * @param cls closure
387 * @param data pointer to input argument
388 * @param data_len number of bytes in @a data (if applicable)
389 * @param[out] param_values SQL data to set
390 * @param[out] param_lengths SQL length data to set
391 * @param[out] param_formats SQL format data to set
392 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
393 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
394 * @param scratch_length number of entries left in @a scratch
395 * @return -1 on error, number of offsets used in @a scratch otherwise
396 */
397static int
398qconv_rsa_public_key (void *cls,
399 const void *data,
400 size_t data_len,
401 void *param_values[],
402 int param_lengths[],
403 int param_formats[],
404 unsigned int param_length,
405 void *scratch[],
406 unsigned int scratch_length)
407{
408 const struct GNUNET_CRYPTO_RsaPublicKey *rsa = data;
409 void *buf;
410 size_t buf_size;
411
412 GNUNET_break (NULL == cls);
413 if (1 != param_length)
414 return -1;
415 buf_size = GNUNET_CRYPTO_rsa_public_key_encode (rsa,
416 &buf);
417 scratch[0] = buf;
418 param_values[0] = (void *) buf;
419 param_lengths[0] = buf_size;
420 param_formats[0] = 1;
421 return 1;
422}
423
424
425struct GNUNET_PQ_QueryParam
426GNUNET_PQ_query_param_rsa_public_key (
427 const struct GNUNET_CRYPTO_RsaPublicKey *x)
428{
429 struct GNUNET_PQ_QueryParam res = {
430 .conv = &qconv_rsa_public_key,
431 .data = x,
432 .num_params = 1
433 };
434
435 return res;
436}
437
438
439/**
440 * Function called to convert input argument into SQL parameters.
441 *
442 * @param cls closure
443 * @param data pointer to input argument
444 * @param data_len number of bytes in @a data (if applicable)
445 * @param[out] param_values SQL data to set
446 * @param[out] param_lengths SQL length data to set
447 * @param[out] param_formats SQL format data to set
448 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
449 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
450 * @param scratch_length number of entries left in @a scratch
451 * @return -1 on error, number of offsets used in @a scratch otherwise
452 */
453static int
454qconv_rsa_signature (void *cls,
455 const void *data,
456 size_t data_len,
457 void *param_values[],
458 int param_lengths[],
459 int param_formats[],
460 unsigned int param_length,
461 void *scratch[],
462 unsigned int scratch_length)
463{
464 const struct GNUNET_CRYPTO_RsaSignature *sig = data;
465 void *buf;
466 size_t buf_size;
467
468 GNUNET_break (NULL == cls);
469 if (1 != param_length)
470 return -1;
471 buf_size = GNUNET_CRYPTO_rsa_signature_encode (sig,
472 &buf);
473 scratch[0] = buf;
474 param_values[0] = (void *) buf;
475 param_lengths[0] = buf_size;
476 param_formats[0] = 1;
477 return 1;
478}
479
480
481struct GNUNET_PQ_QueryParam
482GNUNET_PQ_query_param_rsa_signature (const struct GNUNET_CRYPTO_RsaSignature *x)
483{
484 struct GNUNET_PQ_QueryParam res = {
485 .conv = &qconv_rsa_signature,
486 .data = x,
487 .num_params = 1
488 };
489
490 return res;
491}
492
493
494/**
495 * Function called to convert input argument into SQL parameters.
496 *
497 * @param cls closure
498 * @param data pointer to input argument
499 * @param data_len number of bytes in @a data (if applicable)
500 * @param[out] param_values SQL data to set
501 * @param[out] param_lengths SQL length data to set
502 * @param[out] param_formats SQL format data to set
503 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
504 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
505 * @param scratch_length number of entries left in @a scratch
506 * @return -1 on error, number of offsets used in @a scratch otherwise
507 */
508static int
509qconv_rel_time (void *cls,
510 const void *data,
511 size_t data_len,
512 void *param_values[],
513 int param_lengths[],
514 int param_formats[],
515 unsigned int param_length,
516 void *scratch[],
517 unsigned int scratch_length)
518{
519 const struct GNUNET_TIME_Relative *u = data;
520 struct GNUNET_TIME_Relative rel;
521 uint64_t *u_nbo;
522
523 GNUNET_break (NULL == cls);
524 if (1 != param_length)
525 return -1;
526 rel = *u;
527 if (rel.rel_value_us > INT64_MAX)
528 rel.rel_value_us = INT64_MAX;
529 u_nbo = GNUNET_new (uint64_t);
530 scratch[0] = u_nbo;
531 *u_nbo = GNUNET_htonll (rel.rel_value_us);
532 param_values[0] = (void *) u_nbo;
533 param_lengths[0] = sizeof(uint64_t);
534 param_formats[0] = 1;
535 return 1;
536}
537
538
539struct GNUNET_PQ_QueryParam
540GNUNET_PQ_query_param_relative_time (const struct GNUNET_TIME_Relative *x)
541{
542 struct GNUNET_PQ_QueryParam res = {
543 .conv = &qconv_rel_time,
544 .data = x,
545 .size = sizeof(*x),
546 .num_params = 1
547 };
548
549 return res;
550}
551
552
553/**
554 * Function called to convert input argument into SQL parameters.
555 *
556 * @param cls closure
557 * @param data pointer to input argument
558 * @param data_len number of bytes in @a data (if applicable)
559 * @param[out] param_values SQL data to set
560 * @param[out] param_lengths SQL length data to set
561 * @param[out] param_formats SQL format data to set
562 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
563 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
564 * @param scratch_length number of entries left in @a scratch
565 * @return -1 on error, number of offsets used in @a scratch otherwise
566 */
567static int
568qconv_abs_time (void *cls,
569 const void *data,
570 size_t data_len,
571 void *param_values[],
572 int param_lengths[],
573 int param_formats[],
574 unsigned int param_length,
575 void *scratch[],
576 unsigned int scratch_length)
577{
578 const struct GNUNET_TIME_Absolute *u = data;
579 struct GNUNET_TIME_Absolute abs;
580 uint64_t *u_nbo;
581
582 GNUNET_break (NULL == cls);
583 if (1 != param_length)
584 return -1;
585 abs = *u;
586 if (abs.abs_value_us > INT64_MAX)
587 abs.abs_value_us = INT64_MAX;
588 u_nbo = GNUNET_new (uint64_t);
589 scratch[0] = u_nbo;
590 *u_nbo = GNUNET_htonll (abs.abs_value_us);
591 param_values[0] = (void *) u_nbo;
592 param_lengths[0] = sizeof(uint64_t);
593 param_formats[0] = 1;
594 return 1;
595}
596
597
598struct GNUNET_PQ_QueryParam
599GNUNET_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x)
600{
601 struct GNUNET_PQ_QueryParam res = {
602 .conv = &qconv_abs_time,
603 .data = x,
604 .size = sizeof(*x),
605 .num_params = 1
606 };
607
608 return res;
609}
610
611
612struct GNUNET_PQ_QueryParam
613GNUNET_PQ_query_param_absolute_time_nbo (
614 const struct GNUNET_TIME_AbsoluteNBO *x)
615{
616 return GNUNET_PQ_query_param_auto_from_type (&x->abs_value_us__);
617}
618
619
620struct GNUNET_PQ_QueryParam
621GNUNET_PQ_query_param_timestamp (const struct GNUNET_TIME_Timestamp *x)
622{
623 return GNUNET_PQ_query_param_absolute_time (&x->abs_time);
624}
625
626
627struct GNUNET_PQ_QueryParam
628GNUNET_PQ_query_param_timestamp_nbo (
629 const struct GNUNET_TIME_TimestampNBO *x)
630{
631 return GNUNET_PQ_query_param_absolute_time_nbo (&x->abs_time_nbo);
632}
633
634
635/**
636 * Closure for the array type handlers.
637 *
638 * May contain sizes information for the data, given (and handled) by the
639 * caller.
640 */
641struct qconv_array_cls
642{
643 /**
644 * If not null, contains the array of sizes (the size of the array is the
645 * .size field in the ambient GNUNET_PQ_QueryParam struct). We do not free
646 * this memory.
647 *
648 * If not null, this value has precedence over @a sizes, which MUST be NULL */
649 const size_t *sizes;
650
651 /**
652 * If @a size and @a c_sizes are NULL, this field defines the same size
653 * for each element in the array.
654 */
655 size_t same_size;
656
657 /**
658 * If true, the array parameter to the data pointer to the qconv_array is a
659 * continuous byte array of data, either with @a same_size each or sizes
660 * provided bytes by @a sizes;
661 */
662 bool continuous;
663
664 /**
665 * Type of the array elements
666 */
667 enum array_types typ;
668
669 /**
670 * Oid of the array elements
671 */
672 Oid oid;
673};
674
675/**
676 * Callback to cleanup a qconv_array_cls to be used during
677 * GNUNET_PQ_cleanup_query_params_closures
678 */
679static void
680qconv_array_cls_cleanup (void *cls)
681{
682 GNUNET_free (cls);
683}
684
685
686/**
687 * Function called to convert input argument into SQL parameters for arrays
688 *
689 * Note: the format for the encoding of arrays for libpq is not very well
690 * documented. We peeked into various sources (postgresql and libpqtypes) for
691 * guidance.
692 *
693 * @param cls Closure of type struct qconv_array_cls*
694 * @param data Pointer to first element in the array
695 * @param data_len Number of _elements_ in array @a data (if applicable)
696 * @param[out] param_values SQL data to set
697 * @param[out] param_lengths SQL length data to set
698 * @param[out] param_formats SQL format data to set
699 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
700 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
701 * @param scratch_length number of entries left in @a scratch
702 * @return -1 on error, number of offsets used in @a scratch otherwise
703 */
704static int
705qconv_array (
706 void *cls,
707 const void *data,
708 size_t data_len,
709 void *param_values[],
710 int param_lengths[],
711 int param_formats[],
712 unsigned int param_length,
713 void *scratch[],
714 unsigned int scratch_length)
715{
716 struct qconv_array_cls *meta = cls;
717 size_t num = data_len;
718 size_t total_size;
719 const size_t *sizes;
720 bool same_sized;
721 size_t *string_lengths = NULL;
722 void *elements = NULL;
723 bool noerror = true;
724
725 (void) (param_length);
726 (void) (scratch_length);
727
728 GNUNET_assert (NULL != meta);
729 GNUNET_assert (num < INT_MAX);
730
731 sizes = meta->sizes;
732 same_sized = (0 != meta->same_size);
733
734#define RETURN_UNLESS(cond) \
735 do { \
736 if (! (cond)) \
737 { \
738 GNUNET_break ((cond)); \
739 noerror = false; \
740 goto DONE; \
741 } \
742 } while (0)
743
744 /* Calculate sizes and check bounds */
745 {
746 /* num * length-field */
747 size_t x = sizeof(uint32_t);
748 size_t y = x * num;
749 RETURN_UNLESS ((0 == num) || (y / num == x));
750
751 /* size of header */
752 total_size = x = sizeof(struct pq_array_header);
753 total_size += y;
754 RETURN_UNLESS (total_size >= x);
755
756 /* sizes of elements */
757 if (same_sized)
758 {
759 x = num * meta->same_size;
760 RETURN_UNLESS ((0 == num) || (x / num == meta->same_size));
761
762 y = total_size;
763 total_size += x;
764 RETURN_UNLESS (total_size >= y);
765 }
766 else /* sizes are different per element */
767 {
768 /* for an array of strings we need to get their length's first */
769 if (array_of_string == meta->typ)
770 {
771 string_lengths = GNUNET_new_array (num, size_t);
772
773 if (meta->continuous)
774 {
775 const char *ptr = data;
776 for (unsigned int i = 0; i < num; i++)
777 {
778 size_t len = strlen (ptr);
779 string_lengths[i] = len;
780 ptr += len + 1;
781 }
782 }
783 else
784 {
785 const char **str = (const char **) data;
786 for (unsigned int i = 0; i < num; i++)
787 string_lengths[i] = strlen (str[i]);
788 }
789
790 sizes = string_lengths;
791 }
792
793 for (unsigned int i = 0; i < num; i++)
794 {
795 x = total_size;
796 total_size += sizes[i];
797 RETURN_UNLESS (total_size >= x);
798 }
799 }
800
801 RETURN_UNLESS (total_size < INT_MAX);
802
803 elements = GNUNET_malloc (total_size);
804 }
805
806 /* Write data */
807 {
808 char *in = (char *) data;
809 char *out = elements;
810 size_t nullbyte = (array_of_string == meta->typ) ? 1 : 0;
811 struct pq_array_header h = {
812 .ndim = htonl (1), /* We only support one-dimensional arrays */
813 .has_null = htonl (0), /* We do not support NULL entries in arrays */
814 .lbound = htonl (1), /* Default start index value */
815 .dim = htonl (num),
816 .oid = htonl (meta->oid),
817 };
818
819 /* Write header */
820 GNUNET_memcpy (out, &h, sizeof(h));
821 out += sizeof(h);
822
823
824 /* Write elements */
825 for (unsigned int i = 0; i < num; i++)
826 {
827 size_t sz = same_sized ? meta->same_size : sizes[i];
828 size_t hsz = htonl (sz);
829
830 GNUNET_memcpy (out,
831 &hsz,
832 sizeof(hsz));
833 out += sizeof(uint32_t);
834
835 switch (meta->typ)
836 {
837 case array_of_bool:
838 {
839 GNUNET_assert (sizeof(bool) == sz);
840 *(bool *) out = (*(bool *) in);
841 in += sz;
842 break;
843 }
844 case array_of_uint16:
845 {
846 GNUNET_assert (sizeof(uint16_t) == sz);
847 *(uint16_t *) out = htons (*(uint16_t *) in);
848 in += sz;
849 break;
850 }
851 case array_of_uint32:
852 {
853 uint32_t v;
854 GNUNET_assert (sizeof(uint32_t) == sz);
855
856 v = htonl (*(uint32_t *) in);
857 GNUNET_memcpy (out,
858 &v,
859 sizeof(v));
860 in += sz;
861 break;
862 }
863 case array_of_uint64:
864 {
865 uint64_t tmp;
866 GNUNET_assert (sizeof(uint64_t) == sz);
867
868 tmp = GNUNET_htonll (*(uint64_t *) in);
869 GNUNET_memcpy (out,
870 &tmp,
871 sizeof(tmp));
872 in += sz;
873 break;
874 }
875 case array_of_byte:
876 case array_of_string:
877 {
878 const void *ptr;
879
880 if (meta->continuous)
881 {
882 ptr = in;
883 in += sz + nullbyte;
884 }
885 else
886 ptr = ((const void **) data)[i];
887
888 GNUNET_memcpy (out,
889 ptr,
890 sz);
891 break;
892 }
893 case array_of_abs_time:
894 case array_of_rel_time:
895 case array_of_timestamp:
896 {
897 uint64_t val;
898
899 switch (meta->typ)
900 {
901 case array_of_abs_time:
902 {
903 const struct GNUNET_TIME_Absolute *abs =
904 (const struct GNUNET_TIME_Absolute *) in;
905
906 GNUNET_assert (sizeof(struct GNUNET_TIME_Absolute) == sz);
907
908 if (! meta->continuous)
909 abs = ((const struct GNUNET_TIME_Absolute **) data)[i];
910
911 val = abs->abs_value_us;
912 break;
913 }
914 case array_of_rel_time:
915 {
916 const struct GNUNET_TIME_Relative *rel =
917 (const struct GNUNET_TIME_Relative *) in;
918
919 GNUNET_assert (sizeof(struct GNUNET_TIME_Relative) == sz);
920
921 if (! meta->continuous)
922 rel = ((const struct GNUNET_TIME_Relative **) data)[i];
923
924 val = rel->rel_value_us;
925 break;
926 }
927 case array_of_timestamp:
928 {
929 const struct GNUNET_TIME_Timestamp *ts =
930 (const struct GNUNET_TIME_Timestamp *) in;
931
932 GNUNET_assert (sizeof(struct GNUNET_TIME_Timestamp) == sz);
933
934 if (! meta->continuous)
935 ts = ((const struct GNUNET_TIME_Timestamp **) data)[i];
936
937 val = ts->abs_time.abs_value_us;
938 break;
939 }
940 default:
941 {
942 GNUNET_assert (0);
943 }
944 }
945
946 if (val > INT64_MAX)
947 val = INT64_MAX;
948
949 val = GNUNET_htonll (val);
950 GNUNET_memcpy (out,
951 &val,
952 sizeof(val));
953
954 if (meta->continuous)
955 in += sz;
956
957 break;
958 }
959 default:
960 {
961 GNUNET_assert (0);
962 break;
963 }
964 }
965 out += sz;
966 }
967 }
968
969 param_values[0] = elements;
970 param_lengths[0] = total_size;
971 param_formats[0] = 1;
972 scratch[0] = elements;
973
974DONE:
975 GNUNET_free (string_lengths);
976
977 if (noerror)
978 return 1;
979
980 return -1;
981}
982
983
984/**
985 * Function to genreate a typ specific query parameter and corresponding closure
986 *
987 * @param num Number of elements in @a elements
988 * @param continuous If true, @a elements is an continuous array of data
989 * @param elements Array of @a num elements, either continuous or pointers
990 * @param sizes Array of @a num sizes, one per element, may be NULL
991 * @param same_size If not 0, all elements in @a elements have this size
992 * @param typ Supported internal type of each element in @a elements
993 * @param oid Oid of the type to be used in Postgres
994 * @return Query parameter
995 */
996static struct GNUNET_PQ_QueryParam
997query_param_array_generic (
998 unsigned int num,
999 bool continuous,
1000 const void *elements,
1001 const size_t *sizes,
1002 size_t same_size,
1003 enum array_types typ,
1004 Oid oid)
1005{
1006 struct qconv_array_cls *meta = GNUNET_new (struct qconv_array_cls);
1007
1008 meta->typ = typ;
1009 meta->oid = oid;
1010 meta->sizes = sizes;
1011 meta->same_size = same_size;
1012 meta->continuous = continuous;
1013
1014 {
1015 struct GNUNET_PQ_QueryParam res = {
1016 .conv = qconv_array,
1017 .conv_cls = meta,
1018 .conv_cls_cleanup = &qconv_array_cls_cleanup,
1019 .data = elements,
1020 .size = num,
1021 .num_params = 1,
1022 };
1023
1024 return res;
1025 }
1026}
1027
1028
1029struct GNUNET_PQ_QueryParam
1030GNUNET_PQ_query_param_array_bool (
1031 unsigned int num,
1032 const bool *elements,
1033 struct GNUNET_PQ_Context *db)
1034{
1035 Oid oid;
1036
1037 GNUNET_assert (GNUNET_OK ==
1038 GNUNET_PQ_get_oid_by_name (db,
1039 "bool",
1040 &oid));
1041 return query_param_array_generic (num,
1042 true,
1043 elements,
1044 NULL,
1045 sizeof(bool),
1046 array_of_bool,
1047 oid);
1048}
1049
1050
1051struct GNUNET_PQ_QueryParam
1052GNUNET_PQ_query_param_array_uint16 (
1053 unsigned int num,
1054 const uint16_t *elements,
1055 struct GNUNET_PQ_Context *db)
1056{
1057 Oid oid;
1058
1059 GNUNET_assert (GNUNET_OK ==
1060 GNUNET_PQ_get_oid_by_name (db,
1061 "int2",
1062 &oid));
1063 return query_param_array_generic (num,
1064 true,
1065 elements,
1066 NULL,
1067 sizeof(uint16_t),
1068 array_of_uint16,
1069 oid);
1070}
1071
1072
1073struct GNUNET_PQ_QueryParam
1074GNUNET_PQ_query_param_array_uint32 (
1075 unsigned int num,
1076 const uint32_t *elements,
1077 struct GNUNET_PQ_Context *db)
1078{
1079 Oid oid;
1080
1081 GNUNET_assert (GNUNET_OK ==
1082 GNUNET_PQ_get_oid_by_name (db,
1083 "int4",
1084 &oid));
1085 return query_param_array_generic (num,
1086 true,
1087 elements,
1088 NULL,
1089 sizeof(uint32_t),
1090 array_of_uint32,
1091 oid);
1092}
1093
1094
1095struct GNUNET_PQ_QueryParam
1096GNUNET_PQ_query_param_array_uint64 (
1097 unsigned int num,
1098 const uint64_t *elements,
1099 struct GNUNET_PQ_Context *db)
1100{
1101 Oid oid;
1102
1103 GNUNET_assert (GNUNET_OK ==
1104 GNUNET_PQ_get_oid_by_name (db,
1105 "int8",
1106 &oid));
1107 return query_param_array_generic (num,
1108 true,
1109 elements,
1110 NULL,
1111 sizeof(uint64_t),
1112 array_of_uint64,
1113 oid);
1114}
1115
1116
1117struct GNUNET_PQ_QueryParam
1118GNUNET_PQ_query_param_array_bytes (
1119 unsigned int num,
1120 const void *elements,
1121 const size_t *sizes,
1122 struct GNUNET_PQ_Context *db)
1123{
1124 Oid oid;
1125
1126 GNUNET_assert (GNUNET_OK ==
1127 GNUNET_PQ_get_oid_by_name (db,
1128 "bytea",
1129 &oid));
1130 return query_param_array_generic (num,
1131 true,
1132 elements,
1133 sizes,
1134 0,
1135 array_of_byte,
1136 oid);
1137}
1138
1139
1140struct GNUNET_PQ_QueryParam
1141GNUNET_PQ_query_param_array_ptrs_bytes (
1142 unsigned int num,
1143 const void *elements[static num],
1144 const size_t *sizes,
1145 struct GNUNET_PQ_Context *db)
1146{
1147 Oid oid;
1148
1149 GNUNET_assert (GNUNET_OK ==
1150 GNUNET_PQ_get_oid_by_name (db,
1151 "bytea",
1152 &oid));
1153 return query_param_array_generic (num,
1154 false,
1155 elements,
1156 sizes,
1157 0,
1158 array_of_byte,
1159 oid);
1160}
1161
1162
1163struct GNUNET_PQ_QueryParam
1164GNUNET_PQ_query_param_array_bytes_same_size (
1165 unsigned int num,
1166 const void *elements,
1167 size_t same_size,
1168 struct GNUNET_PQ_Context *db)
1169{
1170 Oid oid;
1171
1172 GNUNET_assert (GNUNET_OK ==
1173 GNUNET_PQ_get_oid_by_name (db,
1174 "bytea",
1175 &oid));
1176 return query_param_array_generic (num,
1177 true,
1178 elements,
1179 NULL,
1180 same_size,
1181 array_of_byte,
1182 oid);
1183}
1184
1185
1186struct GNUNET_PQ_QueryParam
1187GNUNET_PQ_query_param_array_ptrs_bytes_same_size (
1188 unsigned int num,
1189 const void *elements[static num],
1190 size_t same_size,
1191 struct GNUNET_PQ_Context *db)
1192{
1193 Oid oid;
1194
1195 GNUNET_assert (GNUNET_OK ==
1196 GNUNET_PQ_get_oid_by_name (db,
1197 "bytea",
1198 &oid));
1199 return query_param_array_generic (num,
1200 false,
1201 elements,
1202 NULL,
1203 same_size,
1204 array_of_byte,
1205 oid);
1206}
1207
1208
1209struct GNUNET_PQ_QueryParam
1210GNUNET_PQ_query_param_array_string (
1211 unsigned int num,
1212 const char *elements,
1213 struct GNUNET_PQ_Context *db)
1214{
1215 Oid oid;
1216
1217 GNUNET_assert (GNUNET_OK ==
1218 GNUNET_PQ_get_oid_by_name (db,
1219 "text",
1220 &oid));
1221 return query_param_array_generic (num,
1222 true,
1223 elements,
1224 NULL,
1225 0,
1226 array_of_string,
1227 oid);
1228}
1229
1230
1231struct GNUNET_PQ_QueryParam
1232GNUNET_PQ_query_param_array_ptrs_string (
1233 unsigned int num,
1234 const char *elements[static num],
1235 struct GNUNET_PQ_Context *db)
1236{
1237 Oid oid;
1238
1239 GNUNET_assert (GNUNET_OK ==
1240 GNUNET_PQ_get_oid_by_name (db,
1241 "text",
1242 &oid));
1243 return query_param_array_generic (num,
1244 false,
1245 elements,
1246 NULL,
1247 0,
1248 array_of_string,
1249 oid);
1250}
1251
1252
1253struct GNUNET_PQ_QueryParam
1254GNUNET_PQ_query_param_array_abs_time (
1255 unsigned int num,
1256 const struct GNUNET_TIME_Absolute *elements,
1257 struct GNUNET_PQ_Context *db)
1258{
1259 Oid oid;
1260
1261 GNUNET_assert (GNUNET_OK ==
1262 GNUNET_PQ_get_oid_by_name (db,
1263 "int8",
1264 &oid));
1265 return query_param_array_generic (num,
1266 true,
1267 elements,
1268 NULL,
1269 sizeof(struct GNUNET_TIME_Absolute),
1270 array_of_abs_time,
1271 oid);
1272}
1273
1274
1275struct GNUNET_PQ_QueryParam
1276GNUNET_PQ_query_param_array_ptrs_abs_time (
1277 unsigned int num,
1278 const struct GNUNET_TIME_Absolute *elements[],
1279 struct GNUNET_PQ_Context *db)
1280{
1281 Oid oid;
1282
1283 GNUNET_assert (GNUNET_OK ==
1284 GNUNET_PQ_get_oid_by_name (db,
1285 "int8",
1286 &oid));
1287 return query_param_array_generic (num,
1288 false,
1289 elements,
1290 NULL,
1291 sizeof(struct GNUNET_TIME_Absolute),
1292 array_of_abs_time,
1293 oid);
1294}
1295
1296
1297struct GNUNET_PQ_QueryParam
1298GNUNET_PQ_query_param_array_rel_time (
1299 unsigned int num,
1300 const struct GNUNET_TIME_Relative *elements,
1301 struct GNUNET_PQ_Context *db)
1302{
1303 Oid oid;
1304
1305 GNUNET_assert (GNUNET_OK ==
1306 GNUNET_PQ_get_oid_by_name (db,
1307 "int8",
1308 &oid));
1309 return query_param_array_generic (num,
1310 true,
1311 elements,
1312 NULL,
1313 sizeof(struct GNUNET_TIME_Relative),
1314 array_of_abs_time,
1315 oid);
1316}
1317
1318
1319struct GNUNET_PQ_QueryParam
1320GNUNET_PQ_query_param_array_ptrs_rel_time (
1321 unsigned int num,
1322 const struct GNUNET_TIME_Relative *elements[],
1323 struct GNUNET_PQ_Context *db)
1324{
1325 Oid oid;
1326
1327 GNUNET_assert (GNUNET_OK ==
1328 GNUNET_PQ_get_oid_by_name (db,
1329 "int8",
1330 &oid));
1331 return query_param_array_generic (num,
1332 false,
1333 elements,
1334 NULL,
1335 sizeof(struct GNUNET_TIME_Relative),
1336 array_of_abs_time,
1337 oid);
1338}
1339
1340
1341struct GNUNET_PQ_QueryParam
1342GNUNET_PQ_query_param_array_timestamp (
1343 unsigned int num,
1344 const struct GNUNET_TIME_Timestamp *elements,
1345 struct GNUNET_PQ_Context *db)
1346{
1347 Oid oid;
1348
1349 GNUNET_assert (GNUNET_OK ==
1350 GNUNET_PQ_get_oid_by_name (db,
1351 "int8",
1352 &oid));
1353 return query_param_array_generic (num,
1354 true,
1355 elements,
1356 NULL,
1357 sizeof(struct GNUNET_TIME_Timestamp),
1358 array_of_timestamp,
1359 oid);
1360}
1361
1362
1363struct GNUNET_PQ_QueryParam
1364GNUNET_PQ_query_param_array_ptrs_timestamp (
1365 unsigned int num,
1366 const struct GNUNET_TIME_Timestamp *elements[],
1367 struct GNUNET_PQ_Context *db)
1368{
1369 Oid oid;
1370
1371 GNUNET_assert (GNUNET_OK ==
1372 GNUNET_PQ_get_oid_by_name (db,
1373 "int8",
1374 &oid));
1375 return query_param_array_generic (num,
1376 false,
1377 elements,
1378 NULL,
1379 sizeof(struct GNUNET_TIME_Timestamp),
1380 array_of_timestamp,
1381 oid);
1382}
1383
1384
1385/**
1386 * Function called to convert input argument into SQL parameters.
1387 *
1388 * @param cls closure
1389 * @param data pointer to input argument
1390 * @param data_len number of bytes in @a data (if applicable)
1391 * @param[out] param_values SQL data to set
1392 * @param[out] param_lengths SQL length data to set
1393 * @param[out] param_formats SQL format data to set
1394 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
1395 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
1396 * @param scratch_length number of entries left in @a scratch
1397 * @return -1 on error, number of offsets used in @a scratch otherwise
1398 */
1399static int
1400qconv_blind_sign_pub (void *cls,
1401 const void *data,
1402 size_t data_len,
1403 void *param_values[],
1404 int param_lengths[],
1405 int param_formats[],
1406 unsigned int param_length,
1407 void *scratch[],
1408 unsigned int scratch_length)
1409{
1410 const struct GNUNET_CRYPTO_BlindSignPublicKey *public_key = data;
1411 size_t tlen;
1412 size_t len;
1413 uint32_t be;
1414 char *buf;
1415 void *tbuf;
1416
1417 (void) cls;
1418 (void) data_len;
1419 GNUNET_assert (1 == param_length);
1420 GNUNET_assert (scratch_length > 0);
1421 GNUNET_break (NULL == cls);
1422 be = htonl ((uint32_t) public_key->cipher);
1423 switch (public_key->cipher)
1424 {
1425 case GNUNET_CRYPTO_BSA_RSA:
1426 tlen = GNUNET_CRYPTO_rsa_public_key_encode (
1427 public_key->details.rsa_public_key,
1428 &tbuf);
1429 break;
1430 case GNUNET_CRYPTO_BSA_CS:
1431 tlen = sizeof (public_key->details.cs_public_key);
1432 break;
1433 default:
1434 GNUNET_assert (0);
1435 }
1436 len = tlen + sizeof (be);
1437 buf = GNUNET_malloc (len);
1438 GNUNET_memcpy (buf,
1439 &be,
1440 sizeof (be));
1441 switch (public_key->cipher)
1442 {
1443 case GNUNET_CRYPTO_BSA_RSA:
1444 GNUNET_memcpy (&buf[sizeof (be)],
1445 tbuf,
1446 tlen);
1447 GNUNET_free (tbuf);
1448 break;
1449 case GNUNET_CRYPTO_BSA_CS:
1450 GNUNET_memcpy (&buf[sizeof (be)],
1451 &public_key->details.cs_public_key,
1452 tlen);
1453 break;
1454 default:
1455 GNUNET_assert (0);
1456 }
1457
1458 scratch[0] = buf;
1459 param_values[0] = (void *) buf;
1460 param_lengths[0] = len;
1461 param_formats[0] = 1;
1462 return 1;
1463}
1464
1465
1466/**
1467 * Generate query parameter for a blind sign public key of variable size.
1468 *
1469 * @param pub pointer to the query parameter to pass
1470 */
1471struct GNUNET_PQ_QueryParam
1472GNUNET_PQ_query_param_blind_sign_pub (
1473 const struct GNUNET_CRYPTO_BlindSignPublicKey *pub)
1474{
1475 struct GNUNET_PQ_QueryParam res = {
1476 .conv = &qconv_blind_sign_pub,
1477 .data = pub,
1478 .num_params = 1
1479 };
1480
1481 return res;
1482}
1483
1484
1485/**
1486 * Function called to convert input argument into SQL parameters.
1487 *
1488 * @param cls closure
1489 * @param data pointer to input argument
1490 * @param data_len number of bytes in @a data (if applicable)
1491 * @param[out] param_values SQL data to set
1492 * @param[out] param_lengths SQL length data to set
1493 * @param[out] param_formats SQL format data to set
1494 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
1495 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
1496 * @param scratch_length number of entries left in @a scratch
1497 * @return -1 on error, number of offsets used in @a scratch otherwise
1498 */
1499static int
1500qconv_blind_sign_priv (void *cls,
1501 const void *data,
1502 size_t data_len,
1503 void *param_values[],
1504 int param_lengths[],
1505 int param_formats[],
1506 unsigned int param_length,
1507 void *scratch[],
1508 unsigned int scratch_length)
1509{
1510 const struct GNUNET_CRYPTO_BlindSignPrivateKey *private_key = data;
1511 size_t tlen;
1512 size_t len;
1513 uint32_t be;
1514 char *buf;
1515 void *tbuf;
1516
1517 (void) cls;
1518 (void) data_len;
1519 GNUNET_assert (1 == param_length);
1520 GNUNET_assert (scratch_length > 0);
1521 GNUNET_break (NULL == cls);
1522 be = htonl ((uint32_t) private_key->cipher);
1523 switch (private_key->cipher)
1524 {
1525 case GNUNET_CRYPTO_BSA_RSA:
1526 tlen = GNUNET_CRYPTO_rsa_private_key_encode (
1527 private_key->details.rsa_private_key,
1528 &tbuf);
1529 break;
1530 case GNUNET_CRYPTO_BSA_CS:
1531 tlen = sizeof (private_key->details.cs_private_key);
1532 break;
1533 default:
1534 GNUNET_assert (0);
1535 }
1536 len = tlen + sizeof (be);
1537 buf = GNUNET_malloc (len);
1538 GNUNET_memcpy (buf,
1539 &be,
1540 sizeof (be));
1541 switch (private_key->cipher)
1542 {
1543 case GNUNET_CRYPTO_BSA_RSA:
1544 GNUNET_memcpy (&buf[sizeof (be)],
1545 tbuf,
1546 tlen);
1547 GNUNET_free (tbuf);
1548 break;
1549 case GNUNET_CRYPTO_BSA_CS:
1550 GNUNET_memcpy (&buf[sizeof (be)],
1551 &private_key->details.cs_private_key,
1552 tlen);
1553 break;
1554 default:
1555 GNUNET_assert (0);
1556 }
1557
1558 scratch[0] = buf;
1559 param_values[0] = (void *) buf;
1560 param_lengths[0] = len;
1561 param_formats[0] = 1;
1562 return 1;
1563}
1564
1565
1566/**
1567 * Generate query parameter for a blind sign private key of variable size.
1568 *
1569 * @param priv pointer to the query parameter to pass
1570 */
1571struct GNUNET_PQ_QueryParam
1572GNUNET_PQ_query_param_blind_sign_priv (
1573 const struct GNUNET_CRYPTO_BlindSignPrivateKey *priv)
1574{
1575 struct GNUNET_PQ_QueryParam res = {
1576 .conv = &qconv_blind_sign_priv,
1577 .data = priv,
1578 .num_params = 1
1579 };
1580
1581 return res;
1582}
1583
1584
1585/* end of pq_query_helper.c */
diff --git a/src/lib/pq/pq_result_helper.c b/src/lib/pq/pq_result_helper.c
new file mode 100644
index 000000000..cbb1e8e8e
--- /dev/null
+++ b/src/lib/pq/pq_result_helper.c
@@ -0,0 +1,2067 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2014-2024 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/**
21 * @file pq/pq_result_helper.c
22 * @brief functions to extract result values
23 * @author Christian Grothoff
24 * @author Özgür Kesim
25 */
26#include "platform.h"
27#include "gnunet_time_lib.h"
28#include "gnunet_common.h"
29#include "gnunet_util_lib.h"
30#include "gnunet_pq_lib.h"
31#include "pq.h"
32
33
34struct GNUNET_PQ_ResultSpec
35GNUNET_PQ_result_spec_allow_null (struct GNUNET_PQ_ResultSpec rs,
36 bool *is_null)
37{
38 struct GNUNET_PQ_ResultSpec rsr;
39
40 rsr = rs;
41 rsr.is_nullable = true;
42 rsr.is_null = is_null;
43 return rsr;
44}
45
46
47/**
48 * Function called to clean up memory allocated
49 * by a #GNUNET_PQ_ResultConverter.
50 *
51 * @param cls closure
52 * @param rd result data to clean up
53 */
54static void
55clean_varsize_blob (void *cls,
56 void *rd)
57{
58 void **dst = rd;
59
60 (void) cls;
61 if (NULL != *dst)
62 {
63 GNUNET_free (*dst);
64 *dst = NULL;
65 }
66}
67
68
69/**
70 * Extract data from a Postgres database @a result at row @a row.
71 *
72 * @param cls closure
73 * @param result where to extract data from
74 * @param row row to extract data from
75 * @param fname name (or prefix) of the fields to extract from
76 * @param[in,out] dst_size where to store size of result, may be NULL
77 * @param[out] dst where to store the result
78 * @return
79 * #GNUNET_YES if all results could be extracted
80 * #GNUNET_SYSERR if a result was invalid (non-existing field)
81 */
82static enum GNUNET_GenericReturnValue
83extract_varsize_blob (void *cls,
84 PGresult *result,
85 int row,
86 const char *fname,
87 size_t *dst_size,
88 void *dst)
89{
90 size_t len;
91 const char *res;
92 void *idst;
93 int fnum;
94
95 (void) cls;
96 *dst_size = 0;
97 *((void **) dst) = NULL;
98
99 fnum = PQfnumber (result,
100 fname);
101 if (fnum < 0)
102 {
103 GNUNET_break (0);
104 return GNUNET_SYSERR;
105 }
106 if (PQgetisnull (result,
107 row,
108 fnum))
109 return GNUNET_NO;
110 /* if a field is null, continue but
111 * remember that we now return a different result */
112 len = PQgetlength (result,
113 row,
114 fnum);
115 res = PQgetvalue (result,
116 row,
117 fnum);
118 GNUNET_assert (NULL != res);
119 *dst_size = len;
120 idst = GNUNET_malloc (len);
121 *((void **) dst) = idst;
122 GNUNET_memcpy (idst,
123 res,
124 len);
125 return GNUNET_OK;
126}
127
128
129struct GNUNET_PQ_ResultSpec
130GNUNET_PQ_result_spec_variable_size (const char *name,
131 void **dst,
132 size_t *sptr)
133{
134 struct GNUNET_PQ_ResultSpec res = {
135 .conv = &extract_varsize_blob,
136 .cleaner = &clean_varsize_blob,
137 .dst = (void *) (dst),
138 .fname = name,
139 .result_size = sptr
140 };
141
142 return res;
143}
144
145
146/**
147 * Extract data from a Postgres database @a result at row @a row.
148 *
149 * @param cls closure
150 * @param result where to extract data from
151 * @param row row to extract data from
152 * @param fname name (or prefix) of the fields to extract from
153 * @param[in] dst_size desired size, never NULL
154 * @param[out] dst where to store the result
155 * @return
156 * #GNUNET_YES if all results could be extracted
157 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
158 */
159static enum GNUNET_GenericReturnValue
160extract_fixed_blob (void *cls,
161 PGresult *result,
162 int row,
163 const char *fname,
164 size_t *dst_size,
165 void *dst)
166{
167 size_t len;
168 const char *res;
169 int fnum;
170
171 (void) cls;
172 fnum = PQfnumber (result,
173 fname);
174 if (fnum < 0)
175 {
176 GNUNET_break (0);
177 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
178 "Result does not have field %s\n",
179 fname);
180 return GNUNET_SYSERR;
181 }
182 if (PQgetisnull (result,
183 row,
184 fnum))
185 return GNUNET_NO;
186 /* if a field is null, continue but
187 * remember that we now return a different result */
188 len = PQgetlength (result,
189 row,
190 fnum);
191 if (*dst_size != len)
192 {
193 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
194 "Expected %u bytes for field `%s', got %u\n",
195 (unsigned int) *dst_size,
196 fname,
197 (unsigned int) len);
198 GNUNET_break (0);
199 return GNUNET_SYSERR;
200 }
201 res = PQgetvalue (result,
202 row,
203 fnum);
204 GNUNET_assert (NULL != res);
205 GNUNET_memcpy (dst,
206 res,
207 len);
208 return GNUNET_OK;
209}
210
211
212struct GNUNET_PQ_ResultSpec
213GNUNET_PQ_result_spec_fixed_size (const char *name,
214 void *dst,
215 size_t dst_size)
216{
217 struct GNUNET_PQ_ResultSpec res = {
218 .conv = &extract_fixed_blob,
219 .dst = (dst),
220 .dst_size = dst_size,
221 .fname = name
222 };
223
224 return res;
225}
226
227
228/**
229 * Extract data from a Postgres database @a result at row @a row.
230 *
231 * @param cls closure
232 * @param result where to extract data from
233 * @param row row to extract data from
234 * @param fname name (or prefix) of the fields to extract from
235 * @param[in,out] dst_size where to store size of result, may be NULL
236 * @param[out] dst where to store the result
237 * @return
238 * #GNUNET_YES if all results could be extracted
239 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
240 */
241static enum GNUNET_GenericReturnValue
242extract_rsa_public_key (void *cls,
243 PGresult *result,
244 int row,
245 const char *fname,
246 size_t *dst_size,
247 void *dst)
248{
249 struct GNUNET_CRYPTO_RsaPublicKey **pk = dst;
250 size_t len;
251 const char *res;
252 int fnum;
253
254 (void) cls;
255 *pk = NULL;
256 fnum = PQfnumber (result,
257 fname);
258 if (fnum < 0)
259 {
260 GNUNET_break (0);
261 return GNUNET_SYSERR;
262 }
263 if (PQgetisnull (result,
264 row,
265 fnum))
266 return GNUNET_NO;
267
268 /* if a field is null, continue but
269 * remember that we now return a different result */
270 len = PQgetlength (result,
271 row,
272 fnum);
273 res = PQgetvalue (result,
274 row,
275 fnum);
276 *pk = GNUNET_CRYPTO_rsa_public_key_decode (res,
277 len);
278 if (NULL == *pk)
279 {
280 GNUNET_break (0);
281 return GNUNET_SYSERR;
282 }
283 return GNUNET_OK;
284}
285
286
287/**
288 * Function called to clean up memory allocated
289 * by a #GNUNET_PQ_ResultConverter.
290 *
291 * @param cls closure
292 * @param rd result data to clean up
293 */
294static void
295clean_rsa_public_key (void *cls,
296 void *rd)
297{
298 struct GNUNET_CRYPTO_RsaPublicKey **pk = rd;
299
300 (void) cls;
301 if (NULL != *pk)
302 {
303 GNUNET_CRYPTO_rsa_public_key_free (*pk);
304 *pk = NULL;
305 }
306}
307
308
309struct GNUNET_PQ_ResultSpec
310GNUNET_PQ_result_spec_rsa_public_key (const char *name,
311 struct GNUNET_CRYPTO_RsaPublicKey **rsa)
312{
313 struct GNUNET_PQ_ResultSpec res = {
314 .conv = &extract_rsa_public_key,
315 .cleaner = &clean_rsa_public_key,
316 .dst = (void *) rsa,
317 .fname = name
318 };
319
320 return res;
321}
322
323
324/**
325 * Extract data from a Postgres database @a result at row @a row.
326 *
327 * @param cls closure
328 * @param result where to extract data from
329 * @param row row to extract data from
330 * @param fname name (or prefix) of the fields to extract from
331 * @param[in,out] dst_size where to store size of result, may be NULL
332 * @param[out] dst where to store the result
333 * @return
334 * #GNUNET_YES if all results could be extracted
335 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
336 */
337static enum GNUNET_GenericReturnValue
338extract_rsa_signature (void *cls,
339 PGresult *result,
340 int row,
341 const char *fname,
342 size_t *dst_size,
343 void *dst)
344{
345 struct GNUNET_CRYPTO_RsaSignature **sig = dst;
346 size_t len;
347 const void *res;
348 int fnum;
349
350 (void) cls;
351 *sig = NULL;
352 fnum = PQfnumber (result,
353 fname);
354 if (fnum < 0)
355 {
356 GNUNET_break (0);
357 return GNUNET_SYSERR;
358 }
359 if (PQgetisnull (result,
360 row,
361 fnum))
362 return GNUNET_NO;
363 /* if a field is null, continue but
364 * remember that we now return a different result */
365 len = PQgetlength (result,
366 row,
367 fnum);
368 res = PQgetvalue (result,
369 row,
370 fnum);
371 *sig = GNUNET_CRYPTO_rsa_signature_decode (res,
372 len);
373 if (NULL == *sig)
374 {
375 GNUNET_break (0);
376 return GNUNET_SYSERR;
377 }
378 return GNUNET_OK;
379}
380
381
382/**
383 * Function called to clean up memory allocated
384 * by a #GNUNET_PQ_ResultConverter.
385 *
386 * @param cls closure
387 * @param rd result data to clean up
388 */
389static void
390clean_rsa_signature (void *cls,
391 void *rd)
392{
393 struct GNUNET_CRYPTO_RsaSignature **sig = rd;
394
395 (void) cls;
396 if (NULL != *sig)
397 {
398 GNUNET_CRYPTO_rsa_signature_free (*sig);
399 *sig = NULL;
400 }
401}
402
403
404struct GNUNET_PQ_ResultSpec
405GNUNET_PQ_result_spec_rsa_signature (const char *name,
406 struct GNUNET_CRYPTO_RsaSignature **sig)
407{
408 struct GNUNET_PQ_ResultSpec res = {
409 .conv = &extract_rsa_signature,
410 .cleaner = &clean_rsa_signature,
411 .dst = (void *) sig,
412 .fname = name
413 };
414
415 return res;
416}
417
418
419/**
420 * Extract data from a Postgres database @a result at row @a row.
421 *
422 * @param cls closure
423 * @param result where to extract data from
424 * @param row row to extract data from
425 * @param fname name (or prefix) of the fields to extract from
426 * @param[in,out] dst_size where to store size of result, may be NULL
427 * @param[out] dst where to store the result
428 * @return
429 * #GNUNET_YES if all results could be extracted
430 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
431 */
432static enum GNUNET_GenericReturnValue
433extract_string (void *cls,
434 PGresult *result,
435 int row,
436 const char *fname,
437 size_t *dst_size,
438 void *dst)
439{
440 char **str = dst;
441 size_t len;
442 const char *res;
443 int fnum;
444
445 (void) cls;
446 *str = NULL;
447 fnum = PQfnumber (result,
448 fname);
449 if (fnum < 0)
450 {
451 GNUNET_break (0);
452 return GNUNET_SYSERR;
453 }
454 if (PQgetisnull (result,
455 row,
456 fnum))
457 return GNUNET_NO;
458 /* if a field is null, continue but
459 * remember that we now return a different result */
460 len = PQgetlength (result,
461 row,
462 fnum);
463 res = PQgetvalue (result,
464 row,
465 fnum);
466 *str = GNUNET_strndup (res,
467 len);
468 if (NULL == *str)
469 {
470 GNUNET_break (0);
471 return GNUNET_SYSERR;
472 }
473 return GNUNET_OK;
474}
475
476
477/**
478 * Function called to clean up memory allocated
479 * by a #GNUNET_PQ_ResultConverter.
480 *
481 * @param cls closure
482 * @param rd result data to clean up
483 */
484static void
485clean_string (void *cls,
486 void *rd)
487{
488 char **str = rd;
489
490 (void) cls;
491 if (NULL != *str)
492 {
493 GNUNET_free (*str);
494 *str = NULL;
495 }
496}
497
498
499struct GNUNET_PQ_ResultSpec
500GNUNET_PQ_result_spec_string (const char *name,
501 char **dst)
502{
503 struct GNUNET_PQ_ResultSpec res = {
504 .conv = &extract_string,
505 .cleaner = &clean_string,
506 .dst = (void *) dst,
507 .fname = (name)
508 };
509
510 return res;
511}
512
513
514/**
515 * Extract data from a Postgres database @a result at row @a row.
516 *
517 * @param cls closure
518 * @param result where to extract data from
519 * @param row row to extract data from
520 * @param fname name (or prefix) of the fields to extract from
521 * @param[in,out] dst_size where to store size of result, may be NULL
522 * @param[out] dst where to store the result
523 * @return
524 * #GNUNET_YES if all results could be extracted
525 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
526 */
527static enum GNUNET_GenericReturnValue
528extract_bool (void *cls,
529 PGresult *result,
530 int row,
531 const char *fname,
532 size_t *dst_size,
533 void *dst)
534{
535 bool *b = dst;
536 const uint8_t *res;
537 int fnum;
538 size_t len;
539
540 (void) cls;
541 fnum = PQfnumber (result,
542 fname);
543 if (fnum < 0)
544 {
545 GNUNET_break (0);
546 return GNUNET_SYSERR;
547 }
548 if (PQgetisnull (result,
549 row,
550 fnum))
551 return GNUNET_NO;
552 /* if a field is null, continue but
553 * remember that we now return a different result */
554 len = PQgetlength (result,
555 row,
556 fnum);
557 if (sizeof (uint8_t) != len)
558 {
559 GNUNET_break (0);
560 return GNUNET_SYSERR;
561 }
562 res = (const uint8_t *) PQgetvalue (result,
563 row,
564 fnum);
565 *b = (0 != *res);
566 return GNUNET_OK;
567}
568
569
570struct GNUNET_PQ_ResultSpec
571GNUNET_PQ_result_spec_bool (const char *name,
572 bool *dst)
573{
574 struct GNUNET_PQ_ResultSpec res = {
575 .conv = &extract_bool,
576 .dst = (void *) dst,
577 .fname = name
578 };
579
580 return res;
581}
582
583
584/**
585 * Extract data from a Postgres database @a result at row @a row.
586 *
587 * @param cls closure
588 * @param result where to extract data from
589 * @param row row to extract data from
590 * @param fname name (or prefix) of the fields to extract from
591 * @param[in,out] dst_size where to store size of result, may be NULL
592 * @param[out] dst where to store the result
593 * @return
594 * #GNUNET_YES if all results could be extracted
595 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
596 */
597static enum GNUNET_GenericReturnValue
598extract_rel_time (void *cls,
599 PGresult *result,
600 int row,
601 const char *fname,
602 size_t *dst_size,
603 void *dst)
604{
605 struct GNUNET_TIME_Relative *udst = dst;
606 const int64_t *res;
607 int fnum;
608
609 (void) cls;
610 fnum = PQfnumber (result,
611 fname);
612 if (fnum < 0)
613 {
614 GNUNET_break (0);
615 return GNUNET_SYSERR;
616 }
617 if (PQgetisnull (result,
618 row,
619 fnum))
620 return GNUNET_NO;
621 GNUNET_assert (NULL != dst);
622 if (sizeof(struct GNUNET_TIME_Relative) != *dst_size)
623 {
624 GNUNET_break (0);
625 return GNUNET_SYSERR;
626 }
627 if (sizeof(int64_t) !=
628 PQgetlength (result,
629 row,
630 fnum))
631 {
632 GNUNET_break (0);
633 return GNUNET_SYSERR;
634 }
635 res = (int64_t *) PQgetvalue (result,
636 row,
637 fnum);
638 if (INT64_MAX == GNUNET_ntohll ((uint64_t) *res))
639 *udst = GNUNET_TIME_UNIT_FOREVER_REL;
640 else
641 udst->rel_value_us = GNUNET_ntohll ((uint64_t) *res);
642 return GNUNET_OK;
643}
644
645
646struct GNUNET_PQ_ResultSpec
647GNUNET_PQ_result_spec_relative_time (const char *name,
648 struct GNUNET_TIME_Relative *rt)
649{
650 struct GNUNET_PQ_ResultSpec res = {
651 .conv = &extract_rel_time,
652 .dst = (void *) rt,
653 .dst_size = sizeof(*rt),
654 .fname = name
655 };
656
657 return res;
658}
659
660
661/**
662 * Extract data from a Postgres database @a result at row @a row.
663 *
664 * @param cls closure
665 * @param result where to extract data from
666 * @param row row to extract data from
667 * @param fname name (or prefix) of the fields to extract from
668 * @param[in,out] dst_size where to store size of result, may be NULL
669 * @param[out] dst where to store the result
670 * @return
671 * #GNUNET_YES if all results could be extracted
672 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
673 */
674static enum GNUNET_GenericReturnValue
675extract_abs_time (void *cls,
676 PGresult *result,
677 int row,
678 const char *fname,
679 size_t *dst_size,
680 void *dst)
681{
682 struct GNUNET_TIME_Absolute *udst = dst;
683 const int64_t *res;
684 int fnum;
685
686 (void) cls;
687 fnum = PQfnumber (result,
688 fname);
689 if (fnum < 0)
690 {
691 GNUNET_break (0);
692 return GNUNET_SYSERR;
693 }
694 if (PQgetisnull (result,
695 row,
696 fnum))
697 return GNUNET_NO;
698 GNUNET_assert (NULL != dst);
699 if (sizeof(struct GNUNET_TIME_Absolute) != *dst_size)
700 {
701 GNUNET_break (0);
702 return GNUNET_SYSERR;
703 }
704 if (sizeof(int64_t) !=
705 PQgetlength (result,
706 row,
707 fnum))
708 {
709 GNUNET_break (0);
710 return GNUNET_SYSERR;
711 }
712 res = (int64_t *) PQgetvalue (result,
713 row,
714 fnum);
715 if (INT64_MAX == GNUNET_ntohll ((uint64_t) *res))
716 *udst = GNUNET_TIME_UNIT_FOREVER_ABS;
717 else
718 udst->abs_value_us = GNUNET_ntohll ((uint64_t) *res);
719 return GNUNET_OK;
720}
721
722
723struct GNUNET_PQ_ResultSpec
724GNUNET_PQ_result_spec_absolute_time (const char *name,
725 struct GNUNET_TIME_Absolute *at)
726{
727 struct GNUNET_PQ_ResultSpec res = {
728 .conv = &extract_abs_time,
729 .dst = (void *) at,
730 .dst_size = sizeof(*at),
731 .fname = name
732 };
733
734 return res;
735}
736
737
738struct GNUNET_PQ_ResultSpec
739GNUNET_PQ_result_spec_absolute_time_nbo (const char *name,
740 struct GNUNET_TIME_AbsoluteNBO *at)
741{
742 struct GNUNET_PQ_ResultSpec res =
743 GNUNET_PQ_result_spec_auto_from_type (name,
744 &at->abs_value_us__);
745
746 return res;
747}
748
749
750/**
751 * Extract data from a Postgres database @a result at row @a row.
752 *
753 * @param cls closure
754 * @param result where to extract data from
755 * @param row row to extract data from
756 * @param fname name (or prefix) of the fields to extract from
757 * @param[in,out] dst_size where to store size of result, may be NULL
758 * @param[out] dst where to store the result
759 * @return
760 * #GNUNET_YES if all results could be extracted
761 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
762 */
763static enum GNUNET_GenericReturnValue
764extract_timestamp (void *cls,
765 PGresult *result,
766 int row,
767 const char *fname,
768 size_t *dst_size,
769 void *dst)
770{
771 struct GNUNET_TIME_Timestamp *udst = dst;
772 struct GNUNET_TIME_Absolute abs;
773 const int64_t *res;
774 int fnum;
775
776 (void) cls;
777 fnum = PQfnumber (result,
778 fname);
779 if (fnum < 0)
780 {
781 GNUNET_break (0);
782 return GNUNET_SYSERR;
783 }
784 if (PQgetisnull (result,
785 row,
786 fnum))
787 return GNUNET_NO;
788 GNUNET_assert (NULL != dst);
789 if (sizeof(struct GNUNET_TIME_Absolute) != *dst_size)
790 {
791 GNUNET_break (0);
792 return GNUNET_SYSERR;
793 }
794 if (sizeof(int64_t) !=
795 PQgetlength (result,
796 row,
797 fnum))
798 {
799 GNUNET_break (0);
800 return GNUNET_SYSERR;
801 }
802 res = (int64_t *) PQgetvalue (result,
803 row,
804 fnum);
805 if (INT64_MAX == GNUNET_ntohll ((uint64_t) *res))
806 {
807 abs = GNUNET_TIME_UNIT_FOREVER_ABS;
808 }
809 else
810 {
811 abs.abs_value_us = GNUNET_ntohll ((uint64_t) *res);
812 if (0 != abs.abs_value_us % GNUNET_TIME_UNIT_SECONDS.rel_value_us)
813 {
814 /* timestamps must be multiple of seconds! */
815 GNUNET_break (0);
816 return GNUNET_SYSERR;
817 }
818 }
819 udst->abs_time = abs;
820 return GNUNET_OK;
821}
822
823
824struct GNUNET_PQ_ResultSpec
825GNUNET_PQ_result_spec_timestamp (const char *name,
826 struct GNUNET_TIME_Timestamp *at)
827{
828 struct GNUNET_PQ_ResultSpec res = {
829 .conv = &extract_timestamp,
830 .dst = (void *) at,
831 .dst_size = sizeof(*at),
832 .fname = name
833 };
834
835 return res;
836}
837
838
839/**
840 * Extract data from a Postgres database @a result at row @a row.
841 *
842 * @param cls closure
843 * @param result where to extract data from
844 * @param row row to extract data from
845 * @param fname name (or prefix) of the fields to extract from
846 * @param[in,out] dst_size where to store size of result, may be NULL
847 * @param[out] dst where to store the result
848 * @return
849 * #GNUNET_YES if all results could be extracted
850 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
851 */
852static enum GNUNET_GenericReturnValue
853extract_timestamp_nbo (void *cls,
854 PGresult *result,
855 int row,
856 const char *fname,
857 size_t *dst_size,
858 void *dst)
859{
860 struct GNUNET_TIME_TimestampNBO *udst = dst;
861 struct GNUNET_TIME_Timestamp t;
862 enum GNUNET_GenericReturnValue r;
863
864 r = extract_timestamp (NULL,
865 result,
866 row,
867 fname,
868 dst_size,
869 &t);
870 if (GNUNET_OK != r)
871 return r;
872 *udst = GNUNET_TIME_timestamp_hton (t);
873 return r;
874}
875
876
877struct GNUNET_PQ_ResultSpec
878GNUNET_PQ_result_spec_timestamp_nbo (const char *name,
879 struct GNUNET_TIME_TimestampNBO *at)
880{
881 struct GNUNET_PQ_ResultSpec res = {
882 .conv = &extract_timestamp_nbo,
883 .dst = (void *) at,
884 .dst_size = sizeof(*at),
885 .fname = name
886 };
887
888 return res;
889}
890
891
892/**
893 * Extract data from a Postgres database @a result at row @a row.
894 *
895 * @param cls closure
896 * @param result where to extract data from
897 * @param row row to extract data from
898 * @param fname name (or prefix) of the fields to extract from
899 * @param[in,out] dst_size where to store size of result, may be NULL
900 * @param[out] dst where to store the result
901 * @return
902 * #GNUNET_YES if all results could be extracted
903 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
904 */
905static enum GNUNET_GenericReturnValue
906extract_uint16 (void *cls,
907 PGresult *result,
908 int row,
909 const char *fname,
910 size_t *dst_size,
911 void *dst)
912{
913 uint16_t *udst = dst;
914 const uint16_t *res;
915 int fnum;
916
917 (void) cls;
918 fnum = PQfnumber (result,
919 fname);
920 if (fnum < 0)
921 {
922 GNUNET_break (0);
923 return GNUNET_SYSERR;
924 }
925 if (PQgetisnull (result,
926 row,
927 fnum))
928 return GNUNET_NO;
929 GNUNET_assert (NULL != dst);
930 if (sizeof(uint16_t) != *dst_size)
931 {
932 GNUNET_break (0);
933 return GNUNET_SYSERR;
934 }
935 if (sizeof(uint16_t) !=
936 PQgetlength (result,
937 row,
938 fnum))
939 {
940 GNUNET_break (0);
941 return GNUNET_SYSERR;
942 }
943 res = (uint16_t *) PQgetvalue (result,
944 row,
945 fnum);
946 *udst = ntohs (*res);
947 return GNUNET_OK;
948}
949
950
951struct GNUNET_PQ_ResultSpec
952GNUNET_PQ_result_spec_uint16 (const char *name,
953 uint16_t *u16)
954{
955 struct GNUNET_PQ_ResultSpec res = {
956 .conv = &extract_uint16,
957 .dst = (void *) u16,
958 .dst_size = sizeof(*u16),
959 .fname = name
960 };
961
962 return res;
963}
964
965
966/**
967 * Extract data from a Postgres database @a result at row @a row.
968 *
969 * @param cls closure
970 * @param result where to extract data from
971 * @param row row to extract data from
972 * @param fname name (or prefix) of the fields to extract from
973 * @param[in,out] dst_size where to store size of result, may be NULL
974 * @param[out] dst where to store the result
975 * @return
976 * #GNUNET_YES if all results could be extracted
977 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
978 */
979static enum GNUNET_GenericReturnValue
980extract_uint32 (void *cls,
981 PGresult *result,
982 int row,
983 const char *fname,
984 size_t *dst_size,
985 void *dst)
986{
987 uint32_t *udst = dst;
988 const uint32_t *res;
989 int fnum;
990
991 (void) cls;
992 fnum = PQfnumber (result,
993 fname);
994 if (fnum < 0)
995 {
996 GNUNET_break (0);
997 return GNUNET_SYSERR;
998 }
999 if (PQgetisnull (result,
1000 row,
1001 fnum))
1002 return GNUNET_NO;
1003 GNUNET_assert (NULL != dst);
1004 if (sizeof(uint32_t) != *dst_size)
1005 {
1006 GNUNET_break (0);
1007 return GNUNET_SYSERR;
1008 }
1009 if (sizeof(uint32_t) !=
1010 PQgetlength (result,
1011 row,
1012 fnum))
1013 {
1014 GNUNET_break (0);
1015 return GNUNET_SYSERR;
1016 }
1017 res = (uint32_t *) PQgetvalue (result,
1018 row,
1019 fnum);
1020 *udst = ntohl (*res);
1021 return GNUNET_OK;
1022}
1023
1024
1025struct GNUNET_PQ_ResultSpec
1026GNUNET_PQ_result_spec_uint32 (const char *name,
1027 uint32_t *u32)
1028{
1029 struct GNUNET_PQ_ResultSpec res = {
1030 .conv = &extract_uint32,
1031 .dst = (void *) u32,
1032 .dst_size = sizeof(*u32),
1033 .fname = name
1034 };
1035
1036 return res;
1037}
1038
1039
1040/**
1041 * Extract data from a Postgres database @a result at row @a row.
1042 *
1043 * @param cls closure
1044 * @param result where to extract data from
1045 * @param row row to extract data from
1046 * @param fname name (or prefix) of the fields to extract from
1047 * @param[in,out] dst_size where to store size of result, may be NULL
1048 * @param[out] dst where to store the result
1049 * @return
1050 * #GNUNET_YES if all results could be extracted
1051 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
1052 */
1053static enum GNUNET_GenericReturnValue
1054extract_uint64 (void *cls,
1055 PGresult *result,
1056 int row,
1057 const char *fname,
1058 size_t *dst_size,
1059 void *dst)
1060{
1061 uint64_t *udst = dst;
1062 const uint64_t *res;
1063 int fnum;
1064
1065 (void) cls;
1066 fnum = PQfnumber (result,
1067 fname);
1068 if (fnum < 0)
1069 {
1070 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1071 "Field %s missing in result\n",
1072 fname);
1073 GNUNET_break (0);
1074 return GNUNET_SYSERR;
1075 }
1076 if (PQgetisnull (result,
1077 row,
1078 fnum))
1079 return GNUNET_NO;
1080
1081 GNUNET_assert (NULL != dst);
1082 if (sizeof(uint64_t) != *dst_size)
1083 {
1084 GNUNET_break (0);
1085 return GNUNET_SYSERR;
1086 }
1087 if (sizeof(uint64_t) !=
1088 PQgetlength (result,
1089 row,
1090 fnum))
1091 {
1092 GNUNET_break (0);
1093 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1094 "Got length %u for field `%s'\n",
1095 PQgetlength (result,
1096 row,
1097 fnum),
1098 fname);
1099 return GNUNET_SYSERR;
1100 }
1101 res = (uint64_t *) PQgetvalue (result,
1102 row,
1103 fnum);
1104 *udst = GNUNET_ntohll (*res);
1105 return GNUNET_OK;
1106}
1107
1108
1109struct GNUNET_PQ_ResultSpec
1110GNUNET_PQ_result_spec_uint64 (const char *name,
1111 uint64_t *u64)
1112{
1113 struct GNUNET_PQ_ResultSpec res = {
1114 .conv = &extract_uint64,
1115 .dst = (void *) u64,
1116 .dst_size = sizeof(*u64),
1117 .fname = name
1118 };
1119
1120 return res;
1121}
1122
1123
1124/**
1125 * Extract data from a Postgres database @a result at row @a row.
1126 *
1127 * @param cls closure
1128 * @param result where to extract data from
1129 * @param row row to extract data from
1130 * @param fname name (or prefix) of the fields to extract from
1131 * @param[in,out] dst_size where to store size of result, may be NULL
1132 * @param[out] dst where to store the result
1133 * @return
1134 * #GNUNET_YES if all results could be extracted
1135 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
1136 */
1137static enum GNUNET_GenericReturnValue
1138extract_int64 (void *cls,
1139 PGresult *result,
1140 int row,
1141 const char *fname,
1142 size_t *dst_size,
1143 void *dst)
1144{
1145 int64_t *udst = dst;
1146 const int64_t *res;
1147 int fnum;
1148
1149 (void) cls;
1150 fnum = PQfnumber (result,
1151 fname);
1152 if (fnum < 0)
1153 {
1154 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1155 "Field %s missing in result\n",
1156 fname);
1157 GNUNET_break (0);
1158 return GNUNET_SYSERR;
1159 }
1160 if (PQgetisnull (result,
1161 row,
1162 fnum))
1163 return GNUNET_NO;
1164
1165 GNUNET_assert (NULL != dst);
1166 if (sizeof(int64_t) != *dst_size)
1167 {
1168 GNUNET_break (0);
1169 return GNUNET_SYSERR;
1170 }
1171 if (sizeof(int64_t) !=
1172 PQgetlength (result,
1173 row,
1174 fnum))
1175 {
1176 GNUNET_break (0);
1177 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1178 "Got length %u for field `%s'\n",
1179 PQgetlength (result,
1180 row,
1181 fnum),
1182 fname);
1183 return GNUNET_SYSERR;
1184 }
1185 res = (int64_t *) PQgetvalue (result,
1186 row,
1187 fnum);
1188 *udst = GNUNET_ntohll (*res);
1189 return GNUNET_OK;
1190}
1191
1192
1193struct GNUNET_PQ_ResultSpec
1194GNUNET_PQ_result_spec_int64 (const char *name,
1195 int64_t *i64)
1196{
1197 struct GNUNET_PQ_ResultSpec res = {
1198 .conv = &extract_int64,
1199 .dst = (void *) i64,
1200 .dst_size = sizeof(*i64),
1201 .fname = name
1202 };
1203
1204 return res;
1205}
1206
1207
1208/**
1209 * Closure for the array result specifications. Contains type information
1210 * for the generic parser extract_array_generic and out-pointers for the results.
1211 */
1212struct array_result_cls
1213{
1214 /* Oid of the expected type, must match the oid in the header of the PQResult struct */
1215 Oid oid;
1216
1217 /* Target type */
1218 enum array_types typ;
1219
1220 /* If not 0, defines the expected size of each entry */
1221 size_t same_size;
1222
1223 /* Out-pointer to write the number of elements in the array */
1224 size_t *num;
1225
1226 /* Out-pointer. If @a typ is array_of_byte and @a same_size is 0,
1227 * allocate and put the array of @a num sizes here. NULL otherwise */
1228 size_t **sizes;
1229};
1230
1231
1232/**
1233 * Extract data from a Postgres database @a result as array of a specific type
1234 * from row @a row. The type information and optionally additional
1235 * out-parameters are given in @a cls which is of type array_result_cls.
1236 *
1237 * @param cls closure of type array_result_cls
1238 * @param result where to extract data from
1239 * @param row row to extract data from
1240 * @param fname name (or prefix) of the fields to extract from
1241 * @param[in,out] dst_size where to store size of result, may be NULL
1242 * @param[out] dst where to store the result
1243 * @return
1244 * #GNUNET_YES if all results could be extracted
1245 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
1246 */
1247static enum GNUNET_GenericReturnValue
1248extract_array_generic (
1249 void *cls,
1250 PGresult *result,
1251 int row,
1252 const char *fname,
1253 size_t *dst_size,
1254 void *dst)
1255{
1256 const struct array_result_cls *info = cls;
1257 int data_sz;
1258 char *data;
1259 void *out = NULL;
1260 struct pq_array_header header;
1261 int col_num;
1262
1263 GNUNET_assert (NULL != dst);
1264 *((void **) dst) = NULL;
1265
1266 #define FAIL_IF(cond) \
1267 do { \
1268 if ((cond)) \
1269 { \
1270 GNUNET_break (! (cond)); \
1271 goto FAIL; \
1272 } \
1273 } while (0)
1274
1275 col_num = PQfnumber (result, fname);
1276 FAIL_IF (0 > col_num);
1277
1278 data_sz = PQgetlength (result, row, col_num);
1279 FAIL_IF (0 > data_sz);
1280 FAIL_IF (sizeof(header) > (size_t) data_sz);
1281
1282 data = PQgetvalue (result, row, col_num);
1283 FAIL_IF (NULL == data);
1284
1285 {
1286 struct pq_array_header *h =
1287 (struct pq_array_header *) data;
1288
1289 header.ndim = ntohl (h->ndim);
1290 header.has_null = ntohl (h->has_null);
1291 header.oid = ntohl (h->oid);
1292 header.dim = ntohl (h->dim);
1293 header.lbound = ntohl (h->lbound);
1294
1295 FAIL_IF (1 != header.ndim);
1296 FAIL_IF (INT_MAX <= header.dim);
1297 FAIL_IF (0 != header.has_null);
1298 FAIL_IF (1 != header.lbound);
1299 FAIL_IF (info->oid != header.oid);
1300 }
1301
1302 if (NULL != info->num)
1303 *info->num = header.dim;
1304
1305 {
1306 char *in = data + sizeof(header);
1307
1308 switch (info->typ)
1309 {
1310 case array_of_bool:
1311 if (NULL != dst_size)
1312 *dst_size = sizeof(bool) * (header.dim);
1313 out = GNUNET_new_array (header.dim, bool);
1314 *((void **) dst) = out;
1315 for (uint32_t i = 0; i < header.dim; i++)
1316 {
1317 size_t sz = ntohl (*(uint32_t *) in);
1318 FAIL_IF (sz != sizeof(bool));
1319 in += sizeof(uint32_t);
1320 *(bool *) out = *(bool *) in;
1321 in += sz;
1322 out += sz;
1323 }
1324 break;
1325
1326 case array_of_uint16:
1327 if (NULL != dst_size)
1328 *dst_size = sizeof(uint16_t) * (header.dim);
1329 out = GNUNET_new_array (header.dim, uint16_t);
1330 *((void **) dst) = out;
1331 for (uint32_t i = 0; i < header.dim; i++)
1332 {
1333 size_t sz = ntohl (*(uint32_t *) in);
1334 FAIL_IF (sz != sizeof(uint16_t));
1335 in += sizeof(uint32_t);
1336 *(uint16_t *) out = ntohs (*(uint16_t *) in);
1337 in += sz;
1338 out += sz;
1339 }
1340 break;
1341
1342 case array_of_uint32:
1343 if (NULL != dst_size)
1344 *dst_size = sizeof(uint32_t) * (header.dim);
1345 out = GNUNET_new_array (header.dim, uint32_t);
1346 *((void **) dst) = out;
1347 for (uint32_t i = 0; i < header.dim; i++)
1348 {
1349 size_t sz = ntohl (*(uint32_t *) in);
1350 FAIL_IF (sz != sizeof(uint32_t));
1351 in += sizeof(uint32_t);
1352 *(uint32_t *) out = ntohl (*(uint32_t *) in);
1353 in += sz;
1354 out += sz;
1355 }
1356 break;
1357
1358 case array_of_uint64:
1359 if (NULL != dst_size)
1360 *dst_size = sizeof(uint64_t) * (header.dim);
1361 out = GNUNET_new_array (header.dim, uint64_t);
1362 *((void **) dst) = out;
1363 for (uint32_t i = 0; i < header.dim; i++)
1364 {
1365 size_t sz = ntohl (*(uint32_t *) in);
1366 FAIL_IF (sz != sizeof(uint64_t));
1367 in += sizeof(uint32_t);
1368 *(uint64_t *) out = GNUNET_ntohll (*(uint64_t *) in);
1369 in += sz;
1370 out += sz;
1371 }
1372 break;
1373
1374 case array_of_abs_time:
1375 if (NULL != dst_size)
1376 *dst_size = sizeof(struct GNUNET_TIME_Absolute) * (header.dim);
1377 out = GNUNET_new_array (header.dim, struct GNUNET_TIME_Absolute);
1378 *((void **) dst) = out;
1379 for (uint32_t i = 0; i < header.dim; i++)
1380 {
1381 size_t sz = ntohl (*(uint32_t *) in);
1382 FAIL_IF (sz != sizeof(uint64_t));
1383 in += sizeof(uint32_t);
1384 ((struct GNUNET_TIME_Absolute *) out)->abs_value_us =
1385 GNUNET_ntohll (*(uint64_t *) in);
1386 in += sz;
1387 out += sz;
1388 }
1389 break;
1390
1391 case array_of_rel_time:
1392 if (NULL != dst_size)
1393 *dst_size = sizeof(struct GNUNET_TIME_Relative) * (header.dim);
1394 out = GNUNET_new_array (header.dim, struct GNUNET_TIME_Relative);
1395 *((void **) dst) = out;
1396 for (uint32_t i = 0; i < header.dim; i++)
1397 {
1398 size_t sz = ntohl (*(uint32_t *) in);
1399 FAIL_IF (sz != sizeof(uint64_t));
1400 in += sizeof(uint32_t);
1401 ((struct GNUNET_TIME_Relative *) out)->rel_value_us =
1402 GNUNET_ntohll (*(uint64_t *) in);
1403 in += sz;
1404 out += sz;
1405 }
1406 break;
1407
1408 case array_of_timestamp:
1409 if (NULL != dst_size)
1410 *dst_size = sizeof(struct GNUNET_TIME_Timestamp) * (header.dim);
1411 out = GNUNET_new_array (header.dim, struct GNUNET_TIME_Timestamp);
1412 *((void **) dst) = out;
1413 for (uint32_t i = 0; i < header.dim; i++)
1414 {
1415 size_t sz = ntohl (*(uint32_t *) in);
1416 FAIL_IF (sz != sizeof(uint64_t));
1417 in += sizeof(uint32_t);
1418 ((struct GNUNET_TIME_Timestamp *) out)->abs_time.abs_value_us =
1419 GNUNET_ntohll (*(uint64_t *) in);
1420 in += sz;
1421 out += sz;
1422 }
1423 break;
1424
1425 case array_of_byte:
1426 if (0 == info->same_size)
1427 *info->sizes = GNUNET_new_array (header.dim, size_t);
1428 /* fallthrough */
1429 case array_of_string:
1430 {
1431 size_t total = 0;
1432 bool is_string = (array_of_string == info->typ);
1433
1434 /* first, calculate total size required for allocation */
1435 {
1436 char *ptr = data + sizeof(header);
1437 for (uint32_t i = 0; i < header.dim; i++)
1438 {
1439 uint32_t sz;
1440
1441 sz = ntohl (*(uint32_t *) ptr);
1442 sz += is_string ? 1 : 0;
1443 total += sz;
1444 ptr += sizeof(uint32_t);
1445 ptr += sz;
1446
1447 if ((! is_string) &&
1448 (0 == info->same_size))
1449 (*info->sizes)[i] = sz;
1450
1451 FAIL_IF ((0 != info->same_size) &&
1452 (sz != info->same_size));
1453 FAIL_IF (total < sz);
1454 }
1455 }
1456
1457 if (NULL != dst_size)
1458 *dst_size = total;
1459
1460 FAIL_IF (0 == total);
1461 out = GNUNET_malloc (total);
1462
1463 *((void **) dst) = out;
1464
1465 /* copy data */
1466 for (uint32_t i = 0; i < header.dim; i++)
1467 {
1468 size_t sz = ntohl (*(uint32_t *) in);
1469 in += sizeof(uint32_t);
1470 GNUNET_memcpy (out, in, sz);
1471
1472 in += sz;
1473 out += sz;
1474 out += (array_of_string == info->typ) ? 1 : 0;
1475 }
1476 break;
1477 }
1478 default:
1479 FAIL_IF (1 != 0);
1480 }
1481 }
1482
1483 return GNUNET_OK;
1484
1485FAIL:
1486 GNUNET_free (*(void **) dst);
1487 return GNUNET_SYSERR;
1488 #undef FAIL_IF
1489}
1490
1491
1492/**
1493 * Cleanup of the data and closure of an array spec.
1494 */
1495static void
1496array_cleanup (void *cls,
1497 void *rd)
1498{
1499
1500 struct array_result_cls *info = cls;
1501 void **dst = rd;
1502
1503 if ((array_of_byte == info->typ) &&
1504 (0 == info->same_size) &&
1505 (NULL != info->sizes))
1506 GNUNET_free (*(info->sizes));
1507
1508 GNUNET_free (cls);
1509 GNUNET_free (*dst);
1510 *dst = NULL;
1511}
1512
1513
1514struct GNUNET_PQ_ResultSpec
1515GNUNET_PQ_result_spec_array_bool (
1516 struct GNUNET_PQ_Context *db,
1517 const char *name,
1518 size_t *num,
1519 bool **dst)
1520{
1521 struct array_result_cls *info =
1522 GNUNET_new (struct array_result_cls);
1523
1524 info->num = num;
1525 info->typ = array_of_bool;
1526 GNUNET_assert (GNUNET_OK ==
1527 GNUNET_PQ_get_oid_by_name (db,
1528 "bool",
1529 &info->oid));
1530
1531 struct GNUNET_PQ_ResultSpec res = {
1532 .conv = extract_array_generic,
1533 .cleaner = array_cleanup,
1534 .dst = (void *) dst,
1535 .fname = name,
1536 .cls = info
1537 };
1538 return res;
1539}
1540
1541
1542struct GNUNET_PQ_ResultSpec
1543GNUNET_PQ_result_spec_array_uint16 (
1544 struct GNUNET_PQ_Context *db,
1545 const char *name,
1546 size_t *num,
1547 uint16_t **dst)
1548{
1549 struct array_result_cls *info =
1550 GNUNET_new (struct array_result_cls);
1551
1552 info->num = num;
1553 info->typ = array_of_uint16;
1554 GNUNET_assert (GNUNET_OK ==
1555 GNUNET_PQ_get_oid_by_name (db,
1556 "int2",
1557 &info->oid));
1558
1559 struct GNUNET_PQ_ResultSpec res = {
1560 .conv = extract_array_generic,
1561 .cleaner = array_cleanup,
1562 .dst = (void *) dst,
1563 .fname = name,
1564 .cls = info
1565 };
1566 return res;
1567}
1568
1569
1570struct GNUNET_PQ_ResultSpec
1571GNUNET_PQ_result_spec_array_uint32 (
1572 struct GNUNET_PQ_Context *db,
1573 const char *name,
1574 size_t *num,
1575 uint32_t **dst)
1576{
1577 struct array_result_cls *info =
1578 GNUNET_new (struct array_result_cls);
1579
1580 info->num = num;
1581 info->typ = array_of_uint32;
1582 GNUNET_assert (GNUNET_OK ==
1583 GNUNET_PQ_get_oid_by_name (db,
1584 "int4",
1585 &info->oid));
1586
1587 struct GNUNET_PQ_ResultSpec res = {
1588 .conv = extract_array_generic,
1589 .cleaner = array_cleanup,
1590 .dst = (void *) dst,
1591 .fname = name,
1592 .cls = info
1593 };
1594 return res;
1595}
1596
1597
1598struct GNUNET_PQ_ResultSpec
1599GNUNET_PQ_result_spec_array_uint64 (
1600 struct GNUNET_PQ_Context *db,
1601 const char *name,
1602 size_t *num,
1603 uint64_t **dst)
1604{
1605 struct array_result_cls *info =
1606 GNUNET_new (struct array_result_cls);
1607
1608 info->num = num;
1609 info->typ = array_of_uint64;
1610 GNUNET_assert (GNUNET_OK ==
1611 GNUNET_PQ_get_oid_by_name (db,
1612 "int8",
1613 &info->oid));
1614
1615 struct GNUNET_PQ_ResultSpec res = {
1616 .conv = extract_array_generic,
1617 .cleaner = array_cleanup,
1618 .dst = (void *) dst,
1619 .fname = name,
1620 .cls = info
1621 };
1622 return res;
1623}
1624
1625
1626struct GNUNET_PQ_ResultSpec
1627GNUNET_PQ_result_spec_array_abs_time (
1628 struct GNUNET_PQ_Context *db,
1629 const char *name,
1630 size_t *num,
1631 struct GNUNET_TIME_Absolute **dst)
1632{
1633 struct array_result_cls *info =
1634 GNUNET_new (struct array_result_cls);
1635
1636 info->num = num;
1637 info->typ = array_of_abs_time;
1638 GNUNET_assert (GNUNET_OK ==
1639 GNUNET_PQ_get_oid_by_name (db,
1640 "int8",
1641 &info->oid));
1642
1643 struct GNUNET_PQ_ResultSpec res = {
1644 .conv = extract_array_generic,
1645 .cleaner = array_cleanup,
1646 .dst = (void *) dst,
1647 .fname = name,
1648 .cls = info
1649 };
1650 return res;
1651}
1652
1653
1654struct GNUNET_PQ_ResultSpec
1655GNUNET_PQ_result_spec_array_rel_time (
1656 struct GNUNET_PQ_Context *db,
1657 const char *name,
1658 size_t *num,
1659 struct GNUNET_TIME_Relative **dst)
1660{
1661 struct array_result_cls *info =
1662 GNUNET_new (struct array_result_cls);
1663
1664 info->num = num;
1665 info->typ = array_of_rel_time;
1666 GNUNET_assert (GNUNET_OK ==
1667 GNUNET_PQ_get_oid_by_name (db,
1668 "int8",
1669 &info->oid));
1670
1671 struct GNUNET_PQ_ResultSpec res = {
1672 .conv = extract_array_generic,
1673 .cleaner = array_cleanup,
1674 .dst = (void *) dst,
1675 .fname = name,
1676 .cls = info
1677 };
1678 return res;
1679}
1680
1681
1682struct GNUNET_PQ_ResultSpec
1683GNUNET_PQ_result_spec_array_timestamp (
1684 struct GNUNET_PQ_Context *db,
1685 const char *name,
1686 size_t *num,
1687 struct GNUNET_TIME_Timestamp **dst)
1688{
1689 struct array_result_cls *info =
1690 GNUNET_new (struct array_result_cls);
1691
1692 info->num = num;
1693 info->typ = array_of_timestamp;
1694 GNUNET_assert (GNUNET_OK ==
1695 GNUNET_PQ_get_oid_by_name (db,
1696 "int8",
1697 &info->oid));
1698
1699 struct GNUNET_PQ_ResultSpec res = {
1700 .conv = extract_array_generic,
1701 .cleaner = array_cleanup,
1702 .dst = (void *) dst,
1703 .fname = name,
1704 .cls = info
1705 };
1706 return res;
1707}
1708
1709
1710struct GNUNET_PQ_ResultSpec
1711GNUNET_PQ_result_spec_array_variable_size (
1712 struct GNUNET_PQ_Context *db,
1713 const char *name,
1714 size_t *num,
1715 size_t **sizes,
1716 void **dst)
1717{
1718 struct array_result_cls *info =
1719 GNUNET_new (struct array_result_cls);
1720
1721 info->num = num;
1722 info->sizes = sizes;
1723 info->typ = array_of_byte;
1724 GNUNET_assert (GNUNET_OK ==
1725 GNUNET_PQ_get_oid_by_name (db,
1726 "bytea",
1727 &info->oid));
1728
1729 struct GNUNET_PQ_ResultSpec res = {
1730 .conv = extract_array_generic,
1731 .cleaner = array_cleanup,
1732 .dst = (void *) dst,
1733 .fname = name,
1734 .cls = info
1735 };
1736 return res;
1737}
1738
1739
1740struct GNUNET_PQ_ResultSpec
1741GNUNET_PQ_result_spec_array_fixed_size (
1742 struct GNUNET_PQ_Context *db,
1743 const char *name,
1744 size_t size,
1745 size_t *num,
1746 void **dst)
1747{
1748 struct array_result_cls *info =
1749 GNUNET_new (struct array_result_cls);
1750
1751 info->num = num;
1752 info->same_size = size;
1753 info->typ = array_of_byte;
1754 GNUNET_assert (GNUNET_OK ==
1755 GNUNET_PQ_get_oid_by_name (db,
1756 "bytea",
1757 &info->oid));
1758
1759 struct GNUNET_PQ_ResultSpec res = {
1760 .conv = extract_array_generic,
1761 .cleaner = array_cleanup,
1762 .dst = (void *) dst,
1763 .fname = name,
1764 .cls = info
1765 };
1766 return res;
1767}
1768
1769
1770struct GNUNET_PQ_ResultSpec
1771GNUNET_PQ_result_spec_array_string (
1772 struct GNUNET_PQ_Context *db,
1773 const char *name,
1774 size_t *num,
1775 char **dst)
1776{
1777 struct array_result_cls *info =
1778 GNUNET_new (struct array_result_cls);
1779
1780 info->num = num;
1781 info->typ = array_of_string;
1782 GNUNET_assert (GNUNET_OK ==
1783 GNUNET_PQ_get_oid_by_name (db,
1784 "text",
1785 &info->oid));
1786
1787 struct GNUNET_PQ_ResultSpec res = {
1788 .conv = extract_array_generic,
1789 .cleaner = array_cleanup,
1790 .dst = (void *) dst,
1791 .fname = name,
1792 .cls = info
1793 };
1794 return res;
1795}
1796
1797
1798/**
1799 * Extract data from a Postgres database @a result at row @a row.
1800 *
1801 * @param cls closure
1802 * @param result where to extract data from
1803 * @param row the row to extract data from
1804 * @param fname name (or prefix) of the fields to extract from
1805 * @param[in,out] dst_size where to store size of result, may be NULL
1806 * @param[out] dst where to store the result
1807 * @return
1808 * #GNUNET_YES if all results could be extracted
1809 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
1810 */
1811static enum GNUNET_GenericReturnValue
1812extract_blind_sign_pub (void *cls,
1813 PGresult *result,
1814 int row,
1815 const char *fname,
1816 size_t *dst_size,
1817 void *dst)
1818{
1819 struct GNUNET_CRYPTO_BlindSignPublicKey **bpk = dst;
1820 struct GNUNET_CRYPTO_BlindSignPublicKey *tmp;
1821 size_t len;
1822 const char *res;
1823 int fnum;
1824 uint32_t be;
1825
1826 (void) cls;
1827 (void) dst_size;
1828 fnum = PQfnumber (result,
1829 fname);
1830 if (fnum < 0)
1831 {
1832 GNUNET_break (0);
1833 return GNUNET_SYSERR;
1834 }
1835 if (PQgetisnull (result,
1836 row,
1837 fnum))
1838 return GNUNET_NO;
1839
1840 /* if a field is null, continue but
1841 * remember that we now return a different result */
1842 len = PQgetlength (result,
1843 row,
1844 fnum);
1845 res = PQgetvalue (result,
1846 row,
1847 fnum);
1848 if (len < sizeof (be))
1849 {
1850 GNUNET_break (0);
1851 return GNUNET_SYSERR;
1852 }
1853 GNUNET_memcpy (&be,
1854 res,
1855 sizeof (be));
1856 res += sizeof (be);
1857 len -= sizeof (be);
1858 tmp = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
1859 tmp->cipher = ntohl (be);
1860 tmp->rc = 1;
1861 switch (tmp->cipher)
1862 {
1863 case GNUNET_CRYPTO_BSA_INVALID:
1864 break;
1865 case GNUNET_CRYPTO_BSA_RSA:
1866 tmp->details.rsa_public_key
1867 = GNUNET_CRYPTO_rsa_public_key_decode (res,
1868 len);
1869 if (NULL == tmp->details.rsa_public_key)
1870 {
1871 GNUNET_break (0);
1872 GNUNET_free (tmp);
1873 return GNUNET_SYSERR;
1874 }
1875 GNUNET_CRYPTO_hash (res,
1876 len,
1877 &tmp->pub_key_hash);
1878 *bpk = tmp;
1879 return GNUNET_OK;
1880 case GNUNET_CRYPTO_BSA_CS:
1881 if (sizeof (tmp->details.cs_public_key) != len)
1882 {
1883 GNUNET_break (0);
1884 GNUNET_free (tmp);
1885 return GNUNET_SYSERR;
1886 }
1887 GNUNET_memcpy (&tmp->details.cs_public_key,
1888 res,
1889 len);
1890 GNUNET_CRYPTO_hash (res,
1891 len,
1892 &tmp->pub_key_hash);
1893 *bpk = tmp;
1894 return GNUNET_OK;
1895 }
1896 GNUNET_break (0);
1897 GNUNET_free (tmp);
1898 return GNUNET_SYSERR;
1899}
1900
1901
1902/**
1903 * Function called to clean up memory allocated
1904 * by a #GNUNET_PQ_ResultConverter.
1905 *
1906 * @param cls closure
1907 * @param rd result data to clean up
1908 */
1909static void
1910clean_blind_sign_pub (void *cls,
1911 void *rd)
1912{
1913 struct GNUNET_CRYPTO_BlindSignPublicKey **pub = rd;
1914
1915 (void) cls;
1916 GNUNET_CRYPTO_blind_sign_pub_decref (*pub);
1917 *pub = NULL;
1918}
1919
1920
1921struct GNUNET_PQ_ResultSpec
1922GNUNET_PQ_result_spec_blind_sign_pub (const char *name,
1923 struct GNUNET_CRYPTO_BlindSignPublicKey **pub)
1924{
1925 struct GNUNET_PQ_ResultSpec res = {
1926 .conv = &extract_blind_sign_pub,
1927 .cleaner = &clean_blind_sign_pub,
1928 .dst = (void *) pub,
1929 .fname = name
1930 };
1931
1932 return res;
1933}
1934
1935
1936/**
1937 * Extract data from a Postgres database @a result at row @a row.
1938 *
1939 * @param cls closure
1940 * @param result where to extract data from
1941 * @param row the row to extract data from
1942 * @param fname name (or prefix) of the fields to extract from
1943 * @param[in,out] dst_size where to store size of result, may be NULL
1944 * @param[out] dst where to store the result
1945 * @return
1946 * #GNUNET_YES if all results could be extracted
1947 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
1948 */
1949static enum GNUNET_GenericReturnValue
1950extract_blind_sign_priv (void *cls,
1951 PGresult *result,
1952 int row,
1953 const char *fname,
1954 size_t *dst_size,
1955 void *dst)
1956{
1957 struct GNUNET_CRYPTO_BlindSignPrivateKey **bpk = dst;
1958 struct GNUNET_CRYPTO_BlindSignPrivateKey *tmp;
1959 size_t len;
1960 const char *res;
1961 int fnum;
1962 uint32_t be;
1963
1964 (void) cls;
1965 (void) dst_size;
1966 fnum = PQfnumber (result,
1967 fname);
1968 if (fnum < 0)
1969 {
1970 GNUNET_break (0);
1971 return GNUNET_SYSERR;
1972 }
1973 if (PQgetisnull (result,
1974 row,
1975 fnum))
1976 return GNUNET_NO;
1977
1978 /* if a field is null, continue but
1979 * remember that we now return a different result */
1980 len = PQgetlength (result,
1981 row,
1982 fnum);
1983 res = PQgetvalue (result,
1984 row,
1985 fnum);
1986 if (len < sizeof (be))
1987 {
1988 GNUNET_break (0);
1989 return GNUNET_SYSERR;
1990 }
1991 GNUNET_memcpy (&be,
1992 res,
1993 sizeof (be));
1994 res += sizeof (be);
1995 len -= sizeof (be);
1996 tmp = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPrivateKey);
1997 tmp->cipher = ntohl (be);
1998 tmp->rc = 1;
1999 switch (tmp->cipher)
2000 {
2001 case GNUNET_CRYPTO_BSA_INVALID:
2002 break;
2003 case GNUNET_CRYPTO_BSA_RSA:
2004 tmp->details.rsa_private_key
2005 = GNUNET_CRYPTO_rsa_private_key_decode (res,
2006 len);
2007 if (NULL == tmp->details.rsa_private_key)
2008 {
2009 GNUNET_break (0);
2010 GNUNET_free (bpk);
2011 return GNUNET_SYSERR;
2012 }
2013 *bpk = tmp;
2014 return GNUNET_OK;
2015 case GNUNET_CRYPTO_BSA_CS:
2016 if (sizeof (tmp->details.cs_private_key) != len)
2017 {
2018 GNUNET_break (0);
2019 GNUNET_free (tmp);
2020 return GNUNET_SYSERR;
2021 }
2022 GNUNET_memcpy (&tmp->details.cs_private_key,
2023 res,
2024 len);
2025 *bpk = tmp;
2026 return GNUNET_OK;
2027 }
2028 GNUNET_break (0);
2029 GNUNET_free (tmp);
2030 return GNUNET_SYSERR;
2031}
2032
2033
2034/**
2035 * Function called to clean up memory allocated
2036 * by a #GNUNET_PQ_ResultConverter.
2037 *
2038 * @param cls closure
2039 * @param rd result data to clean up
2040 */
2041static void
2042clean_blind_sign_priv (void *cls,
2043 void *rd)
2044{
2045 struct GNUNET_CRYPTO_BlindSignPrivateKey **priv = rd;
2046
2047 (void) cls;
2048 GNUNET_CRYPTO_blind_sign_priv_decref (*priv);
2049 *priv = NULL;
2050}
2051
2052
2053struct GNUNET_PQ_ResultSpec
2054GNUNET_PQ_result_spec_blind_sign_priv (const char *name,
2055 struct GNUNET_CRYPTO_BlindSignPrivateKey **priv)
2056{
2057 struct GNUNET_PQ_ResultSpec res = {
2058 .conv = &extract_blind_sign_priv,
2059 .cleaner = &clean_blind_sign_priv,
2060 .dst = (void *) priv,
2061 .fname = name
2062 };
2063
2064 return res;
2065}
2066
2067/* end of pq_result_helper.c */
diff --git a/src/lib/pq/test_pq.c b/src/lib/pq/test_pq.c
new file mode 100644
index 000000000..813c4a019
--- /dev/null
+++ b/src/lib/pq/test_pq.c
@@ -0,0 +1,630 @@
1/*
2 This file is part of GNUnet
3 (C) 2015, 2016, 2019, 2020 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/**
21 * @file pq/test_pq.c
22 * @brief Tests for Postgres convenience API
23 * @author Christian Grothoff <christian@grothoff.org>
24 */
25#include "platform.h"
26#include "gnunet_common.h"
27#include "gnunet_pq_lib.h"
28#include "gnunet_time_lib.h"
29#include "pq.h"
30
31/**
32 * Database handle.
33 */
34static struct GNUNET_PQ_Context *db;
35
36/**
37 * Global return value, 0 on success.
38 */
39static int ret;
40
41/**
42 * An event handler.
43 */
44static struct GNUNET_DB_EventHandler *eh;
45
46/**
47 * Timeout task.
48 */
49static struct GNUNET_SCHEDULER_Task *tt;
50
51
52/**
53 * Setup prepared statements.
54 *
55 * @param db database handle to initialize
56 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
57 */
58static int
59postgres_prepare (struct GNUNET_PQ_Context *db)
60{
61 struct GNUNET_PQ_PreparedStatement ps[] = {
62 GNUNET_PQ_make_prepare ("test_insert",
63 "INSERT INTO test_pq ("
64 " pub"
65 ",sig"
66 ",abs_time"
67 ",forever"
68 ",hash"
69 ",vsize"
70 ",u16"
71 ",u32"
72 ",u64"
73 ",unn"
74 ",arr_bool"
75 ",arr_int2"
76 ",arr_int4"
77 ",arr_int8"
78 ",arr_bytea"
79 ",arr_text"
80 ",arr_abs_time"
81 ",arr_rel_time"
82 ",arr_timestamp"
83 ") VALUES "
84 "($1, $2, $3, $4, $5, $6,"
85 "$7, $8, $9, $10,"
86 "$11, $12, $13, $14, $15, $16,"
87 "$17, $18, $19);"),
88 GNUNET_PQ_make_prepare ("test_select",
89 "SELECT"
90 " pub"
91 ",sig"
92 ",abs_time"
93 ",forever"
94 ",hash"
95 ",vsize"
96 ",u16"
97 ",u32"
98 ",u64"
99 ",unn"
100 ",arr_bool"
101 ",arr_int2"
102 ",arr_int4"
103 ",arr_int8"
104 ",arr_bytea"
105 ",arr_text"
106 ",arr_abs_time"
107 ",arr_rel_time"
108 ",arr_timestamp"
109 " FROM test_pq"
110 " ORDER BY abs_time DESC "
111 " LIMIT 1;"),
112 GNUNET_PQ_PREPARED_STATEMENT_END
113 };
114
115 return GNUNET_PQ_prepare_statements (db,
116 ps);
117}
118
119
120/**
121 * Run actual test queries.
122 *
123 * @param db database handle
124 * @return 0 on success
125 */
126static int
127run_queries (struct GNUNET_PQ_Context *db)
128{
129 struct GNUNET_CRYPTO_RsaPublicKey *pub;
130 struct GNUNET_CRYPTO_RsaPublicKey *pub2 = NULL;
131 struct GNUNET_CRYPTO_RsaSignature *sig;
132 struct GNUNET_CRYPTO_RsaSignature *sig2 = NULL;
133 struct GNUNET_TIME_Absolute abs_time = GNUNET_TIME_absolute_get ();
134 struct GNUNET_TIME_Absolute abs_time2;
135 struct GNUNET_TIME_Absolute forever = GNUNET_TIME_UNIT_FOREVER_ABS;
136 struct GNUNET_TIME_Absolute forever2;
137 struct GNUNET_HashCode hc;
138 struct GNUNET_HashCode hc2;
139 PGresult *result;
140 int ret;
141 struct GNUNET_CRYPTO_RsaPrivateKey *priv;
142 const char msg[] = "hello";
143 void *msg2;
144 struct GNUNET_HashCode hmsg;
145 size_t msg2_len;
146 uint16_t u16;
147 uint16_t u162;
148 uint32_t u32;
149 uint32_t u322;
150 uint64_t u64;
151 uint64_t u642;
152 uint64_t uzzz = 42;
153 struct GNUNET_HashCode ahc[3] = {};
154 bool ab[5] = {true, false, false, true, false};
155 uint16_t ai2[3] = {42, 0x0001, 0xFFFF};
156 uint32_t ai4[3] = {42, 0x00010000, 0xFFFFFFFF};
157 uint64_t ai8[3] = {42, 0x0001000000000000, 0xFFFFFFFFFFFFFFFF};
158 const char *as[] = {"foo", "bar", "buzz"};
159 const struct GNUNET_TIME_Absolute ata[2] = {GNUNET_TIME_absolute_get (),
160 GNUNET_TIME_absolute_get ()};
161 const struct GNUNET_TIME_Relative atr[2] = {GNUNET_TIME_relative_get_hour_ (),
162 GNUNET_TIME_relative_get_minute_ ()};
163 const struct GNUNET_TIME_Timestamp ats[2] = {GNUNET_TIME_timestamp_get (),
164 GNUNET_TIME_timestamp_get ()};
165
166
167 priv = GNUNET_CRYPTO_rsa_private_key_create (1024);
168 pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv);
169 memset (&hmsg, 42, sizeof(hmsg));
170 sig = GNUNET_CRYPTO_rsa_sign_fdh (priv,
171 &hmsg,
172 sizeof (hmsg));
173 u16 = 16;
174 u32 = 32;
175 u64 = 64;
176 for (int i = 0; i < 3; i++)
177 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
178 &ahc[i],
179 sizeof(ahc[i]));
180
181 /* FIXME: test GNUNET_PQ_result_spec_variable_size */
182 {
183 struct GNUNET_PQ_QueryParam params_insert[] = {
184 GNUNET_PQ_query_param_rsa_public_key (pub),
185 GNUNET_PQ_query_param_rsa_signature (sig),
186 GNUNET_PQ_query_param_absolute_time (&abs_time),
187 GNUNET_PQ_query_param_absolute_time (&forever),
188 GNUNET_PQ_query_param_auto_from_type (&hc),
189 GNUNET_PQ_query_param_string (msg),
190 GNUNET_PQ_query_param_uint16 (&u16),
191 GNUNET_PQ_query_param_uint32 (&u32),
192 GNUNET_PQ_query_param_uint64 (&u64),
193 GNUNET_PQ_query_param_null (),
194 GNUNET_PQ_query_param_array_bool (5, ab, db),
195 GNUNET_PQ_query_param_array_uint16 (3, ai2, db),
196 GNUNET_PQ_query_param_array_uint32 (3, ai4, db),
197 GNUNET_PQ_query_param_array_uint64 (3, ai8, db),
198 GNUNET_PQ_query_param_array_bytes_same_size (3,
199 ahc,
200 sizeof(ahc[0]),
201 db),
202 GNUNET_PQ_query_param_array_ptrs_string (3, as, db),
203 GNUNET_PQ_query_param_array_abs_time (2, ata, db),
204 GNUNET_PQ_query_param_array_rel_time (2, atr, db),
205 GNUNET_PQ_query_param_array_timestamp (2, ats, db),
206 GNUNET_PQ_query_param_end
207 };
208 struct GNUNET_PQ_QueryParam params_select[] = {
209 GNUNET_PQ_query_param_end
210 };
211 bool got_null = false;
212 size_t num_bool;
213 bool *arr_bools;
214 size_t num_u16;
215 uint16_t *arr_u16;
216 size_t num_u32;
217 uint32_t *arr_u32;
218 size_t num_u64;
219 uint64_t *arr_u64;
220 size_t num_abs;
221 struct GNUNET_TIME_Absolute *arr_abs;
222 size_t num_rel;
223 struct GNUNET_TIME_Relative *arr_rel;
224 size_t num_tstmp;
225 struct GNUNET_TIME_Timestamp *arr_tstmp;
226 size_t num_str;
227 char *arr_str;
228 size_t num_hash;
229 struct GNUNET_HashCode *arr_hash;
230 size_t num_buf;
231 void *arr_buf;
232 size_t *sz_buf;
233 struct GNUNET_PQ_ResultSpec results_select[] = {
234 GNUNET_PQ_result_spec_rsa_public_key ("pub", &pub2),
235 GNUNET_PQ_result_spec_rsa_signature ("sig", &sig2),
236 GNUNET_PQ_result_spec_absolute_time ("abs_time", &abs_time2),
237 GNUNET_PQ_result_spec_absolute_time ("forever", &forever2),
238 GNUNET_PQ_result_spec_auto_from_type ("hash", &hc2),
239 GNUNET_PQ_result_spec_variable_size ("vsize", &msg2, &msg2_len),
240 GNUNET_PQ_result_spec_uint16 ("u16", &u162),
241 GNUNET_PQ_result_spec_uint32 ("u32", &u322),
242 GNUNET_PQ_result_spec_uint64 ("u64", &u642),
243 GNUNET_PQ_result_spec_allow_null (
244 GNUNET_PQ_result_spec_uint64 ("unn", &uzzz),
245 &got_null),
246 GNUNET_PQ_result_spec_array_bool (db,
247 "arr_bool",
248 &num_bool,
249 &arr_bools),
250 GNUNET_PQ_result_spec_array_uint16 (db,
251 "arr_int2",
252 &num_u16,
253 &arr_u16),
254 GNUNET_PQ_result_spec_array_uint32 (db,
255 "arr_int4",
256 &num_u32,
257 &arr_u32),
258 GNUNET_PQ_result_spec_array_uint64 (db,
259 "arr_int8",
260 &num_u64,
261 &arr_u64),
262 GNUNET_PQ_result_spec_array_abs_time (db,
263 "arr_abs_time",
264 &num_abs,
265 &arr_abs),
266 GNUNET_PQ_result_spec_array_rel_time (db,
267 "arr_rel_time",
268 &num_rel,
269 &arr_rel),
270 GNUNET_PQ_result_spec_array_timestamp (db,
271 "arr_timestamp",
272 &num_tstmp,
273 &arr_tstmp),
274 GNUNET_PQ_result_spec_auto_array_from_type (db,
275 "arr_bytea",
276 &num_hash,
277 arr_hash),
278 GNUNET_PQ_result_spec_array_variable_size (db,
279 "arr_bytea",
280 &num_buf,
281 &sz_buf,
282 &arr_buf),
283 GNUNET_PQ_result_spec_array_string (db,
284 "arr_text",
285 &num_str,
286 &arr_str),
287 GNUNET_PQ_result_spec_end
288 };
289
290 result = GNUNET_PQ_exec_prepared (db,
291 "test_insert",
292 params_insert);
293 if (PGRES_COMMAND_OK != PQresultStatus (result))
294 {
295 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
296 "Database failure: %s\n",
297 PQresultErrorMessage (result));
298 PQclear (result);
299 GNUNET_CRYPTO_rsa_signature_free (sig);
300 GNUNET_CRYPTO_rsa_private_key_free (priv);
301 GNUNET_CRYPTO_rsa_public_key_free (pub);
302 return 1;
303 }
304
305 PQclear (result);
306 result = GNUNET_PQ_exec_prepared (db,
307 "test_select",
308 params_select);
309 if (1 !=
310 PQntuples (result))
311 {
312 GNUNET_break (0);
313 PQclear (result);
314 GNUNET_CRYPTO_rsa_signature_free (sig);
315 GNUNET_CRYPTO_rsa_private_key_free (priv);
316 GNUNET_CRYPTO_rsa_public_key_free (pub);
317 return 1;
318 }
319 ret = GNUNET_PQ_extract_result (result,
320 results_select,
321 0);
322 GNUNET_break (GNUNET_YES == ret);
323 GNUNET_break (abs_time.abs_value_us == abs_time2.abs_value_us);
324 GNUNET_break (forever.abs_value_us == forever2.abs_value_us);
325 GNUNET_break (0 ==
326 GNUNET_memcmp (&hc,
327 &hc2));
328 GNUNET_break (0 ==
329 GNUNET_CRYPTO_rsa_signature_cmp (sig,
330 sig2));
331 GNUNET_break (0 ==
332 GNUNET_CRYPTO_rsa_public_key_cmp (pub,
333 pub2));
334 GNUNET_break (strlen (msg) == msg2_len);
335 GNUNET_break (0 ==
336 strncmp (msg,
337 msg2,
338 msg2_len));
339 GNUNET_break (16 == u162);
340 GNUNET_break (32 == u322);
341 GNUNET_break (64 == u642);
342 GNUNET_break (42 == uzzz);
343 GNUNET_break (got_null);
344
345 /* Check arrays */
346 GNUNET_break (num_bool == 5);
347 for (size_t i = 0; i < num_bool; i++)
348 GNUNET_break (arr_bools[i] == ab[i]);
349
350 GNUNET_break (num_u16 == 3);
351 for (size_t i = 0; i < num_u16; i++)
352 GNUNET_break (arr_u16[i] == ai2[i]);
353
354 GNUNET_break (num_u32 == 3);
355 for (size_t i = 0; i < num_u32; i++)
356 GNUNET_break (arr_u32[i] == ai4[i]);
357
358 GNUNET_break (num_u64 == 3);
359 for (size_t i = 0; i < num_u64; i++)
360 GNUNET_break (arr_u64[i] == ai8[i]);
361
362 GNUNET_break (num_abs == 2);
363 for (size_t i = 0; i < num_abs; i++)
364 GNUNET_break (arr_abs[i].abs_value_us == ata[i].abs_value_us);
365
366 GNUNET_break (num_rel == 2);
367 for (size_t i = 0; i < num_rel; i++)
368 GNUNET_break (arr_rel[i].rel_value_us == atr[i].rel_value_us);
369
370 GNUNET_break (num_tstmp == 2);
371 for (size_t i = 0; i < num_tstmp; i++)
372 GNUNET_break (arr_tstmp[i].abs_time.abs_value_us ==
373 ats[i].abs_time.abs_value_us);
374
375 GNUNET_break (num_str == 3);
376 GNUNET_break (0 == strcmp (arr_str, as[0]));
377 GNUNET_break (0 == strcmp (arr_str + 4, as[1]));
378 GNUNET_break (0 == strcmp (arr_str + 8, as[2]));
379
380 GNUNET_break (num_hash == 3);
381 for (size_t i = 0; i < num_hash; i++)
382 GNUNET_break (0 == GNUNET_memcmp (&arr_hash[i], &ahc[i]));
383
384 GNUNET_break (num_buf == 3);
385 for (size_t i = 0; i < num_buf; i++)
386 {
387 GNUNET_break (0 == memcmp (((char *) arr_buf) + i * sizeof(ahc[i]),
388 &ahc[i],
389 sizeof(ahc[i])));
390 }
391
392 GNUNET_PQ_cleanup_result (results_select);
393 PQclear (result);
394
395 GNUNET_PQ_cleanup_query_params_closures (params_insert);
396 }
397
398 GNUNET_CRYPTO_rsa_signature_free (sig);
399 GNUNET_CRYPTO_rsa_private_key_free (priv);
400 GNUNET_CRYPTO_rsa_public_key_free (pub);
401 if (GNUNET_OK != ret)
402 return 1;
403
404 return 0;
405}
406
407
408/**
409 * Task called on shutdown.
410 *
411 * @param cls NULL
412 */
413static void
414event_end (void *cls)
415{
416 GNUNET_PQ_event_listen_cancel (eh);
417 eh = NULL;
418 if (NULL != tt)
419 {
420 GNUNET_SCHEDULER_cancel (tt);
421 tt = NULL;
422 }
423}
424
425
426/**
427 * Task called on timeout. Should not happen, means
428 * we did not get the expected event.
429 *
430 * @param cls NULL
431 */
432static void
433timeout_cb (void *cls)
434{
435 ret = 2;
436 GNUNET_break (0);
437 tt = NULL;
438 GNUNET_SCHEDULER_shutdown ();
439}
440
441
442/**
443 * Task called on expected event
444 *
445 * @param cls NULL
446 */
447static void
448event_sched_cb (void *cls,
449 const void *extra,
450 size_t extra_size)
451{
452 GNUNET_assert (5 == extra_size);
453 GNUNET_assert (0 ==
454 memcmp ("hello",
455 extra,
456 5));
457 GNUNET_SCHEDULER_shutdown ();
458}
459
460
461/**
462 * Run tests that need a scheduler.
463 *
464 * @param cls NULL
465 */
466static void
467sched_tests (void *cls)
468{
469 struct GNUNET_DB_EventHeaderP es = {
470 .size = htons (sizeof (es)),
471 .type = htons (42)
472 };
473
474
475 tt = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
476 &timeout_cb,
477 NULL);
478 eh = GNUNET_PQ_event_listen (db,
479 &es,
480 GNUNET_TIME_UNIT_FOREVER_REL,
481 &event_sched_cb,
482 NULL);
483 GNUNET_PQ_reconnect (db);
484 GNUNET_SCHEDULER_add_shutdown (&event_end,
485 NULL);
486 GNUNET_PQ_event_notify (db,
487 &es,
488 "hello",
489 5);
490}
491
492
493int
494main (int argc,
495 const char *const argv[])
496{
497 struct GNUNET_PQ_ExecuteStatement es[] = {
498 GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
499 " pub BYTEA NOT NULL"
500 ",sig BYTEA NOT NULL"
501 ",abs_time INT8 NOT NULL"
502 ",forever INT8 NOT NULL"
503 ",hash BYTEA NOT NULL CHECK(LENGTH(hash)=64)"
504 ",vsize VARCHAR NOT NULL"
505 ",u16 INT2 NOT NULL"
506 ",u32 INT4 NOT NULL"
507 ",u64 INT8 NOT NULL"
508 ",unn INT8"
509 ",arr_bool BOOL[]"
510 ",arr_int2 INT2[]"
511 ",arr_int4 INT4[]"
512 ",arr_int8 INT8[]"
513 ",arr_bytea BYTEA[]"
514 ",arr_text TEXT[]"
515 ",arr_abs_time INT8[]"
516 ",arr_rel_time INT8[]"
517 ",arr_timestamp INT8[]"
518 ")"),
519 GNUNET_PQ_EXECUTE_STATEMENT_END
520 };
521
522 GNUNET_log_setup ("test-pq",
523 "INFO",
524 NULL);
525 db = GNUNET_PQ_connect ("postgres:///gnunetcheck",
526 NULL,
527 es,
528 NULL);
529 if (NULL == db)
530 {
531 fprintf (stderr,
532 "Cannot run test, database connection failed\n");
533 return 77;
534 }
535 if (CONNECTION_OK != PQstatus (db->conn))
536 {
537 fprintf (stderr,
538 "Cannot run test, database connection failed: %s\n",
539 PQerrorMessage (db->conn));
540 GNUNET_break (0);
541 GNUNET_PQ_disconnect (db);
542 return 77; /* signal test was skipped */
543 }
544 if (GNUNET_OK !=
545 postgres_prepare (db))
546 {
547 GNUNET_break (0);
548 GNUNET_PQ_disconnect (db);
549 return 1;
550 }
551 ret = run_queries (db);
552 if (0 != ret)
553 {
554 GNUNET_break (0);
555 GNUNET_PQ_disconnect (db);
556 return ret;
557 }
558
559 /* ensure oid lookup works */
560 {
561 enum GNUNET_GenericReturnValue ret;
562 Oid oid;
563
564 ret = GNUNET_PQ_get_oid_by_name (db, "int8", &oid);
565
566 if (GNUNET_OK != ret)
567 {
568 fprintf (stderr,
569 "Cannot lookup oid for int8: %s\n",
570 PQerrorMessage (db->conn));
571 GNUNET_break (0);
572 GNUNET_PQ_disconnect (db);
573 return 77; /* signal test was skipped */
574 }
575
576 PQexec (db->conn, "CREATE TYPE foo AS (foo int, bar int);");
577
578 ret = GNUNET_PQ_get_oid_by_name (db, "foo", &oid);
579 if (GNUNET_OK != ret)
580 {
581 fprintf (stderr,
582 "Cannot lookup oid for foo: %s\n",
583 PQerrorMessage (db->conn));
584 GNUNET_break (0);
585 GNUNET_PQ_disconnect (db);
586 return 77; /* signal test was skipped */
587 }
588
589 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
590 "got oid %d for type foo\n", oid);
591 }
592
593 GNUNET_SCHEDULER_run (&sched_tests,
594 NULL);
595 if (0 != ret)
596 {
597 GNUNET_break (0);
598 GNUNET_PQ_disconnect (db);
599 return ret;
600 }
601#if TEST_RESTART
602 fprintf (stderr, "Please restart Postgres database now!\n");
603 sleep (60);
604 ret |= run_queries (db);
605 fprintf (stderr, "Result: %d (expect: 1 -- if you restarted the DB)\n", ret);
606 ret |= run_queries (db);
607 fprintf (stderr, "Result: %d (expect: 0)\n", ret);
608#endif
609 {
610 struct GNUNET_PQ_ExecuteStatement es[] = {
611 GNUNET_PQ_make_execute ("DROP TABLE test_pq"),
612 GNUNET_PQ_EXECUTE_STATEMENT_END
613 };
614
615 if (GNUNET_OK !=
616 GNUNET_PQ_exec_statements (db,
617 es))
618 {
619 fprintf (stderr,
620 "Failed to drop table\n");
621 GNUNET_PQ_disconnect (db);
622 return 1;
623 }
624 }
625 GNUNET_PQ_disconnect (db);
626 return ret;
627}
628
629
630/* end of test_pq.c */
diff --git a/src/lib/pq/versioning.sql b/src/lib/pq/versioning.sql
new file mode 100644
index 000000000..c7fa81213
--- /dev/null
+++ b/src/lib/pq/versioning.sql
@@ -0,0 +1,298 @@
1-- LICENSE AND COPYRIGHT
2--
3-- Copyright (C) 2010 Hubert depesz Lubaczewski
4--
5-- This program is distributed under the (Revised) BSD License:
6-- L<http://www.opensource.org/licenses/bsd-license.php>
7--
8-- Redistribution and use in source and binary forms, with or without
9-- modification, are permitted provided that the following conditions
10-- are met:
11--
12-- * Redistributions of source code must retain the above copyright
13-- notice, this list of conditions and the following disclaimer.
14--
15-- * Redistributions in binary form must reproduce the above copyright
16-- notice, this list of conditions and the following disclaimer in the
17-- documentation and/or other materials provided with the distribution.
18--
19-- * Neither the name of Hubert depesz Lubaczewski's Organization
20-- nor the names of its contributors may be used to endorse or
21-- promote products derived from this software without specific
22-- prior written permission.
23--
24-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
28-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34--
35-- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql
36--
37--
38-- # NAME
39--
40-- **Versioning** - simplistic take on tracking and applying changes to databases.
41--
42-- # DESCRIPTION
43--
44-- This project strives to provide simple way to manage changes to
45-- database.
46--
47-- Instead of making changes on development server, then finding
48-- differences between production and development, deciding which ones
49-- should be installed on production, and finding a way to install them -
50-- you start with writing diffs themselves!
51--
52-- # INSTALLATION
53--
54-- To install versioning simply run install.versioning.sql in your database
55-- (all of them: production, stage, test, devel, ...).
56--
57-- # USAGE
58--
59-- In your files with patches to database, put whole logic in single
60-- transaction, and use \_v.\* functions - usually \_v.register_patch() at
61-- least to make sure everything is OK.
62--
63-- For example. Let's assume you have patch files:
64--
65-- ## 0001.sql:
66--
67-- ```
68-- create table users (id serial primary key, username text);
69-- ```
70--
71-- ## 0002.sql:
72--
73-- ```
74-- insert into users (username) values ('depesz');
75-- ```
76-- To change it to use versioning you would change the files, to this
77-- state:
78--
79-- 0000.sql:
80--
81-- ```
82-- BEGIN;
83-- select _v.register_patch('000-base', NULL, NULL);
84-- create table users (id serial primary key, username text);
85-- COMMIT;
86-- ```
87--
88-- ## 0002.sql:
89--
90-- ```
91-- BEGIN;
92-- select _v.register_patch('001-users', ARRAY['000-base'], NULL);
93-- insert into users (username) values ('depesz');
94-- COMMIT;
95-- ```
96--
97-- This will make sure that patch 001-users can only be applied after
98-- 000-base.
99--
100-- # AVAILABLE FUNCTIONS
101--
102-- ## \_v.register_patch( TEXT )
103--
104-- Registers named patch, or dies if it is already registered.
105--
106-- Returns integer which is id of patch in \_v.patches table - only if it
107-- succeeded.
108--
109-- ## \_v.register_patch( TEXT, TEXT[] )
110--
111-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as
112-- array in second argument) are already registered.
113--
114-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] )
115--
116-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches.
117--
118-- Third argument is array of names of patches that conflict with current one. So
119-- if any of them is installed - register_patch will error out.
120--
121-- ## \_v.unregister_patch( TEXT )
122--
123-- Removes information about given patch from the versioning data.
124--
125-- It doesn't remove objects that were created by this patch - just removes
126-- metainformation.
127--
128-- ## \_v.assert_user_is_superuser()
129--
130-- Make sure that current patch is being loaded by superuser.
131--
132-- If it's not - it will raise exception, and break transaction.
133--
134-- ## \_v.assert_user_is_not_superuser()
135--
136-- Make sure that current patch is not being loaded by superuser.
137--
138-- If it is - it will raise exception, and break transaction.
139--
140-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... )
141--
142-- Make sure that current patch is being loaded by one of listed users.
143--
144-- If ```current_user``` is not listed as one of arguments - function will raise
145-- exception and break the transaction.
146
147BEGIN;
148
149-- Added by Christian Grothoff to support concurrency, see
150-- https://stackoverflow.com/questions/29900845/create-schema-if-not-exists-raises-duplicate-key-error?rq=4
151LOCK TABLE pg_catalog.pg_namespace;
152
153
154-- This file adds versioning support to database it will be loaded to.
155-- It requires that PL/pgSQL is already loaded - will raise exception otherwise.
156-- All versioning "stuff" (tables, functions) is in "_v" schema.
157
158-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows).
159-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling
160CREATE SCHEMA IF NOT EXISTS _v;
161COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
162
163CREATE TABLE IF NOT EXISTS _v.patches (
164 patch_name TEXT PRIMARY KEY,
165 applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(),
166 applied_by TEXT NOT NULL,
167 requires TEXT[],
168 conflicts TEXT[]
169);
170COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.';
171COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.';
172COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
173COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)';
174COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.';
175COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.';
176
177CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$
178DECLARE
179 t_text TEXT;
180 t_text_a TEXT[];
181 i INT4;
182BEGIN
183 -- Thanks to this we know only one patch will be applied at a time
184 LOCK TABLE _v.patches IN EXCLUSIVE MODE;
185
186 SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
187 IF FOUND THEN
188 RAISE EXCEPTION 'Patch % is already applied!', in_patch_name;
189 END IF;
190
191 t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) );
192 IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
193 RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' );
194 END IF;
195
196 IF array_upper( in_requirements, 1 ) IS NOT NULL THEN
197 t_text_a := '{}';
198 FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP
199 SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i];
200 IF NOT FOUND THEN
201 t_text_a := t_text_a || in_requirements[i];
202 END IF;
203 END LOOP;
204 IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
205 RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' );
206 END IF;
207 END IF;
208
209 INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) );
210 RETURN;
211END;
212$$ language plpgsql;
213COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
214
215CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$
216 SELECT _v.register_patch( $1, $2, NULL );
217$$ language sql;
218COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.';
219CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$
220 SELECT _v.register_patch( $1, NULL, NULL );
221$$ language sql;
222COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.';
223
224CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$
225DECLARE
226 i INT4;
227 t_text_a TEXT[];
228BEGIN
229 -- Thanks to this we know only one patch will be applied at a time
230 LOCK TABLE _v.patches IN EXCLUSIVE MODE;
231
232 t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) );
233 IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
234 RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' );
235 END IF;
236
237 DELETE FROM _v.patches WHERE patch_name = in_patch_name;
238 GET DIAGNOSTICS i = ROW_COUNT;
239 IF i < 1 THEN
240 RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name;
241 END IF;
242
243 RETURN;
244END;
245$$ language plpgsql;
246COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.';
247
248CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$
249DECLARE
250 t_text TEXT;
251BEGIN
252 SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
253 IF NOT FOUND THEN
254 RAISE EXCEPTION 'Patch % is not applied!', in_patch_name;
255 END IF;
256 RETURN format('Patch %s is applied.', in_patch_name);
257END;
258$$ language plpgsql;
259COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.';
260
261CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$
262DECLARE
263 v_super bool;
264BEGIN
265 SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
266 IF v_super THEN
267 RETURN 'assert_user_is_superuser: OK';
268 END IF;
269 RAISE EXCEPTION 'Current user is not superuser - cannot continue.';
270END;
271$$ language plpgsql;
272COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.';
273
274CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$
275DECLARE
276 v_super bool;
277BEGIN
278 SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
279 IF v_super THEN
280 RAISE EXCEPTION 'Current user is superuser - cannot continue.';
281 END IF;
282 RETURN 'assert_user_is_not_superuser: OK';
283END;
284$$ language plpgsql;
285COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.';
286
287CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$
288DECLARE
289BEGIN
290 IF current_user = any( p_acceptable_users ) THEN
291 RETURN 'assert_user_is_one_of: OK';
292 END IF;
293 RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users;
294END;
295$$ language plpgsql;
296COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.';
297
298COMMIT;