aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Makefile.am8
-rw-r--r--src/lib/pq/.gitignore1
-rw-r--r--src/lib/pq/Makefile.am51
-rw-r--r--src/lib/pq/meson.build25
-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.c1328
-rw-r--r--src/lib/pq/pq_result_helper.c1714
-rw-r--r--src/lib/pq/test_pq.c629
-rw-r--r--src/lib/pq/versioning.sql298
-rw-r--r--src/lib/sq/.gitignore1
-rw-r--r--src/lib/sq/Makefile.am38
-rw-r--r--src/lib/sq/meson.build25
-rw-r--r--src/lib/sq/sq.c131
-rw-r--r--src/lib/sq/sq_exec.c113
-rw-r--r--src/lib/sq/sq_prepare.c77
-rw-r--r--src/lib/sq/sq_query_helper.c510
-rw-r--r--src/lib/sq/sq_result_helper.c785
-rw-r--r--src/lib/sq/test_sq.c290
24 files changed, 8159 insertions, 1 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 27cbfcb14..c9871d8c7 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,7 +1,13 @@
1if HAVE_POSTGRESQL
2 POSTGRES_DIR = pq
3endif
4
1SUBDIRS = \ 5SUBDIRS = \
2 util \ 6 util \
3 hello \ 7 hello \
4 block \ 8 block \
5 gnsrecord \ 9 gnsrecord \
6 curl \ 10 curl \
7 json 11 json \
12 sq \
13 $(POSTGRES_DIR)
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..6a42499ca
--- /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 4:0:0
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..b9b20d93c
--- /dev/null
+++ b/src/lib/pq/meson.build
@@ -0,0 +1,25 @@
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
10if get_option('monolith')
11 foreach p : libgnunetpq_src
12 gnunet_src += 'pq/' + p
13 endforeach
14 subdir_done()
15endif
16
17libgnunetpq = library('gnunetpq',
18 libgnunetpq_src,
19 soversion: '4',
20 version: '4.0.0',
21 dependencies: [libgnunetutil_dep, pq_dep],
22 include_directories: [incdir, configuration_inc],
23 install: true,
24 install_dir: get_option('libdir'))
25libgnunetpq_dep = declare_dependency(link_with : libgnunetpq)
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..c46d865a3
--- /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 {
459 PGresult *res;
460 ExecStatusType est;
461
462 res = PQexec (db->conn,
463 "SELECT"
464 " schema_name"
465 " FROM information_schema.schemata"
466 " WHERE schema_name='_v';");
467 est = PQresultStatus (res);
468 if ( (PGRES_COMMAND_OK != est) &&
469 (PGRES_TUPLES_OK != est) )
470 {
471 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
472 "Failed to run statement to check versioning schema. Bad!\n");
473 PQclear (res);
474 PQfinish (db->conn);
475 db->conn = NULL;
476 return;
477 }
478 if (0 == PQntuples (res))
479 {
480 enum GNUNET_GenericReturnValue ret;
481
482 PQclear (res);
483 if (0 != (db->flags & GNUNET_PQ_FLAG_DROP))
484 {
485 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
486 "Versioning schema does not exist yet. Not attempting drop!\n");
487 PQfinish (db->conn);
488 db->conn = NULL;
489 return;
490 }
491 ret = GNUNET_PQ_exec_sql (db,
492 "versioning");
493 if (GNUNET_NO == ret)
494 {
495 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
496 "Failed to find SQL file to load database versioning logic\n");
497 PQfinish (db->conn);
498 db->conn = NULL;
499 return;
500 }
501 if (GNUNET_SYSERR == ret)
502 {
503 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
504 "Failed to run SQL logic to setup database versioning logic\n");
505 PQfinish (db->conn);
506 db->conn = NULL;
507 return;
508 }
509 }
510 else
511 {
512 PQclear (res);
513 }
514 }
515
516 /* Prepare statement for OID lookup by name */
517 {
518 PGresult *res;
519
520 res = PQprepare (db->conn,
521 "gnunet_pq_get_oid_by_name",
522 "SELECT"
523 " typname, oid"
524 " FROM pg_type"
525 " WHERE typname = $1"
526 " LIMIT 1",
527 1,
528 NULL);
529 if (PGRES_COMMAND_OK != PQresultStatus (res))
530 {
531 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
532 "Failed to run SQL statement prepare OID lookups: %s/%s\n",
533 PQresultErrorMessage (res),
534 PQerrorMessage (db->conn));
535 PQclear (res);
536 PQfinish (db->conn);
537 db->conn = NULL;
538 return;
539 }
540 PQclear (res);
541 }
542
543 /* Reset the OID-cache and retrieve the OIDs for the supported Array types */
544 db->oids.num = 0;
545 if (GNUNET_SYSERR == load_initial_oids (db))
546 {
547 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
548 "Failed to retrieve OID information for array types!\n");
549 PQfinish (db->conn);
550 db->conn = NULL;
551 return;
552 }
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..fff9e16f7
--- /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_PG_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..1e2f7a965
--- /dev/null
+++ b/src/lib/pq/pq_query_helper.c
@@ -0,0 +1,1328 @@
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_rsa_public_key (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 struct GNUNET_CRYPTO_RsaPublicKey *rsa = data;
352 void *buf;
353 size_t buf_size;
354
355 GNUNET_break (NULL == cls);
356 if (1 != param_length)
357 return -1;
358 buf_size = GNUNET_CRYPTO_rsa_public_key_encode (rsa,
359 &buf);
360 scratch[0] = buf;
361 param_values[0] = (void *) buf;
362 param_lengths[0] = buf_size;
363 param_formats[0] = 1;
364 return 1;
365}
366
367
368struct GNUNET_PQ_QueryParam
369GNUNET_PQ_query_param_rsa_public_key (
370 const struct GNUNET_CRYPTO_RsaPublicKey *x)
371{
372 struct GNUNET_PQ_QueryParam res = {
373 .conv = &qconv_rsa_public_key,
374 .data = x,
375 .num_params = 1
376 };
377
378 return res;
379}
380
381
382/**
383 * Function called to convert input argument into SQL parameters.
384 *
385 * @param cls closure
386 * @param data pointer to input argument
387 * @param data_len number of bytes in @a data (if applicable)
388 * @param[out] param_values SQL data to set
389 * @param[out] param_lengths SQL length data to set
390 * @param[out] param_formats SQL format data to set
391 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
392 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
393 * @param scratch_length number of entries left in @a scratch
394 * @return -1 on error, number of offsets used in @a scratch otherwise
395 */
396static int
397qconv_rsa_signature (void *cls,
398 const void *data,
399 size_t data_len,
400 void *param_values[],
401 int param_lengths[],
402 int param_formats[],
403 unsigned int param_length,
404 void *scratch[],
405 unsigned int scratch_length)
406{
407 const struct GNUNET_CRYPTO_RsaSignature *sig = data;
408 void *buf;
409 size_t buf_size;
410
411 GNUNET_break (NULL == cls);
412 if (1 != param_length)
413 return -1;
414 buf_size = GNUNET_CRYPTO_rsa_signature_encode (sig,
415 &buf);
416 scratch[0] = buf;
417 param_values[0] = (void *) buf;
418 param_lengths[0] = buf_size;
419 param_formats[0] = 1;
420 return 1;
421}
422
423
424struct GNUNET_PQ_QueryParam
425GNUNET_PQ_query_param_rsa_signature (const struct GNUNET_CRYPTO_RsaSignature *x)
426{
427 struct GNUNET_PQ_QueryParam res = {
428 .conv = &qconv_rsa_signature,
429 .data = x,
430 .num_params = 1
431 };
432
433 return res;
434}
435
436
437/**
438 * Function called to convert input argument into SQL parameters.
439 *
440 * @param cls closure
441 * @param data pointer to input argument
442 * @param data_len number of bytes in @a data (if applicable)
443 * @param[out] param_values SQL data to set
444 * @param[out] param_lengths SQL length data to set
445 * @param[out] param_formats SQL format data to set
446 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
447 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
448 * @param scratch_length number of entries left in @a scratch
449 * @return -1 on error, number of offsets used in @a scratch otherwise
450 */
451static int
452qconv_rel_time (void *cls,
453 const void *data,
454 size_t data_len,
455 void *param_values[],
456 int param_lengths[],
457 int param_formats[],
458 unsigned int param_length,
459 void *scratch[],
460 unsigned int scratch_length)
461{
462 const struct GNUNET_TIME_Relative *u = data;
463 struct GNUNET_TIME_Relative rel;
464 uint64_t *u_nbo;
465
466 GNUNET_break (NULL == cls);
467 if (1 != param_length)
468 return -1;
469 rel = *u;
470 if (rel.rel_value_us > INT64_MAX)
471 rel.rel_value_us = INT64_MAX;
472 u_nbo = GNUNET_new (uint64_t);
473 scratch[0] = u_nbo;
474 *u_nbo = GNUNET_htonll (rel.rel_value_us);
475 param_values[0] = (void *) u_nbo;
476 param_lengths[0] = sizeof(uint64_t);
477 param_formats[0] = 1;
478 return 1;
479}
480
481
482struct GNUNET_PQ_QueryParam
483GNUNET_PQ_query_param_relative_time (const struct GNUNET_TIME_Relative *x)
484{
485 struct GNUNET_PQ_QueryParam res = {
486 .conv = &qconv_rel_time,
487 .data = x,
488 .size = sizeof(*x),
489 .num_params = 1
490 };
491
492 return res;
493}
494
495
496/**
497 * Function called to convert input argument into SQL parameters.
498 *
499 * @param cls closure
500 * @param data pointer to input argument
501 * @param data_len number of bytes in @a data (if applicable)
502 * @param[out] param_values SQL data to set
503 * @param[out] param_lengths SQL length data to set
504 * @param[out] param_formats SQL format data to set
505 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
506 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
507 * @param scratch_length number of entries left in @a scratch
508 * @return -1 on error, number of offsets used in @a scratch otherwise
509 */
510static int
511qconv_abs_time (void *cls,
512 const void *data,
513 size_t data_len,
514 void *param_values[],
515 int param_lengths[],
516 int param_formats[],
517 unsigned int param_length,
518 void *scratch[],
519 unsigned int scratch_length)
520{
521 const struct GNUNET_TIME_Absolute *u = data;
522 struct GNUNET_TIME_Absolute abs;
523 uint64_t *u_nbo;
524
525 GNUNET_break (NULL == cls);
526 if (1 != param_length)
527 return -1;
528 abs = *u;
529 if (abs.abs_value_us > INT64_MAX)
530 abs.abs_value_us = INT64_MAX;
531 u_nbo = GNUNET_new (uint64_t);
532 scratch[0] = u_nbo;
533 *u_nbo = GNUNET_htonll (abs.abs_value_us);
534 param_values[0] = (void *) u_nbo;
535 param_lengths[0] = sizeof(uint64_t);
536 param_formats[0] = 1;
537 return 1;
538}
539
540
541struct GNUNET_PQ_QueryParam
542GNUNET_PQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x)
543{
544 struct GNUNET_PQ_QueryParam res = {
545 .conv = &qconv_abs_time,
546 .data = x,
547 .size = sizeof(*x),
548 .num_params = 1
549 };
550
551 return res;
552}
553
554
555struct GNUNET_PQ_QueryParam
556GNUNET_PQ_query_param_absolute_time_nbo (
557 const struct GNUNET_TIME_AbsoluteNBO *x)
558{
559 return GNUNET_PQ_query_param_auto_from_type (&x->abs_value_us__);
560}
561
562
563struct GNUNET_PQ_QueryParam
564GNUNET_PQ_query_param_timestamp (const struct GNUNET_TIME_Timestamp *x)
565{
566 return GNUNET_PQ_query_param_absolute_time (&x->abs_time);
567}
568
569
570struct GNUNET_PQ_QueryParam
571GNUNET_PQ_query_param_timestamp_nbo (
572 const struct GNUNET_TIME_TimestampNBO *x)
573{
574 return GNUNET_PQ_query_param_absolute_time_nbo (&x->abs_time_nbo);
575}
576
577
578/**
579 * Closure for the array type handlers.
580 *
581 * May contain sizes information for the data, given (and handled) by the
582 * caller.
583 */
584struct qconv_array_cls
585{
586 /**
587 * If not null, contains the array of sizes (the size of the array is the
588 * .size field in the ambient GNUNET_PQ_QueryParam struct). We do not free
589 * this memory.
590 *
591 * If not null, this value has precedence over @a sizes, which MUST be NULL */
592 const size_t *sizes;
593
594 /**
595 * If @a size and @a c_sizes are NULL, this field defines the same size
596 * for each element in the array.
597 */
598 size_t same_size;
599
600 /**
601 * If true, the array parameter to the data pointer to the qconv_array is a
602 * continuous byte array of data, either with @a same_size each or sizes
603 * provided bytes by @a sizes;
604 */
605 bool continuous;
606
607 /**
608 * Type of the array elements
609 */
610 enum array_types typ;
611
612 /**
613 * Oid of the array elements
614 */
615 Oid oid;
616};
617
618/**
619 * Callback to cleanup a qconv_array_cls to be used during
620 * GNUNET_PQ_cleanup_query_params_closures
621 */
622static void
623qconv_array_cls_cleanup (void *cls)
624{
625 GNUNET_free (cls);
626}
627
628
629/**
630 * Function called to convert input argument into SQL parameters for arrays
631 *
632 * Note: the format for the encoding of arrays for libpq is not very well
633 * documented. We peeked into various sources (postgresql and libpqtypes) for
634 * guidance.
635 *
636 * @param cls Closure of type struct qconv_array_cls*
637 * @param data Pointer to first element in the array
638 * @param data_len Number of _elements_ in array @a data (if applicable)
639 * @param[out] param_values SQL data to set
640 * @param[out] param_lengths SQL length data to set
641 * @param[out] param_formats SQL format data to set
642 * @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
643 * @param[out] scratch buffer for dynamic allocations (to be done via #GNUNET_malloc()
644 * @param scratch_length number of entries left in @a scratch
645 * @return -1 on error, number of offsets used in @a scratch otherwise
646 */
647static int
648qconv_array (
649 void *cls,
650 const void *data,
651 size_t data_len,
652 void *param_values[],
653 int param_lengths[],
654 int param_formats[],
655 unsigned int param_length,
656 void *scratch[],
657 unsigned int scratch_length)
658{
659 struct qconv_array_cls *meta = cls;
660 size_t num = data_len;
661 size_t total_size;
662 const size_t *sizes;
663 bool same_sized;
664 size_t *string_lengths = NULL;
665 void *elements = NULL;
666 bool noerror = true;
667
668 (void) (param_length);
669 (void) (scratch_length);
670
671 GNUNET_assert (NULL != meta);
672 GNUNET_assert (num < INT_MAX);
673
674 sizes = meta->sizes;
675 same_sized = (0 != meta->same_size);
676
677#define RETURN_UNLESS(cond) \
678 do { \
679 if (! (cond)) \
680 { \
681 GNUNET_break ((cond)); \
682 noerror = false; \
683 goto DONE; \
684 } \
685 } while (0)
686
687 /* Calculate sizes and check bounds */
688 {
689 /* num * length-field */
690 size_t x = sizeof(uint32_t);
691 size_t y = x * num;
692 RETURN_UNLESS ((0 == num) || (y / num == x));
693
694 /* size of header */
695 total_size = x = sizeof(struct pq_array_header);
696 total_size += y;
697 RETURN_UNLESS (total_size >= x);
698
699 /* sizes of elements */
700 if (same_sized)
701 {
702 x = num * meta->same_size;
703 RETURN_UNLESS ((0 == num) || (x / num == meta->same_size));
704
705 y = total_size;
706 total_size += x;
707 RETURN_UNLESS (total_size >= y);
708 }
709 else /* sizes are different per element */
710 {
711 /* for an array of strings we need to get their length's first */
712 if (array_of_string == meta->typ)
713 {
714 string_lengths = GNUNET_new_array (num, size_t);
715
716 if (meta->continuous)
717 {
718 const char *ptr = data;
719 for (unsigned int i = 0; i < num; i++)
720 {
721 size_t len = strlen (ptr);
722 string_lengths[i] = len;
723 ptr += len + 1;
724 }
725 }
726 else
727 {
728 const char **str = (const char **) data;
729 for (unsigned int i = 0; i < num; i++)
730 string_lengths[i] = strlen (str[i]);
731 }
732
733 sizes = string_lengths;
734 }
735
736 for (unsigned int i = 0; i < num; i++)
737 {
738 x = total_size;
739 total_size += sizes[i];
740 RETURN_UNLESS (total_size >= x);
741 }
742 }
743
744 RETURN_UNLESS (total_size < INT_MAX);
745
746 elements = GNUNET_malloc (total_size);
747 }
748
749 /* Write data */
750 {
751 char *in = (char *) data;
752 char *out = elements;
753 size_t nullbyte = (array_of_string == meta->typ) ? 1 : 0;
754 struct pq_array_header h = {
755 .ndim = htonl (1), /* We only support one-dimensional arrays */
756 .has_null = htonl (0), /* We do not support NULL entries in arrays */
757 .lbound = htonl (1), /* Default start index value */
758 .dim = htonl (num),
759 .oid = htonl (meta->oid),
760 };
761
762 /* Write header */
763 GNUNET_memcpy (out, &h, sizeof(h));
764 out += sizeof(h);
765
766
767 /* Write elements */
768 for (unsigned int i = 0; i < num; i++)
769 {
770 size_t sz = same_sized ? meta->same_size : sizes[i];
771 size_t hsz = htonl (sz);
772
773 GNUNET_memcpy (out,
774 &hsz,
775 sizeof(hsz));
776 out += sizeof(uint32_t);
777
778 switch (meta->typ)
779 {
780 case array_of_bool:
781 {
782 GNUNET_assert (sizeof(bool) == sz);
783 *(bool *) out = (*(bool *) in);
784 in += sz;
785 break;
786 }
787 case array_of_uint16:
788 {
789 GNUNET_assert (sizeof(uint16_t) == sz);
790 *(uint16_t *) out = htons (*(uint16_t *) in);
791 in += sz;
792 break;
793 }
794 case array_of_uint32:
795 {
796 uint32_t v;
797 GNUNET_assert (sizeof(uint32_t) == sz);
798
799 v = htonl (*(uint32_t *) in);
800 GNUNET_memcpy (out,
801 &v,
802 sizeof(v));
803 in += sz;
804 break;
805 }
806 case array_of_uint64:
807 {
808 uint64_t tmp;
809 GNUNET_assert (sizeof(uint64_t) == sz);
810
811 tmp = GNUNET_htonll (*(uint64_t *) in);
812 GNUNET_memcpy (out,
813 &tmp,
814 sizeof(tmp));
815 in += sz;
816 break;
817 }
818 case array_of_byte:
819 case array_of_string:
820 {
821 const void *ptr;
822
823 if (meta->continuous)
824 {
825 ptr = in;
826 in += sz + nullbyte;
827 }
828 else
829 ptr = ((const void **) data)[i];
830
831 GNUNET_memcpy (out,
832 ptr,
833 sz);
834 break;
835 }
836 case array_of_abs_time:
837 case array_of_rel_time:
838 case array_of_timestamp:
839 {
840 uint64_t val;
841
842 switch (meta->typ)
843 {
844 case array_of_abs_time:
845 {
846 const struct GNUNET_TIME_Absolute *abs =
847 (const struct GNUNET_TIME_Absolute *) in;
848
849 GNUNET_assert (sizeof(struct GNUNET_TIME_Absolute) == sz);
850
851 if (! meta->continuous)
852 abs = ((const struct GNUNET_TIME_Absolute **) data)[i];
853
854 val = abs->abs_value_us;
855 break;
856 }
857 case array_of_rel_time:
858 {
859 const struct GNUNET_TIME_Relative *rel =
860 (const struct GNUNET_TIME_Relative *) in;
861
862 GNUNET_assert (sizeof(struct GNUNET_TIME_Relative) == sz);
863
864 if (! meta->continuous)
865 rel = ((const struct GNUNET_TIME_Relative **) data)[i];
866
867 val = rel->rel_value_us;
868 break;
869 }
870 case array_of_timestamp:
871 {
872 const struct GNUNET_TIME_Timestamp *ts =
873 (const struct GNUNET_TIME_Timestamp *) in;
874
875 GNUNET_assert (sizeof(struct GNUNET_TIME_Timestamp) == sz);
876
877 if (! meta->continuous)
878 ts = ((const struct GNUNET_TIME_Timestamp **) data)[i];
879
880 val = ts->abs_time.abs_value_us;
881 break;
882 }
883 default:
884 {
885 GNUNET_assert (0);
886 }
887 }
888
889 if (val > INT64_MAX)
890 val = INT64_MAX;
891
892 val = GNUNET_htonll (val);
893 GNUNET_memcpy (out,
894 &val,
895 sizeof(val));
896
897 if (meta->continuous)
898 in += sz;
899
900 break;
901 }
902 default:
903 {
904 GNUNET_assert (0);
905 break;
906 }
907 }
908 out += sz;
909 }
910 }
911
912 param_values[0] = elements;
913 param_lengths[0] = total_size;
914 param_formats[0] = 1;
915 scratch[0] = elements;
916
917DONE:
918 GNUNET_free (string_lengths);
919
920 if (noerror)
921 return 1;
922
923 return -1;
924}
925
926
927/**
928 * Function to genreate a typ specific query parameter and corresponding closure
929 *
930 * @param num Number of elements in @a elements
931 * @param continuous If true, @a elements is an continuous array of data
932 * @param elements Array of @a num elements, either continuous or pointers
933 * @param sizes Array of @a num sizes, one per element, may be NULL
934 * @param same_size If not 0, all elements in @a elements have this size
935 * @param typ Supported internal type of each element in @a elements
936 * @param oid Oid of the type to be used in Postgres
937 * @return Query parameter
938 */
939static struct GNUNET_PQ_QueryParam
940query_param_array_generic (
941 unsigned int num,
942 bool continuous,
943 const void *elements,
944 const size_t *sizes,
945 size_t same_size,
946 enum array_types typ,
947 Oid oid)
948{
949 struct qconv_array_cls *meta = GNUNET_new (struct qconv_array_cls);
950
951 meta->typ = typ;
952 meta->oid = oid;
953 meta->sizes = sizes;
954 meta->same_size = same_size;
955 meta->continuous = continuous;
956
957 {
958 struct GNUNET_PQ_QueryParam res = {
959 .conv = qconv_array,
960 .conv_cls = meta,
961 .conv_cls_cleanup = &qconv_array_cls_cleanup,
962 .data = elements,
963 .size = num,
964 .num_params = 1,
965 };
966
967 return res;
968 }
969}
970
971
972struct GNUNET_PQ_QueryParam
973GNUNET_PQ_query_param_array_bool (
974 unsigned int num,
975 const bool *elements,
976 struct GNUNET_PQ_Context *db)
977{
978 Oid oid;
979
980 GNUNET_assert (GNUNET_OK ==
981 GNUNET_PQ_get_oid_by_name (db,
982 "bool",
983 &oid));
984 return query_param_array_generic (num,
985 true,
986 elements,
987 NULL,
988 sizeof(bool),
989 array_of_bool,
990 oid);
991}
992
993
994struct GNUNET_PQ_QueryParam
995GNUNET_PQ_query_param_array_uint16 (
996 unsigned int num,
997 const uint16_t *elements,
998 struct GNUNET_PQ_Context *db)
999{
1000 Oid oid;
1001
1002 GNUNET_assert (GNUNET_OK ==
1003 GNUNET_PQ_get_oid_by_name (db,
1004 "int2",
1005 &oid));
1006 return query_param_array_generic (num,
1007 true,
1008 elements,
1009 NULL,
1010 sizeof(uint16_t),
1011 array_of_uint16,
1012 oid);
1013}
1014
1015
1016struct GNUNET_PQ_QueryParam
1017GNUNET_PQ_query_param_array_uint32 (
1018 unsigned int num,
1019 const uint32_t *elements,
1020 struct GNUNET_PQ_Context *db)
1021{
1022 Oid oid;
1023
1024 GNUNET_assert (GNUNET_OK ==
1025 GNUNET_PQ_get_oid_by_name (db,
1026 "int4",
1027 &oid));
1028 return query_param_array_generic (num,
1029 true,
1030 elements,
1031 NULL,
1032 sizeof(uint32_t),
1033 array_of_uint32,
1034 oid);
1035}
1036
1037
1038struct GNUNET_PQ_QueryParam
1039GNUNET_PQ_query_param_array_uint64 (
1040 unsigned int num,
1041 const uint64_t *elements,
1042 struct GNUNET_PQ_Context *db)
1043{
1044 Oid oid;
1045
1046 GNUNET_assert (GNUNET_OK ==
1047 GNUNET_PQ_get_oid_by_name (db,
1048 "int8",
1049 &oid));
1050 return query_param_array_generic (num,
1051 true,
1052 elements,
1053 NULL,
1054 sizeof(uint64_t),
1055 array_of_uint64,
1056 oid);
1057}
1058
1059
1060struct GNUNET_PQ_QueryParam
1061GNUNET_PQ_query_param_array_bytes (
1062 unsigned int num,
1063 const void *elements,
1064 const size_t *sizes,
1065 struct GNUNET_PQ_Context *db)
1066{
1067 Oid oid;
1068
1069 GNUNET_assert (GNUNET_OK ==
1070 GNUNET_PQ_get_oid_by_name (db,
1071 "bytea",
1072 &oid));
1073 return query_param_array_generic (num,
1074 true,
1075 elements,
1076 sizes,
1077 0,
1078 array_of_byte,
1079 oid);
1080}
1081
1082
1083struct GNUNET_PQ_QueryParam
1084GNUNET_PQ_query_param_array_ptrs_bytes (
1085 unsigned int num,
1086 const void *elements[static num],
1087 const size_t *sizes,
1088 struct GNUNET_PQ_Context *db)
1089{
1090 Oid oid;
1091
1092 GNUNET_assert (GNUNET_OK ==
1093 GNUNET_PQ_get_oid_by_name (db,
1094 "bytea",
1095 &oid));
1096 return query_param_array_generic (num,
1097 false,
1098 elements,
1099 sizes,
1100 0,
1101 array_of_byte,
1102 oid);
1103}
1104
1105
1106struct GNUNET_PQ_QueryParam
1107GNUNET_PQ_query_param_array_bytes_same_size (
1108 unsigned int num,
1109 const void *elements,
1110 size_t same_size,
1111 struct GNUNET_PQ_Context *db)
1112{
1113 Oid oid;
1114
1115 GNUNET_assert (GNUNET_OK ==
1116 GNUNET_PQ_get_oid_by_name (db,
1117 "bytea",
1118 &oid));
1119 return query_param_array_generic (num,
1120 true,
1121 elements,
1122 NULL,
1123 same_size,
1124 array_of_byte,
1125 oid);
1126}
1127
1128
1129struct GNUNET_PQ_QueryParam
1130GNUNET_PQ_query_param_array_ptrs_bytes_same_size (
1131 unsigned int num,
1132 const void *elements[static num],
1133 size_t same_size,
1134 struct GNUNET_PQ_Context *db)
1135{
1136 Oid oid;
1137
1138 GNUNET_assert (GNUNET_OK ==
1139 GNUNET_PQ_get_oid_by_name (db,
1140 "bytea",
1141 &oid));
1142 return query_param_array_generic (num,
1143 false,
1144 elements,
1145 NULL,
1146 same_size,
1147 array_of_byte,
1148 oid);
1149}
1150
1151
1152struct GNUNET_PQ_QueryParam
1153GNUNET_PQ_query_param_array_string (
1154 unsigned int num,
1155 const char *elements,
1156 struct GNUNET_PQ_Context *db)
1157{
1158 Oid oid;
1159
1160 GNUNET_assert (GNUNET_OK ==
1161 GNUNET_PQ_get_oid_by_name (db,
1162 "text",
1163 &oid));
1164 return query_param_array_generic (num,
1165 true,
1166 elements,
1167 NULL,
1168 0,
1169 array_of_string,
1170 oid);
1171}
1172
1173
1174struct GNUNET_PQ_QueryParam
1175GNUNET_PQ_query_param_array_ptrs_string (
1176 unsigned int num,
1177 const char *elements[static num],
1178 struct GNUNET_PQ_Context *db)
1179{
1180 Oid oid;
1181
1182 GNUNET_assert (GNUNET_OK ==
1183 GNUNET_PQ_get_oid_by_name (db,
1184 "text",
1185 &oid));
1186 return query_param_array_generic (num,
1187 false,
1188 elements,
1189 NULL,
1190 0,
1191 array_of_string,
1192 oid);
1193}
1194
1195
1196struct GNUNET_PQ_QueryParam
1197GNUNET_PQ_query_param_array_abs_time (
1198 unsigned int num,
1199 const struct GNUNET_TIME_Absolute *elements,
1200 struct GNUNET_PQ_Context *db)
1201{
1202 Oid oid;
1203
1204 GNUNET_assert (GNUNET_OK ==
1205 GNUNET_PQ_get_oid_by_name (db,
1206 "int8",
1207 &oid));
1208 return query_param_array_generic (num,
1209 true,
1210 elements,
1211 NULL,
1212 sizeof(struct GNUNET_TIME_Absolute),
1213 array_of_abs_time,
1214 oid);
1215}
1216
1217
1218struct GNUNET_PQ_QueryParam
1219GNUNET_PQ_query_param_array_ptrs_abs_time (
1220 unsigned int num,
1221 const struct GNUNET_TIME_Absolute *elements[],
1222 struct GNUNET_PQ_Context *db)
1223{
1224 Oid oid;
1225
1226 GNUNET_assert (GNUNET_OK ==
1227 GNUNET_PQ_get_oid_by_name (db,
1228 "int8",
1229 &oid));
1230 return query_param_array_generic (num,
1231 false,
1232 elements,
1233 NULL,
1234 sizeof(struct GNUNET_TIME_Absolute),
1235 array_of_abs_time,
1236 oid);
1237}
1238
1239
1240struct GNUNET_PQ_QueryParam
1241GNUNET_PQ_query_param_array_rel_time (
1242 unsigned int num,
1243 const struct GNUNET_TIME_Relative *elements,
1244 struct GNUNET_PQ_Context *db)
1245{
1246 Oid oid;
1247
1248 GNUNET_assert (GNUNET_OK ==
1249 GNUNET_PQ_get_oid_by_name (db,
1250 "int8",
1251 &oid));
1252 return query_param_array_generic (num,
1253 true,
1254 elements,
1255 NULL,
1256 sizeof(struct GNUNET_TIME_Relative),
1257 array_of_abs_time,
1258 oid);
1259}
1260
1261
1262struct GNUNET_PQ_QueryParam
1263GNUNET_PQ_query_param_array_ptrs_rel_time (
1264 unsigned int num,
1265 const struct GNUNET_TIME_Relative *elements[],
1266 struct GNUNET_PQ_Context *db)
1267{
1268 Oid oid;
1269
1270 GNUNET_assert (GNUNET_OK ==
1271 GNUNET_PQ_get_oid_by_name (db,
1272 "int8",
1273 &oid));
1274 return query_param_array_generic (num,
1275 false,
1276 elements,
1277 NULL,
1278 sizeof(struct GNUNET_TIME_Relative),
1279 array_of_abs_time,
1280 oid);
1281}
1282
1283
1284struct GNUNET_PQ_QueryParam
1285GNUNET_PQ_query_param_array_timestamp (
1286 unsigned int num,
1287 const struct GNUNET_TIME_Timestamp *elements,
1288 struct GNUNET_PQ_Context *db)
1289{
1290 Oid oid;
1291
1292 GNUNET_assert (GNUNET_OK ==
1293 GNUNET_PQ_get_oid_by_name (db,
1294 "int8",
1295 &oid));
1296 return query_param_array_generic (num,
1297 true,
1298 elements,
1299 NULL,
1300 sizeof(struct GNUNET_TIME_Timestamp),
1301 array_of_timestamp,
1302 oid);
1303}
1304
1305
1306struct GNUNET_PQ_QueryParam
1307GNUNET_PQ_query_param_array_ptrs_timestamp (
1308 unsigned int num,
1309 const struct GNUNET_TIME_Timestamp *elements[],
1310 struct GNUNET_PQ_Context *db)
1311{
1312 Oid oid;
1313
1314 GNUNET_assert (GNUNET_OK ==
1315 GNUNET_PQ_get_oid_by_name (db,
1316 "int8",
1317 &oid));
1318 return query_param_array_generic (num,
1319 false,
1320 elements,
1321 NULL,
1322 sizeof(struct GNUNET_TIME_Timestamp),
1323 array_of_timestamp,
1324 oid);
1325}
1326
1327
1328/* 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..f230826cb
--- /dev/null
+++ b/src/lib/pq/pq_result_helper.c
@@ -0,0 +1,1714 @@
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_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 * Closure for the array result specifications. Contains type information
1126 * for the generic parser extract_array_generic and out-pointers for the results.
1127 */
1128struct array_result_cls
1129{
1130 /* Oid of the expected type, must match the oid in the header of the PQResult struct */
1131 Oid oid;
1132
1133 /* Target type */
1134 enum array_types typ;
1135
1136 /* If not 0, defines the expected size of each entry */
1137 size_t same_size;
1138
1139 /* Out-pointer to write the number of elements in the array */
1140 size_t *num;
1141
1142 /* Out-pointer. If @a typ is array_of_byte and @a same_size is 0,
1143 * allocate and put the array of @a num sizes here. NULL otherwise */
1144 size_t **sizes;
1145};
1146
1147
1148/**
1149 * Extract data from a Postgres database @a result as array of a specific type
1150 * from row @a row. The type information and optionally additional
1151 * out-parameters are given in @a cls which is of type array_result_cls.
1152 *
1153 * @param cls closure of type array_result_cls
1154 * @param result where to extract data from
1155 * @param row row to extract data from
1156 * @param fname name (or prefix) of the fields to extract from
1157 * @param[in,out] dst_size where to store size of result, may be NULL
1158 * @param[out] dst where to store the result
1159 * @return
1160 * #GNUNET_YES if all results could be extracted
1161 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
1162 */
1163static enum GNUNET_GenericReturnValue
1164extract_array_generic (
1165 void *cls,
1166 PGresult *result,
1167 int row,
1168 const char *fname,
1169 size_t *dst_size,
1170 void *dst)
1171{
1172 const struct array_result_cls *info = cls;
1173 int data_sz;
1174 char *data;
1175 void *out = NULL;
1176 struct pq_array_header header;
1177 int col_num;
1178
1179 GNUNET_assert (NULL != dst);
1180 *((void **) dst) = NULL;
1181
1182 #define FAIL_IF(cond) \
1183 do { \
1184 if ((cond)) \
1185 { \
1186 GNUNET_break (! (cond)); \
1187 goto FAIL; \
1188 } \
1189 } while (0)
1190
1191 col_num = PQfnumber (result, fname);
1192 FAIL_IF (0 > col_num);
1193
1194 data_sz = PQgetlength (result, row, col_num);
1195 FAIL_IF (0 > data_sz);
1196 FAIL_IF (sizeof(header) > (size_t) data_sz);
1197
1198 data = PQgetvalue (result, row, col_num);
1199 FAIL_IF (NULL == data);
1200
1201 {
1202 struct pq_array_header *h =
1203 (struct pq_array_header *) data;
1204
1205 header.ndim = ntohl (h->ndim);
1206 header.has_null = ntohl (h->has_null);
1207 header.oid = ntohl (h->oid);
1208 header.dim = ntohl (h->dim);
1209 header.lbound = ntohl (h->lbound);
1210
1211 FAIL_IF (1 != header.ndim);
1212 FAIL_IF (INT_MAX <= header.dim);
1213 FAIL_IF (0 != header.has_null);
1214 FAIL_IF (1 != header.lbound);
1215 FAIL_IF (info->oid != header.oid);
1216 }
1217
1218 if (NULL != info->num)
1219 *info->num = header.dim;
1220
1221 {
1222 char *in = data + sizeof(header);
1223
1224 switch (info->typ)
1225 {
1226 case array_of_bool:
1227 if (NULL != dst_size)
1228 *dst_size = sizeof(bool) * (header.dim);
1229 out = GNUNET_new_array (header.dim, bool);
1230 *((void **) dst) = out;
1231 for (uint32_t i = 0; i < header.dim; i++)
1232 {
1233 size_t sz = ntohl (*(uint32_t *) in);
1234 FAIL_IF (sz != sizeof(bool));
1235 in += sizeof(uint32_t);
1236 *(bool *) out = *(bool *) in;
1237 in += sz;
1238 out += sz;
1239 }
1240 break;
1241
1242 case array_of_uint16:
1243 if (NULL != dst_size)
1244 *dst_size = sizeof(uint16_t) * (header.dim);
1245 out = GNUNET_new_array (header.dim, uint16_t);
1246 *((void **) dst) = out;
1247 for (uint32_t i = 0; i < header.dim; i++)
1248 {
1249 size_t sz = ntohl (*(uint32_t *) in);
1250 FAIL_IF (sz != sizeof(uint16_t));
1251 in += sizeof(uint32_t);
1252 *(uint16_t *) out = ntohs (*(uint16_t *) in);
1253 in += sz;
1254 out += sz;
1255 }
1256 break;
1257
1258 case array_of_uint32:
1259 if (NULL != dst_size)
1260 *dst_size = sizeof(uint32_t) * (header.dim);
1261 out = GNUNET_new_array (header.dim, uint32_t);
1262 *((void **) dst) = out;
1263 for (uint32_t i = 0; i < header.dim; i++)
1264 {
1265 size_t sz = ntohl (*(uint32_t *) in);
1266 FAIL_IF (sz != sizeof(uint32_t));
1267 in += sizeof(uint32_t);
1268 *(uint32_t *) out = ntohl (*(uint32_t *) in);
1269 in += sz;
1270 out += sz;
1271 }
1272 break;
1273
1274 case array_of_uint64:
1275 if (NULL != dst_size)
1276 *dst_size = sizeof(uint64_t) * (header.dim);
1277 out = GNUNET_new_array (header.dim, uint64_t);
1278 *((void **) dst) = out;
1279 for (uint32_t i = 0; i < header.dim; i++)
1280 {
1281 size_t sz = ntohl (*(uint32_t *) in);
1282 FAIL_IF (sz != sizeof(uint64_t));
1283 in += sizeof(uint32_t);
1284 *(uint64_t *) out = GNUNET_ntohll (*(uint64_t *) in);
1285 in += sz;
1286 out += sz;
1287 }
1288 break;
1289
1290 case array_of_abs_time:
1291 if (NULL != dst_size)
1292 *dst_size = sizeof(struct GNUNET_TIME_Absolute) * (header.dim);
1293 out = GNUNET_new_array (header.dim, struct GNUNET_TIME_Absolute);
1294 *((void **) dst) = out;
1295 for (uint32_t i = 0; i < header.dim; i++)
1296 {
1297 size_t sz = ntohl (*(uint32_t *) in);
1298 FAIL_IF (sz != sizeof(uint64_t));
1299 in += sizeof(uint32_t);
1300 ((struct GNUNET_TIME_Absolute *) out)->abs_value_us =
1301 GNUNET_ntohll (*(uint64_t *) in);
1302 in += sz;
1303 out += sz;
1304 }
1305 break;
1306
1307 case array_of_rel_time:
1308 if (NULL != dst_size)
1309 *dst_size = sizeof(struct GNUNET_TIME_Relative) * (header.dim);
1310 out = GNUNET_new_array (header.dim, struct GNUNET_TIME_Relative);
1311 *((void **) dst) = out;
1312 for (uint32_t i = 0; i < header.dim; i++)
1313 {
1314 size_t sz = ntohl (*(uint32_t *) in);
1315 FAIL_IF (sz != sizeof(uint64_t));
1316 in += sizeof(uint32_t);
1317 ((struct GNUNET_TIME_Relative *) out)->rel_value_us =
1318 GNUNET_ntohll (*(uint64_t *) in);
1319 in += sz;
1320 out += sz;
1321 }
1322 break;
1323
1324 case array_of_timestamp:
1325 if (NULL != dst_size)
1326 *dst_size = sizeof(struct GNUNET_TIME_Timestamp) * (header.dim);
1327 out = GNUNET_new_array (header.dim, struct GNUNET_TIME_Timestamp);
1328 *((void **) dst) = out;
1329 for (uint32_t i = 0; i < header.dim; i++)
1330 {
1331 size_t sz = ntohl (*(uint32_t *) in);
1332 FAIL_IF (sz != sizeof(uint64_t));
1333 in += sizeof(uint32_t);
1334 ((struct GNUNET_TIME_Timestamp *) out)->abs_time.abs_value_us =
1335 GNUNET_ntohll (*(uint64_t *) in);
1336 in += sz;
1337 out += sz;
1338 }
1339 break;
1340
1341 case array_of_byte:
1342 if (0 == info->same_size)
1343 *info->sizes = GNUNET_new_array (header.dim, size_t);
1344 /* fallthrough */
1345 case array_of_string:
1346 {
1347 size_t total = 0;
1348 bool is_string = (array_of_string == info->typ);
1349
1350 /* first, calculate total size required for allocation */
1351 {
1352 char *ptr = data + sizeof(header);
1353 for (uint32_t i = 0; i < header.dim; i++)
1354 {
1355 uint32_t sz;
1356
1357 sz = ntohl (*(uint32_t *) ptr);
1358 sz += is_string ? 1 : 0;
1359 total += sz;
1360 ptr += sizeof(uint32_t);
1361 ptr += sz;
1362
1363 if ((! is_string) &&
1364 (0 == info->same_size))
1365 (*info->sizes)[i] = sz;
1366
1367 FAIL_IF ((0 != info->same_size) &&
1368 (sz != info->same_size));
1369 FAIL_IF (total < sz);
1370 }
1371 }
1372
1373 if (NULL != dst_size)
1374 *dst_size = total;
1375
1376 FAIL_IF (0 == total);
1377 out = GNUNET_malloc (total);
1378
1379 *((void **) dst) = out;
1380
1381 /* copy data */
1382 for (uint32_t i = 0; i < header.dim; i++)
1383 {
1384 size_t sz = ntohl (*(uint32_t *) in);
1385 in += sizeof(uint32_t);
1386 GNUNET_memcpy (out, in, sz);
1387
1388 in += sz;
1389 out += sz;
1390 out += (array_of_string == info->typ) ? 1 : 0;
1391 }
1392 break;
1393 }
1394 default:
1395 FAIL_IF (1 != 0);
1396 }
1397 }
1398
1399 return GNUNET_OK;
1400
1401FAIL:
1402 GNUNET_free (*(void **) dst);
1403 return GNUNET_SYSERR;
1404 #undef FAIL_IF
1405}
1406
1407
1408/**
1409 * Cleanup of the data and closure of an array spec.
1410 */
1411static void
1412array_cleanup (void *cls,
1413 void *rd)
1414{
1415
1416 struct array_result_cls *info = cls;
1417 void **dst = rd;
1418
1419 if ((array_of_byte == info->typ) &&
1420 (0 == info->same_size) &&
1421 (NULL != info->sizes))
1422 GNUNET_free (*(info->sizes));
1423
1424 GNUNET_free (cls);
1425 GNUNET_free (*dst);
1426 *dst = NULL;
1427}
1428
1429
1430struct GNUNET_PQ_ResultSpec
1431GNUNET_PQ_result_spec_array_bool (
1432 struct GNUNET_PQ_Context *db,
1433 const char *name,
1434 size_t *num,
1435 bool **dst)
1436{
1437 struct array_result_cls *info =
1438 GNUNET_new (struct array_result_cls);
1439
1440 info->num = num;
1441 info->typ = array_of_bool;
1442 GNUNET_assert (GNUNET_OK ==
1443 GNUNET_PQ_get_oid_by_name (db,
1444 "bool",
1445 &info->oid));
1446
1447 struct GNUNET_PQ_ResultSpec res = {
1448 .conv = extract_array_generic,
1449 .cleaner = array_cleanup,
1450 .dst = (void *) dst,
1451 .fname = name,
1452 .cls = info
1453 };
1454 return res;
1455}
1456
1457
1458struct GNUNET_PQ_ResultSpec
1459GNUNET_PQ_result_spec_array_uint16 (
1460 struct GNUNET_PQ_Context *db,
1461 const char *name,
1462 size_t *num,
1463 uint16_t **dst)
1464{
1465 struct array_result_cls *info =
1466 GNUNET_new (struct array_result_cls);
1467
1468 info->num = num;
1469 info->typ = array_of_uint16;
1470 GNUNET_assert (GNUNET_OK ==
1471 GNUNET_PQ_get_oid_by_name (db,
1472 "int2",
1473 &info->oid));
1474
1475 struct GNUNET_PQ_ResultSpec res = {
1476 .conv = extract_array_generic,
1477 .cleaner = array_cleanup,
1478 .dst = (void *) dst,
1479 .fname = name,
1480 .cls = info
1481 };
1482 return res;
1483}
1484
1485
1486struct GNUNET_PQ_ResultSpec
1487GNUNET_PQ_result_spec_array_uint32 (
1488 struct GNUNET_PQ_Context *db,
1489 const char *name,
1490 size_t *num,
1491 uint32_t **dst)
1492{
1493 struct array_result_cls *info =
1494 GNUNET_new (struct array_result_cls);
1495
1496 info->num = num;
1497 info->typ = array_of_uint32;
1498 GNUNET_assert (GNUNET_OK ==
1499 GNUNET_PQ_get_oid_by_name (db,
1500 "int4",
1501 &info->oid));
1502
1503 struct GNUNET_PQ_ResultSpec res = {
1504 .conv = extract_array_generic,
1505 .cleaner = array_cleanup,
1506 .dst = (void *) dst,
1507 .fname = name,
1508 .cls = info
1509 };
1510 return res;
1511}
1512
1513
1514struct GNUNET_PQ_ResultSpec
1515GNUNET_PQ_result_spec_array_uint64 (
1516 struct GNUNET_PQ_Context *db,
1517 const char *name,
1518 size_t *num,
1519 uint64_t **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_uint64;
1526 GNUNET_assert (GNUNET_OK ==
1527 GNUNET_PQ_get_oid_by_name (db,
1528 "int8",
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_abs_time (
1544 struct GNUNET_PQ_Context *db,
1545 const char *name,
1546 size_t *num,
1547 struct GNUNET_TIME_Absolute **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_abs_time;
1554 GNUNET_assert (GNUNET_OK ==
1555 GNUNET_PQ_get_oid_by_name (db,
1556 "int8",
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_rel_time (
1572 struct GNUNET_PQ_Context *db,
1573 const char *name,
1574 size_t *num,
1575 struct GNUNET_TIME_Relative **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_rel_time;
1582 GNUNET_assert (GNUNET_OK ==
1583 GNUNET_PQ_get_oid_by_name (db,
1584 "int8",
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_timestamp (
1600 struct GNUNET_PQ_Context *db,
1601 const char *name,
1602 size_t *num,
1603 struct GNUNET_TIME_Timestamp **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_timestamp;
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_variable_size (
1628 struct GNUNET_PQ_Context *db,
1629 const char *name,
1630 size_t *num,
1631 size_t **sizes,
1632 void **dst)
1633{
1634 struct array_result_cls *info =
1635 GNUNET_new (struct array_result_cls);
1636
1637 info->num = num;
1638 info->sizes = sizes;
1639 info->typ = array_of_byte;
1640 GNUNET_assert (GNUNET_OK ==
1641 GNUNET_PQ_get_oid_by_name (db,
1642 "bytea",
1643 &info->oid));
1644
1645 struct GNUNET_PQ_ResultSpec res = {
1646 .conv = extract_array_generic,
1647 .cleaner = array_cleanup,
1648 .dst = (void *) dst,
1649 .fname = name,
1650 .cls = info
1651 };
1652 return res;
1653}
1654
1655
1656struct GNUNET_PQ_ResultSpec
1657GNUNET_PQ_result_spec_array_fixed_size (
1658 struct GNUNET_PQ_Context *db,
1659 const char *name,
1660 size_t size,
1661 size_t *num,
1662 void **dst)
1663{
1664 struct array_result_cls *info =
1665 GNUNET_new (struct array_result_cls);
1666
1667 info->num = num;
1668 info->same_size = size;
1669 info->typ = array_of_byte;
1670 GNUNET_assert (GNUNET_OK ==
1671 GNUNET_PQ_get_oid_by_name (db,
1672 "bytea",
1673 &info->oid));
1674
1675 struct GNUNET_PQ_ResultSpec res = {
1676 .conv = extract_array_generic,
1677 .cleaner = array_cleanup,
1678 .dst = (void *) dst,
1679 .fname = name,
1680 .cls = info
1681 };
1682 return res;
1683}
1684
1685
1686struct GNUNET_PQ_ResultSpec
1687GNUNET_PQ_result_spec_array_string (
1688 struct GNUNET_PQ_Context *db,
1689 const char *name,
1690 size_t *num,
1691 char **dst)
1692{
1693 struct array_result_cls *info =
1694 GNUNET_new (struct array_result_cls);
1695
1696 info->num = num;
1697 info->typ = array_of_string;
1698 GNUNET_assert (GNUNET_OK ==
1699 GNUNET_PQ_get_oid_by_name (db,
1700 "text",
1701 &info->oid));
1702
1703 struct GNUNET_PQ_ResultSpec res = {
1704 .conv = extract_array_generic,
1705 .cleaner = array_cleanup,
1706 .dst = (void *) dst,
1707 .fname = name,
1708 .cls = info
1709 };
1710 return res;
1711}
1712
1713
1714/* 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..d70e18e6d
--- /dev/null
+++ b/src/lib/pq/test_pq.c
@@ -0,0 +1,629 @@
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 u16 = 16;
173 u32 = 32;
174 u64 = 64;
175 for (int i = 0; i < 3; i++)
176 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
177 &ahc[i],
178 sizeof(ahc[i]));
179
180 /* FIXME: test GNUNET_PQ_result_spec_variable_size */
181 {
182 struct GNUNET_PQ_QueryParam params_insert[] = {
183 GNUNET_PQ_query_param_rsa_public_key (pub),
184 GNUNET_PQ_query_param_rsa_signature (sig),
185 GNUNET_PQ_query_param_absolute_time (&abs_time),
186 GNUNET_PQ_query_param_absolute_time (&forever),
187 GNUNET_PQ_query_param_auto_from_type (&hc),
188 GNUNET_PQ_query_param_string (msg),
189 GNUNET_PQ_query_param_uint16 (&u16),
190 GNUNET_PQ_query_param_uint32 (&u32),
191 GNUNET_PQ_query_param_uint64 (&u64),
192 GNUNET_PQ_query_param_null (),
193 GNUNET_PQ_query_param_array_bool (5, ab, db),
194 GNUNET_PQ_query_param_array_uint16 (3, ai2, db),
195 GNUNET_PQ_query_param_array_uint32 (3, ai4, db),
196 GNUNET_PQ_query_param_array_uint64 (3, ai8, db),
197 GNUNET_PQ_query_param_array_bytes_same_size (3,
198 ahc,
199 sizeof(ahc[0]),
200 db),
201 GNUNET_PQ_query_param_array_ptrs_string (3, as, db),
202 GNUNET_PQ_query_param_array_abs_time (2, ata, db),
203 GNUNET_PQ_query_param_array_rel_time (2, atr, db),
204 GNUNET_PQ_query_param_array_timestamp (2, ats, db),
205 GNUNET_PQ_query_param_end
206 };
207 struct GNUNET_PQ_QueryParam params_select[] = {
208 GNUNET_PQ_query_param_end
209 };
210 bool got_null = false;
211 size_t num_bool;
212 bool *arr_bools;
213 size_t num_u16;
214 uint16_t *arr_u16;
215 size_t num_u32;
216 uint32_t *arr_u32;
217 size_t num_u64;
218 uint64_t *arr_u64;
219 size_t num_abs;
220 struct GNUNET_TIME_Absolute *arr_abs;
221 size_t num_rel;
222 struct GNUNET_TIME_Relative *arr_rel;
223 size_t num_tstmp;
224 struct GNUNET_TIME_Timestamp *arr_tstmp;
225 size_t num_str;
226 char *arr_str;
227 size_t num_hash;
228 struct GNUNET_HashCode *arr_hash;
229 size_t num_buf;
230 void *arr_buf;
231 size_t *sz_buf;
232 struct GNUNET_PQ_ResultSpec results_select[] = {
233 GNUNET_PQ_result_spec_rsa_public_key ("pub", &pub2),
234 GNUNET_PQ_result_spec_rsa_signature ("sig", &sig2),
235 GNUNET_PQ_result_spec_absolute_time ("abs_time", &abs_time2),
236 GNUNET_PQ_result_spec_absolute_time ("forever", &forever2),
237 GNUNET_PQ_result_spec_auto_from_type ("hash", &hc2),
238 GNUNET_PQ_result_spec_variable_size ("vsize", &msg2, &msg2_len),
239 GNUNET_PQ_result_spec_uint16 ("u16", &u162),
240 GNUNET_PQ_result_spec_uint32 ("u32", &u322),
241 GNUNET_PQ_result_spec_uint64 ("u64", &u642),
242 GNUNET_PQ_result_spec_allow_null (
243 GNUNET_PQ_result_spec_uint64 ("unn", &uzzz),
244 &got_null),
245 GNUNET_PQ_result_spec_array_bool (db,
246 "arr_bool",
247 &num_bool,
248 &arr_bools),
249 GNUNET_PQ_result_spec_array_uint16 (db,
250 "arr_int2",
251 &num_u16,
252 &arr_u16),
253 GNUNET_PQ_result_spec_array_uint32 (db,
254 "arr_int4",
255 &num_u32,
256 &arr_u32),
257 GNUNET_PQ_result_spec_array_uint64 (db,
258 "arr_int8",
259 &num_u64,
260 &arr_u64),
261 GNUNET_PQ_result_spec_array_abs_time (db,
262 "arr_abs_time",
263 &num_abs,
264 &arr_abs),
265 GNUNET_PQ_result_spec_array_rel_time (db,
266 "arr_rel_time",
267 &num_rel,
268 &arr_rel),
269 GNUNET_PQ_result_spec_array_timestamp (db,
270 "arr_timestamp",
271 &num_tstmp,
272 &arr_tstmp),
273 GNUNET_PQ_result_spec_auto_array_from_type (db,
274 "arr_bytea",
275 &num_hash,
276 arr_hash),
277 GNUNET_PQ_result_spec_array_variable_size (db,
278 "arr_bytea",
279 &num_buf,
280 &sz_buf,
281 &arr_buf),
282 GNUNET_PQ_result_spec_array_string (db,
283 "arr_text",
284 &num_str,
285 &arr_str),
286 GNUNET_PQ_result_spec_end
287 };
288
289 result = GNUNET_PQ_exec_prepared (db,
290 "test_insert",
291 params_insert);
292 if (PGRES_COMMAND_OK != PQresultStatus (result))
293 {
294 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
295 "Database failure: %s\n",
296 PQresultErrorMessage (result));
297 PQclear (result);
298 GNUNET_CRYPTO_rsa_signature_free (sig);
299 GNUNET_CRYPTO_rsa_private_key_free (priv);
300 GNUNET_CRYPTO_rsa_public_key_free (pub);
301 return 1;
302 }
303
304 PQclear (result);
305 result = GNUNET_PQ_exec_prepared (db,
306 "test_select",
307 params_select);
308 if (1 !=
309 PQntuples (result))
310 {
311 GNUNET_break (0);
312 PQclear (result);
313 GNUNET_CRYPTO_rsa_signature_free (sig);
314 GNUNET_CRYPTO_rsa_private_key_free (priv);
315 GNUNET_CRYPTO_rsa_public_key_free (pub);
316 return 1;
317 }
318 ret = GNUNET_PQ_extract_result (result,
319 results_select,
320 0);
321 GNUNET_break (GNUNET_YES == ret);
322 GNUNET_break (abs_time.abs_value_us == abs_time2.abs_value_us);
323 GNUNET_break (forever.abs_value_us == forever2.abs_value_us);
324 GNUNET_break (0 ==
325 GNUNET_memcmp (&hc,
326 &hc2));
327 GNUNET_break (0 ==
328 GNUNET_CRYPTO_rsa_signature_cmp (sig,
329 sig2));
330 GNUNET_break (0 ==
331 GNUNET_CRYPTO_rsa_public_key_cmp (pub,
332 pub2));
333 GNUNET_break (strlen (msg) == msg2_len);
334 GNUNET_break (0 ==
335 strncmp (msg,
336 msg2,
337 msg2_len));
338 GNUNET_break (16 == u162);
339 GNUNET_break (32 == u322);
340 GNUNET_break (64 == u642);
341 GNUNET_break (42 == uzzz);
342 GNUNET_break (got_null);
343
344 /* Check arrays */
345 GNUNET_break (num_bool == 5);
346 for (size_t i = 0; i < num_bool; i++)
347 GNUNET_break (arr_bools[i] == ab[i]);
348
349 GNUNET_break (num_u16 == 3);
350 for (size_t i = 0; i < num_u16; i++)
351 GNUNET_break (arr_u16[i] == ai2[i]);
352
353 GNUNET_break (num_u32 == 3);
354 for (size_t i = 0; i < num_u32; i++)
355 GNUNET_break (arr_u32[i] == ai4[i]);
356
357 GNUNET_break (num_u64 == 3);
358 for (size_t i = 0; i < num_u64; i++)
359 GNUNET_break (arr_u64[i] == ai8[i]);
360
361 GNUNET_break (num_abs == 2);
362 for (size_t i = 0; i < num_abs; i++)
363 GNUNET_break (arr_abs[i].abs_value_us == ata[i].abs_value_us);
364
365 GNUNET_break (num_rel == 2);
366 for (size_t i = 0; i < num_rel; i++)
367 GNUNET_break (arr_rel[i].rel_value_us == atr[i].rel_value_us);
368
369 GNUNET_break (num_tstmp == 2);
370 for (size_t i = 0; i < num_tstmp; i++)
371 GNUNET_break (arr_tstmp[i].abs_time.abs_value_us ==
372 ats[i].abs_time.abs_value_us);
373
374 GNUNET_break (num_str == 3);
375 GNUNET_break (0 == strcmp (arr_str, as[0]));
376 GNUNET_break (0 == strcmp (arr_str + 4, as[1]));
377 GNUNET_break (0 == strcmp (arr_str + 8, as[2]));
378
379 GNUNET_break (num_hash == 3);
380 for (size_t i = 0; i < num_hash; i++)
381 GNUNET_break (0 == GNUNET_memcmp (&arr_hash[i], &ahc[i]));
382
383 GNUNET_break (num_buf == 3);
384 for (size_t i = 0; i < num_buf; i++)
385 {
386 GNUNET_break (0 == memcmp (((char *) arr_buf) + i * sizeof(ahc[i]),
387 &ahc[i],
388 sizeof(ahc[i])));
389 }
390
391 GNUNET_PQ_cleanup_result (results_select);
392 PQclear (result);
393
394 GNUNET_PQ_cleanup_query_params_closures (params_insert);
395 }
396
397 GNUNET_CRYPTO_rsa_signature_free (sig);
398 GNUNET_CRYPTO_rsa_private_key_free (priv);
399 GNUNET_CRYPTO_rsa_public_key_free (pub);
400 if (GNUNET_OK != ret)
401 return 1;
402
403 return 0;
404}
405
406
407/**
408 * Task called on shutdown.
409 *
410 * @param cls NULL
411 */
412static void
413event_end (void *cls)
414{
415 GNUNET_PQ_event_listen_cancel (eh);
416 eh = NULL;
417 if (NULL != tt)
418 {
419 GNUNET_SCHEDULER_cancel (tt);
420 tt = NULL;
421 }
422}
423
424
425/**
426 * Task called on timeout. Should not happen, means
427 * we did not get the expected event.
428 *
429 * @param cls NULL
430 */
431static void
432timeout_cb (void *cls)
433{
434 ret = 2;
435 GNUNET_break (0);
436 tt = NULL;
437 GNUNET_SCHEDULER_shutdown ();
438}
439
440
441/**
442 * Task called on expected event
443 *
444 * @param cls NULL
445 */
446static void
447event_sched_cb (void *cls,
448 const void *extra,
449 size_t extra_size)
450{
451 GNUNET_assert (5 == extra_size);
452 GNUNET_assert (0 ==
453 memcmp ("hello",
454 extra,
455 5));
456 GNUNET_SCHEDULER_shutdown ();
457}
458
459
460/**
461 * Run tests that need a scheduler.
462 *
463 * @param cls NULL
464 */
465static void
466sched_tests (void *cls)
467{
468 struct GNUNET_DB_EventHeaderP es = {
469 .size = htons (sizeof (es)),
470 .type = htons (42)
471 };
472
473
474 tt = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
475 &timeout_cb,
476 NULL);
477 eh = GNUNET_PQ_event_listen (db,
478 &es,
479 GNUNET_TIME_UNIT_FOREVER_REL,
480 &event_sched_cb,
481 NULL);
482 GNUNET_PQ_reconnect (db);
483 GNUNET_SCHEDULER_add_shutdown (&event_end,
484 NULL);
485 GNUNET_PQ_event_notify (db,
486 &es,
487 "hello",
488 5);
489}
490
491
492int
493main (int argc,
494 const char *const argv[])
495{
496 struct GNUNET_PQ_ExecuteStatement es[] = {
497 GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
498 " pub BYTEA NOT NULL"
499 ",sig BYTEA NOT NULL"
500 ",abs_time INT8 NOT NULL"
501 ",forever INT8 NOT NULL"
502 ",hash BYTEA NOT NULL CHECK(LENGTH(hash)=64)"
503 ",vsize VARCHAR NOT NULL"
504 ",u16 INT2 NOT NULL"
505 ",u32 INT4 NOT NULL"
506 ",u64 INT8 NOT NULL"
507 ",unn INT8"
508 ",arr_bool BOOL[]"
509 ",arr_int2 INT2[]"
510 ",arr_int4 INT4[]"
511 ",arr_int8 INT8[]"
512 ",arr_bytea BYTEA[]"
513 ",arr_text TEXT[]"
514 ",arr_abs_time INT8[]"
515 ",arr_rel_time INT8[]"
516 ",arr_timestamp INT8[]"
517 ")"),
518 GNUNET_PQ_EXECUTE_STATEMENT_END
519 };
520
521 GNUNET_log_setup ("test-pq",
522 "INFO",
523 NULL);
524 db = GNUNET_PQ_connect ("postgres:///gnunetcheck",
525 NULL,
526 es,
527 NULL);
528 if (NULL == db)
529 {
530 fprintf (stderr,
531 "Cannot run test, database connection failed\n");
532 return 77;
533 }
534 if (CONNECTION_OK != PQstatus (db->conn))
535 {
536 fprintf (stderr,
537 "Cannot run test, database connection failed: %s\n",
538 PQerrorMessage (db->conn));
539 GNUNET_break (0);
540 GNUNET_PQ_disconnect (db);
541 return 77; /* signal test was skipped */
542 }
543 if (GNUNET_OK !=
544 postgres_prepare (db))
545 {
546 GNUNET_break (0);
547 GNUNET_PQ_disconnect (db);
548 return 1;
549 }
550 ret = run_queries (db);
551 if (0 != ret)
552 {
553 GNUNET_break (0);
554 GNUNET_PQ_disconnect (db);
555 return ret;
556 }
557
558 /* ensure oid lookup works */
559 {
560 enum GNUNET_GenericReturnValue ret;
561 Oid oid;
562
563 ret = GNUNET_PQ_get_oid_by_name (db, "int8", &oid);
564
565 if (GNUNET_OK != ret)
566 {
567 fprintf (stderr,
568 "Cannot lookup oid for int8: %s\n",
569 PQerrorMessage (db->conn));
570 GNUNET_break (0);
571 GNUNET_PQ_disconnect (db);
572 return 77; /* signal test was skipped */
573 }
574
575 PQexec (db->conn, "CREATE TYPE foo AS (foo int, bar int);");
576
577 ret = GNUNET_PQ_get_oid_by_name (db, "foo", &oid);
578 if (GNUNET_OK != ret)
579 {
580 fprintf (stderr,
581 "Cannot lookup oid for foo: %s\n",
582 PQerrorMessage (db->conn));
583 GNUNET_break (0);
584 GNUNET_PQ_disconnect (db);
585 return 77; /* signal test was skipped */
586 }
587
588 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
589 "got oid %d for type foo\n", oid);
590 }
591
592 GNUNET_SCHEDULER_run (&sched_tests,
593 NULL);
594 if (0 != ret)
595 {
596 GNUNET_break (0);
597 GNUNET_PQ_disconnect (db);
598 return ret;
599 }
600#if TEST_RESTART
601 fprintf (stderr, "Please restart Postgres database now!\n");
602 sleep (60);
603 ret |= run_queries (db);
604 fprintf (stderr, "Result: %d (expect: 1 -- if you restarted the DB)\n", ret);
605 ret |= run_queries (db);
606 fprintf (stderr, "Result: %d (expect: 0)\n", ret);
607#endif
608 {
609 struct GNUNET_PQ_ExecuteStatement es[] = {
610 GNUNET_PQ_make_execute ("DROP TABLE test_pq"),
611 GNUNET_PQ_EXECUTE_STATEMENT_END
612 };
613
614 if (GNUNET_OK !=
615 GNUNET_PQ_exec_statements (db,
616 es))
617 {
618 fprintf (stderr,
619 "Failed to drop table\n");
620 GNUNET_PQ_disconnect (db);
621 return 1;
622 }
623 }
624 GNUNET_PQ_disconnect (db);
625 return ret;
626}
627
628
629/* 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;
diff --git a/src/lib/sq/.gitignore b/src/lib/sq/.gitignore
new file mode 100644
index 000000000..951587047
--- /dev/null
+++ b/src/lib/sq/.gitignore
@@ -0,0 +1 @@
test_sq
diff --git a/src/lib/sq/Makefile.am b/src/lib/sq/Makefile.am
new file mode 100644
index 000000000..a77a380af
--- /dev/null
+++ b/src/lib/sq/Makefile.am
@@ -0,0 +1,38 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage
6endif
7
8if HAVE_SQLITE
9lib_LTLIBRARIES = libgnunetsq.la
10endif
11
12libgnunetsq_la_SOURCES = \
13 sq.c \
14 sq_exec.c \
15 sq_prepare.c \
16 sq_query_helper.c \
17 sq_result_helper.c
18libgnunetsq_la_LIBADD = -lsqlite3 \
19 $(top_builddir)/src/lib/util/libgnunetutil.la
20libgnunetsq_la_LDFLAGS = \
21 $(GN_LIBINTL) \
22 $(GN_LIB_LDFLAGS) \
23 -version-info 0:0:0
24
25if ENABLE_TEST_RUN
26TESTS = \
27 test_sq
28endif
29
30check_PROGRAMS= \
31 test_sq
32
33test_sq_SOURCES = \
34 test_sq.c
35test_sq_LDADD = \
36 libgnunetsq.la \
37 $(top_builddir)/src/lib/util/libgnunetutil.la \
38 -lsqlite3 $(XLIB)
diff --git a/src/lib/sq/meson.build b/src/lib/sq/meson.build
new file mode 100644
index 000000000..482d1c40a
--- /dev/null
+++ b/src/lib/sq/meson.build
@@ -0,0 +1,25 @@
1libgnunetsq_src = ['sq.c',
2 'sq_exec.c',
3 'sq_prepare.c',
4 'sq_query_helper.c',
5 'sq_result_helper.c']
6
7if get_option('monolith')
8 foreach p : libgnunetsq_src
9 gnunet_src += 'sq/' + p
10 endforeach
11 subdir_done()
12endif
13
14libgnunetsq = library('gnunetsq',
15 libgnunetsq_src,
16 soversion: '0',
17 version: '0.0.0',
18 dependencies: [libgnunetutil_dep, sqlite_dep],
19 include_directories: [incdir, configuration_inc],
20 install: true,
21 install_dir: get_option('libdir'))
22pkg.generate(libgnunetsq, url: 'https://www.gnunet.org',
23 description : 'Provides API for accessing the SQ service')
24libgnunetsq_dep = declare_dependency(link_with : libgnunetsq)
25
diff --git a/src/lib/sq/sq.c b/src/lib/sq/sq.c
new file mode 100644
index 000000000..96d1d333d
--- /dev/null
+++ b/src/lib/sq/sq.c
@@ -0,0 +1,131 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017 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 sq/sq.c
22 * @brief helper functions for Sqlite3 DB interactions
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_sq_lib.h"
27
28
29enum GNUNET_GenericReturnValue
30GNUNET_SQ_bind (sqlite3_stmt *stmt,
31 const struct GNUNET_SQ_QueryParam *params)
32{
33 unsigned int j;
34
35 j = 1;
36 for (unsigned int i = 0; NULL != params[i].conv; i++)
37 {
38 if (GNUNET_OK !=
39 params[i].conv (params[i].conv_cls,
40 params[i].data,
41 params[i].size,
42 stmt,
43 j))
44 {
45 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
46 "sq",
47 _ ("Failure to bind %u-th SQL parameter\n"),
48 i);
49 if (SQLITE_OK !=
50 sqlite3_reset (stmt))
51 {
52 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
53 "sq",
54 _ ("Failure in sqlite3_reset (!)\n"));
55 return GNUNET_SYSERR;
56 }
57 }
58 GNUNET_assert (0 != params[i].num_params);
59 j += params[i].num_params;
60 }
61 return GNUNET_OK;
62}
63
64
65/**
66 * Extract results from a query result according to the given specification.
67 *
68 * @param result result to process
69 * @param[in,out] rs result specification to extract for
70 * @return
71 * #GNUNET_OK if all results could be extracted
72 * #GNUNET_SYSERR if a result was invalid (non-existing field)
73 */
74enum GNUNET_GenericReturnValue
75GNUNET_SQ_extract_result (sqlite3_stmt *result,
76 struct GNUNET_SQ_ResultSpec *rs)
77{
78 unsigned int j = 0;
79
80 for (unsigned int i = 0; NULL != rs[i].conv; i++)
81 {
82 if (NULL == rs[i].result_size)
83 rs[i].result_size = &rs[i].dst_size;
84 if (GNUNET_OK !=
85 rs[i].conv (rs[i].cls,
86 result,
87 j,
88 rs[i].result_size,
89 rs[i].dst))
90 {
91 for (unsigned int k = 0; k < i; k++)
92 if (NULL != rs[k].cleaner)
93 rs[k].cleaner (rs[k].cls);
94 return GNUNET_SYSERR;
95 }
96 GNUNET_assert (0 != rs[i].num_params);
97 j += rs[i].num_params;
98 }
99 return GNUNET_OK;
100}
101
102
103void
104GNUNET_SQ_cleanup_result (struct GNUNET_SQ_ResultSpec *rs)
105{
106 for (unsigned int i = 0; NULL != rs[i].conv; i++)
107 if (NULL != rs[i].cleaner)
108 rs[i].cleaner (rs[i].cls);
109}
110
111
112/**
113 * Reset @a stmt and log error.
114 *
115 * @param dbh database handle
116 * @param stmt statement to reset
117 */
118void
119GNUNET_SQ_reset (sqlite3 *dbh,
120 sqlite3_stmt *stmt)
121{
122 if (SQLITE_OK !=
123 sqlite3_reset (stmt))
124 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
125 "sqlite",
126 _ ("Failed to reset sqlite statement with error: %s\n"),
127 sqlite3_errmsg (dbh));
128}
129
130
131/* end of sq.c */
diff --git a/src/lib/sq/sq_exec.c b/src/lib/sq/sq_exec.c
new file mode 100644
index 000000000..d4690ee99
--- /dev/null
+++ b/src/lib/sq/sq_exec.c
@@ -0,0 +1,113 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2018 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file sq/sq_exec.c
22 * @brief helper functions for executing SQL statements
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_sq_lib.h"
27
28
29/**
30 * Create a `struct GNUNET_SQ_ExecuteStatement` where errors are fatal.
31 *
32 * @param sql actual SQL statement
33 * @return initialized struct
34 */
35struct GNUNET_SQ_ExecuteStatement
36GNUNET_SQ_make_execute (const char *sql)
37{
38 struct GNUNET_SQ_ExecuteStatement es = {
39 .sql = sql,
40 .ignore_errors = GNUNET_NO
41 };
42
43 return es;
44}
45
46
47/**
48 * Create a `struct GNUNET_SQ_ExecuteStatement` where errors should
49 * be tolerated.
50 *
51 * @param sql actual SQL statement
52 * @return initialized struct
53 */
54struct GNUNET_SQ_ExecuteStatement
55GNUNET_SQ_make_try_execute (const char *sql)
56{
57 struct GNUNET_SQ_ExecuteStatement es = {
58 .sql = sql,
59 .ignore_errors = GNUNET_YES
60 };
61
62 return es;
63}
64
65
66/**
67 * Request execution of an array of statements @a es from Postgres.
68 *
69 * @param dbh database to execute the statements over
70 * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated array of prepared
71 * statements.
72 * @return #GNUNET_OK on success (modulo statements where errors can be ignored)
73 * #GNUNET_SYSERR on error
74 */
75enum GNUNET_GenericReturnValue
76GNUNET_SQ_exec_statements (sqlite3 *dbh,
77 const struct GNUNET_SQ_ExecuteStatement *es)
78{
79 for (unsigned int i = 0; NULL != es[i].sql; i++)
80 {
81 char *emsg = NULL;
82
83 if (SQLITE_OK !=
84 sqlite3_exec (dbh,
85 es[i].sql,
86 NULL,
87 NULL,
88 &emsg))
89 {
90 if (es[i].ignore_errors)
91 {
92 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
93 "Failed to run SQL `%s': %s\n",
94 es[i].sql,
95 emsg);
96 }
97 else
98 {
99 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
100 "Failed to run SQL `%s': %s\n",
101 es[i].sql,
102 emsg);
103 sqlite3_free (emsg);
104 return GNUNET_SYSERR;
105 }
106 sqlite3_free (emsg);
107 }
108 }
109 return GNUNET_OK;
110}
111
112
113/* end of sq_exec */
diff --git a/src/lib/sq/sq_prepare.c b/src/lib/sq/sq_prepare.c
new file mode 100644
index 000000000..d1ca0e8bd
--- /dev/null
+++ b/src/lib/sq/sq_prepare.c
@@ -0,0 +1,77 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2018 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file sq/sq_prepare.c
22 * @brief helper functions for executing SQL statements
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_sq_lib.h"
27
28
29/**
30 * Create a `struct GNUNET_SQ_PrepareStatement`
31 *
32 * @param sql actual SQL statement
33 * @param pstmt where to store the handle
34 * @return initialized struct
35 */
36struct GNUNET_SQ_PrepareStatement
37GNUNET_SQ_make_prepare (const char *sql,
38 sqlite3_stmt **pstmt)
39{
40 struct GNUNET_SQ_PrepareStatement ps = {
41 .sql = sql,
42 .pstmt = pstmt
43 };
44
45 return ps;
46}
47
48
49enum GNUNET_GenericReturnValue
50GNUNET_SQ_prepare (sqlite3 *dbh,
51 const struct GNUNET_SQ_PrepareStatement *ps)
52{
53 for (unsigned int i = 0; NULL != ps[i].sql; i++)
54 {
55 const char *epos = NULL;
56 int ret;
57
58 if (SQLITE_OK !=
59 (ret = sqlite3_prepare_v2 (dbh,
60 ps[i].sql,
61 strlen (ps[i].sql),
62 ps[i].pstmt,
63 &epos)))
64 {
65 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
66 "Failed to prepare SQL `%s': error %d at %s\n",
67 ps[i].sql,
68 ret,
69 epos);
70 return GNUNET_SYSERR;
71 }
72 }
73 return GNUNET_OK;
74}
75
76
77/* end of sq_prepare.c */
diff --git a/src/lib/sq/sq_query_helper.c b/src/lib/sq/sq_query_helper.c
new file mode 100644
index 000000000..ead1b5bdd
--- /dev/null
+++ b/src/lib/sq/sq_query_helper.c
@@ -0,0 +1,510 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017 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 sq/sq_query_helper.c
22 * @brief helper functions for queries
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_sq_lib.h"
27
28
29/**
30 * Function called to convert input argument into SQL parameters.
31 *
32 * @param cls closure
33 * @param data pointer to input argument
34 * @param data_len number of bytes in @a data (if applicable)
35 * @param stmt sqlite statement to bind parameters for
36 * @param off offset of the argument to bind in @a stmt, numbered from 1,
37 * so immediately suitable for passing to `sqlite3_bind`-functions.
38 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
39 */
40static int
41bind_fixed_blob (void *cls,
42 const void *data,
43 size_t data_len,
44 sqlite3_stmt *stmt,
45 unsigned int off)
46{
47 if (SQLITE_OK !=
48 sqlite3_bind_blob64 (stmt,
49 (int) off,
50 data,
51 (sqlite3_uint64) data_len,
52 SQLITE_TRANSIENT))
53 return GNUNET_SYSERR;
54 return GNUNET_OK;
55}
56
57
58/**
59 * Generate query parameter for a buffer @a ptr of
60 * @a ptr_size bytes.
61 *
62 * @param ptr pointer to the query parameter to pass
63 * @param ptr_size number of bytes in @a ptr
64 */
65struct GNUNET_SQ_QueryParam
66GNUNET_SQ_query_param_fixed_size (const void *ptr,
67 size_t ptr_size)
68{
69 struct GNUNET_SQ_QueryParam qp = {
70 .conv = &bind_fixed_blob,
71 .data = ptr,
72 .size = ptr_size,
73 .num_params = 1
74 };
75
76 return qp;
77}
78
79
80/**
81 * Function called to convert input argument into SQL parameters.
82 *
83 * @param cls closure
84 * @param data pointer to input argument
85 * @param data_len number of bytes in @a data (if applicable)
86 * @param stmt sqlite statement to bind parameters for
87 * @param off offset of the argument to bind in @a stmt, numbered from 1,
88 * so immediately suitable for passing to `sqlite3_bind`-functions.
89 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
90 */
91static int
92bind_string (void *cls,
93 const void *data,
94 size_t data_len,
95 sqlite3_stmt *stmt,
96 unsigned int off)
97{
98 if (NULL == data)
99 {
100 if (SQLITE_OK !=
101 sqlite3_bind_null (stmt,
102 (int) off))
103 return GNUNET_SYSERR;
104 return GNUNET_OK;
105 }
106 if (SQLITE_OK !=
107 sqlite3_bind_text (stmt,
108 (int) off,
109 (const char *) data,
110 -1,
111 SQLITE_TRANSIENT))
112 return GNUNET_SYSERR;
113 return GNUNET_OK;
114}
115
116
117/**
118 * Generate query parameter for a string.
119 *
120 * @param ptr pointer to the string query parameter to pass
121 */
122struct GNUNET_SQ_QueryParam
123GNUNET_SQ_query_param_string (const char *ptr)
124{
125 struct GNUNET_SQ_QueryParam qp = {
126 .conv = &bind_string,
127 .data = ptr,
128 .num_params = 1
129 };
130
131 return qp;
132}
133
134
135/**
136 * Function called to convert input argument into SQL parameters.
137 *
138 * @param cls closure
139 * @param data pointer to input argument
140 * @param data_len number of bytes in @a data (if applicable)
141 * @param stmt sqlite statement to bind parameters for
142 * @param off offset of the argument to bind in @a stmt, numbered from 1,
143 * so immediately suitable for passing to `sqlite3_bind`-functions.
144 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
145 */
146static int
147bind_rsa_pub (void *cls,
148 const void *data,
149 size_t data_len,
150 sqlite3_stmt *stmt,
151 unsigned int off)
152{
153 const struct GNUNET_CRYPTO_RsaPublicKey *rsa = data;
154 void *buf;
155 size_t buf_size;
156
157 GNUNET_break (NULL == cls);
158 buf_size = GNUNET_CRYPTO_rsa_public_key_encode (rsa,
159 &buf);
160 if (SQLITE_OK !=
161 sqlite3_bind_blob64 (stmt,
162 (int) off,
163 buf,
164 (sqlite3_uint64) buf_size,
165 SQLITE_TRANSIENT))
166 {
167 GNUNET_free (buf);
168 return GNUNET_SYSERR;
169 }
170 GNUNET_free (buf);
171 return GNUNET_OK;
172}
173
174
175/**
176 * Generate query parameter for an RSA public key. The
177 * database must contain a BLOB type in the respective position.
178 *
179 * @param x the query parameter to pass.
180 */
181struct GNUNET_SQ_QueryParam
182GNUNET_SQ_query_param_rsa_public_key (const struct
183 GNUNET_CRYPTO_RsaPublicKey *x)
184{
185 struct GNUNET_SQ_QueryParam qp = {
186 .conv = &bind_rsa_pub,
187 .data = x,
188 .num_params = 1
189 };
190
191 return qp;
192}
193
194
195/**
196 * Function called to convert input argument into SQL parameters.
197 *
198 * @param cls closure
199 * @param data pointer to input argument
200 * @param data_len number of bytes in @a data (if applicable)
201 * @param stmt sqlite statement to bind parameters for
202 * @param off offset of the argument to bind in @a stmt, numbered from 1,
203 * so immediately suitable for passing to `sqlite3_bind`-functions.
204 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
205 */
206static int
207bind_rsa_sig (void *cls,
208 const void *data,
209 size_t data_len,
210 sqlite3_stmt *stmt,
211 unsigned int off)
212{
213 const struct GNUNET_CRYPTO_RsaSignature *sig = data;
214 void *buf;
215 size_t buf_size;
216
217 GNUNET_break (NULL == cls);
218 buf_size = GNUNET_CRYPTO_rsa_signature_encode (sig,
219 &buf);
220 if (SQLITE_OK !=
221 sqlite3_bind_blob64 (stmt,
222 (int) off,
223 buf,
224 (sqlite3_uint64) buf_size,
225 SQLITE_TRANSIENT))
226 {
227 GNUNET_free (buf);
228 return GNUNET_SYSERR;
229 }
230 GNUNET_free (buf);
231 return GNUNET_OK;
232}
233
234
235/**
236 * Generate query parameter for an RSA signature. The
237 * database must contain a BLOB type in the respective position.
238 *
239 * @param x the query parameter to pass
240 */
241struct GNUNET_SQ_QueryParam
242GNUNET_SQ_query_param_rsa_signature (const struct GNUNET_CRYPTO_RsaSignature *x)
243{
244 struct GNUNET_SQ_QueryParam qp = {
245 .conv = &bind_rsa_sig,
246 .data = x,
247 .num_params = 1
248 };
249
250 return qp;
251}
252
253
254/**
255 * Function called to convert input argument into SQL parameters.
256 *
257 * @param cls closure
258 * @param data pointer to input argument
259 * @param data_len number of bytes in @a data (if applicable)
260 * @param stmt sqlite statement to bind parameters for
261 * @param off offset of the argument to bind in @a stmt, numbered from 1,
262 * so immediately suitable for passing to `sqlite3_bind`-functions.
263 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
264 */
265static int
266bind_abstime (void *cls,
267 const void *data,
268 size_t data_len,
269 sqlite3_stmt *stmt,
270 unsigned int off)
271{
272 const struct GNUNET_TIME_Absolute *u = data;
273 struct GNUNET_TIME_Absolute abs;
274
275 abs = *u;
276 if (abs.abs_value_us > INT64_MAX)
277 abs.abs_value_us = INT64_MAX;
278 GNUNET_assert (sizeof(uint64_t) == data_len);
279 if (SQLITE_OK !=
280 sqlite3_bind_int64 (stmt,
281 (int) off,
282 (sqlite3_int64) abs.abs_value_us))
283 return GNUNET_SYSERR;
284 return GNUNET_OK;
285}
286
287
288/**
289 * Generate query parameter for an absolute time value.
290 * The database must store a 64-bit integer.
291 *
292 * @param x pointer to the query parameter to pass
293 */
294struct GNUNET_SQ_QueryParam
295GNUNET_SQ_query_param_absolute_time (const struct GNUNET_TIME_Absolute *x)
296{
297 struct GNUNET_SQ_QueryParam qp = {
298 .conv = &bind_abstime,
299 .data = x,
300 .size = sizeof(struct GNUNET_TIME_Absolute),
301 .num_params = 1
302 };
303
304 return qp;
305}
306
307
308/**
309 * Function called to convert input argument into SQL parameters.
310 *
311 * @param cls closure
312 * @param data pointer to input argument
313 * @param data_len number of bytes in @a data (if applicable)
314 * @param stmt sqlite statement to bind parameters for
315 * @param off offset of the argument to bind in @a stmt, numbered from 1,
316 * so immediately suitable for passing to `sqlite3_bind`-functions.
317 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
318 */
319static int
320bind_nbotime (void *cls,
321 const void *data,
322 size_t data_len,
323 sqlite3_stmt *stmt,
324 unsigned int off)
325{
326 const struct GNUNET_TIME_AbsoluteNBO *u = data;
327 struct GNUNET_TIME_Absolute abs;
328
329 abs = GNUNET_TIME_absolute_ntoh (*u);
330 if (abs.abs_value_us > INT64_MAX)
331 abs.abs_value_us = INT64_MAX;
332 GNUNET_assert (sizeof(uint64_t) == data_len);
333 if (SQLITE_OK !=
334 sqlite3_bind_int64 (stmt,
335 (int) off,
336 (sqlite3_int64) abs.abs_value_us))
337 return GNUNET_SYSERR;
338 return GNUNET_OK;
339}
340
341
342/**
343 * Generate query parameter for an absolute time value.
344 * The database must store a 64-bit integer.
345 *
346 * @param x pointer to the query parameter to pass
347 */
348struct GNUNET_SQ_QueryParam
349GNUNET_SQ_query_param_absolute_time_nbo (const struct
350 GNUNET_TIME_AbsoluteNBO *x)
351{
352 struct GNUNET_SQ_QueryParam qp = {
353 .conv = &bind_nbotime,
354 .data = x,
355 .size = sizeof(struct GNUNET_TIME_AbsoluteNBO),
356 .num_params = 1
357 };
358
359 return qp;
360}
361
362
363/**
364 * Function called to convert input argument into SQL parameters.
365 *
366 * @param cls closure
367 * @param data pointer to input argument
368 * @param data_len number of bytes in @a data (if applicable)
369 * @param stmt sqlite statement to bind parameters for
370 * @param off offset of the argument to bind in @a stmt, numbered from 1,
371 * so immediately suitable for passing to `sqlite3_bind`-functions.
372 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
373 */
374static int
375bind_u16 (void *cls,
376 const void *data,
377 size_t data_len,
378 sqlite3_stmt *stmt,
379 unsigned int off)
380{
381 const uint16_t *u = data;
382
383 GNUNET_assert (sizeof(uint16_t) == data_len);
384 if (SQLITE_OK !=
385 sqlite3_bind_int (stmt,
386 (int) off,
387 (int) *u))
388 return GNUNET_SYSERR;
389 return GNUNET_OK;
390}
391
392
393/**
394 * Generate query parameter for an uint16_t in host byte order.
395 *
396 * @param x pointer to the query parameter to pass
397 */
398struct GNUNET_SQ_QueryParam
399GNUNET_SQ_query_param_uint16 (const uint16_t *x)
400{
401 struct GNUNET_SQ_QueryParam qp = {
402 .conv = &bind_u16,
403 .data = x,
404 .size = sizeof(uint16_t),
405 .num_params = 1
406 };
407
408 return qp;
409}
410
411
412/**
413 * Function called to convert input argument into SQL parameters.
414 *
415 * @param cls closure
416 * @param data pointer to input argument
417 * @param data_len number of bytes in @a data (if applicable)
418 * @param stmt sqlite statement to bind parameters for
419 * @param off offset of the argument to bind in @a stmt, numbered from 1,
420 * so immediately suitable for passing to `sqlite3_bind`-functions.
421 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
422 */
423static int
424bind_u32 (void *cls,
425 const void *data,
426 size_t data_len,
427 sqlite3_stmt *stmt,
428 unsigned int off)
429{
430 const uint32_t *u = data;
431
432 GNUNET_assert (sizeof(uint32_t) == data_len);
433 if (SQLITE_OK !=
434 sqlite3_bind_int64 (stmt,
435 (int) off,
436 (sqlite3_int64) * u))
437 return GNUNET_SYSERR;
438 return GNUNET_OK;
439}
440
441
442/**
443 * Generate query parameter for an uint32_t in host byte order.
444 *
445 * @param x pointer to the query parameter to pass
446 */
447struct GNUNET_SQ_QueryParam
448GNUNET_SQ_query_param_uint32 (const uint32_t *x)
449{
450 struct GNUNET_SQ_QueryParam qp = {
451 .conv = &bind_u32,
452 .data = x,
453 .size = sizeof(uint32_t),
454 .num_params = 1
455 };
456
457 return qp;
458}
459
460
461/**
462 * Function called to convert input argument into SQL parameters.
463 *
464 * @param cls closure
465 * @param data pointer to input argument
466 * @param data_len number of bytes in @a data (if applicable)
467 * @param stmt sqlite statement to bind parameters for
468 * @param off offset of the argument to bind in @a stmt, numbered from 1,
469 * so immediately suitable for passing to `sqlite3_bind`-functions.
470 * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
471 */
472static int
473bind_u64 (void *cls,
474 const void *data,
475 size_t data_len,
476 sqlite3_stmt *stmt,
477 unsigned int off)
478{
479 const uint64_t *u = data;
480
481 GNUNET_assert (sizeof(uint64_t) == data_len);
482 if (SQLITE_OK !=
483 sqlite3_bind_int64 (stmt,
484 (int) off,
485 (sqlite3_int64) * u))
486 return GNUNET_SYSERR;
487 return GNUNET_OK;
488}
489
490
491/**
492 * Generate query parameter for an uint16_t in host byte order.
493 *
494 * @param x pointer to the query parameter to pass
495 */
496struct GNUNET_SQ_QueryParam
497GNUNET_SQ_query_param_uint64 (const uint64_t *x)
498{
499 struct GNUNET_SQ_QueryParam qp = {
500 .conv = &bind_u64,
501 .data = x,
502 .size = sizeof(uint64_t),
503 .num_params = 1
504 };
505
506 return qp;
507}
508
509
510/* end of sq_query_helper.c */
diff --git a/src/lib/sq/sq_result_helper.c b/src/lib/sq/sq_result_helper.c
new file mode 100644
index 000000000..5ea3f1e56
--- /dev/null
+++ b/src/lib/sq/sq_result_helper.c
@@ -0,0 +1,785 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017 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 sq/sq_result_helper.c
22 * @brief helper functions for queries
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_sq_lib.h"
27
28
29/**
30 * Extract variable-sized binary data from a Postgres database @a result at row @a row.
31 *
32 * @param cls closure
33 * @param result where to extract data from
34 * @param column column to extract data from
35 * @param[in,out] dst_size where to store size of result, may be NULL
36 * @param[out] dst where to store the result (actually a `void **`)
37 * @return
38 * #GNUNET_YES if all results could be extracted
39 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
40 */
41static int
42extract_var_blob (void *cls,
43 sqlite3_stmt *result,
44 unsigned int column,
45 size_t *dst_size,
46 void *dst)
47{
48 int have;
49 const void *ret;
50 void **rdst = (void **) dst;
51
52 if (SQLITE_NULL ==
53 sqlite3_column_type (result,
54 column))
55 {
56 *rdst = NULL;
57 *dst_size = 0;
58 return GNUNET_YES;
59 }
60
61 if (SQLITE_BLOB !=
62 sqlite3_column_type (result,
63 column))
64 {
65 GNUNET_break (0);
66 return GNUNET_SYSERR;
67 }
68 /* sqlite manual says to invoke 'sqlite3_column_blob()'
69 before calling sqlite3_column_bytes() */
70 ret = sqlite3_column_blob (result,
71 column);
72 have = sqlite3_column_bytes (result,
73 column);
74 if (have < 0)
75 {
76 GNUNET_break (0);
77 return GNUNET_SYSERR;
78 }
79 *dst_size = have;
80 if (0 == have)
81 {
82 *rdst = NULL;
83 return GNUNET_OK;
84 }
85 *rdst = GNUNET_malloc (have);
86 GNUNET_memcpy (*rdst,
87 ret,
88 have);
89 return GNUNET_OK;
90}
91
92
93/**
94 * Cleanup memory allocated by #extract_var_blob().
95 *
96 * @param cls pointer to pointer of allocation
97 */
98static void
99clean_var_blob (void *cls)
100{
101 void **dptr = (void **) cls;
102
103 if (NULL != *dptr)
104 {
105 GNUNET_free (*dptr);
106 *dptr = NULL;
107 }
108}
109
110
111/**
112 * Variable-size result expected.
113 *
114 * @param[out] dst where to store the result, allocated
115 * @param[out] sptr where to store the size of @a dst
116 * @return array entry for the result specification to use
117 */
118struct GNUNET_SQ_ResultSpec
119GNUNET_SQ_result_spec_variable_size (void **dst,
120 size_t *sptr)
121{
122 struct GNUNET_SQ_ResultSpec rs = {
123 .conv = &extract_var_blob,
124 .cleaner = &clean_var_blob,
125 .dst = dst,
126 .cls = dst,
127 .result_size = sptr,
128 .num_params = 1
129 };
130
131 return rs;
132}
133
134
135/**
136 * Extract fixed-sized binary data from a Postgres database @a result at row @a row.
137 *
138 * @param cls closure
139 * @param result where to extract data from
140 * @param column column to extract data from
141 * @param[in,out] dst_size where to store size of result, may be NULL
142 * @param[out] dst where to store the result
143 * @return
144 * #GNUNET_YES if all results could be extracted
145 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
146 */
147static int
148extract_fixed_blob (void *cls,
149 sqlite3_stmt *result,
150 unsigned int column,
151 size_t *dst_size,
152 void *dst)
153{
154 int have;
155 const void *ret;
156
157 if ((0 == *dst_size) &&
158 (SQLITE_NULL ==
159 sqlite3_column_type (result,
160 column)))
161 {
162 return GNUNET_YES;
163 }
164
165 if (SQLITE_BLOB !=
166 sqlite3_column_type (result,
167 column))
168 {
169 GNUNET_break (0);
170 return GNUNET_SYSERR;
171 }
172 /* sqlite manual says to invoke 'sqlite3_column_blob()'
173 before calling sqlite3_column_bytes() */
174 ret = sqlite3_column_blob (result,
175 column);
176 have = sqlite3_column_bytes (result,
177 column);
178 if (*dst_size != have)
179 {
180 GNUNET_break (0);
181 return GNUNET_SYSERR;
182 }
183 GNUNET_memcpy (dst,
184 ret,
185 have);
186 return GNUNET_OK;
187}
188
189
190/**
191 * Fixed-size result expected.
192 *
193 * @param[out] dst where to store the result
194 * @param dst_size number of bytes in @a dst
195 * @return array entry for the result specification to use
196 */
197struct GNUNET_SQ_ResultSpec
198GNUNET_SQ_result_spec_fixed_size (void *dst,
199 size_t dst_size)
200{
201 struct GNUNET_SQ_ResultSpec rs = {
202 .conv = &extract_fixed_blob,
203 .dst = dst,
204 .dst_size = dst_size,
205 .num_params = 1
206 };
207
208 return rs;
209}
210
211
212/**
213 * Extract fixed-sized binary data from a Postgres database @a result at row @a row.
214 *
215 * @param cls closure
216 * @param result where to extract data from
217 * @param column column to extract data from
218 * @param[in,out] dst_size where to store size of result, may be NULL
219 * @param[out] dst where to store the result
220 * @return
221 * #GNUNET_YES if all results could be extracted
222 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
223 */
224static int
225extract_utf8_string (void *cls,
226 sqlite3_stmt *result,
227 unsigned int column,
228 size_t *dst_size,
229 void *dst)
230{
231 const char *text;
232 char **rdst = dst;
233
234 if (SQLITE_NULL ==
235 sqlite3_column_type (result,
236 column))
237 {
238 *rdst = NULL;
239 return GNUNET_OK;
240 }
241 if (SQLITE_TEXT !=
242 sqlite3_column_type (result,
243 column))
244 {
245 GNUNET_break (0);
246 return GNUNET_SYSERR;
247 }
248 /* sqlite manual guarantees that 'sqlite3_column_text()'
249 is 0-terminated */
250 text = (const char *) sqlite3_column_text (result,
251 column);
252 if (NULL == text)
253 {
254 GNUNET_break (0);
255 return GNUNET_SYSERR;
256 }
257 *dst_size = strlen (text) + 1;
258 *rdst = GNUNET_strdup (text);
259 return GNUNET_OK;
260}
261
262
263/**
264 * Cleanup memory allocated by #extract_var_blob().
265 *
266 * @param cls pointer to pointer of allocation
267 */
268static void
269clean_utf8_string (void *cls)
270{
271 char **dptr = (char **) cls;
272
273 if (NULL != *dptr)
274 {
275 GNUNET_free (*dptr);
276 *dptr = NULL;
277 }
278}
279
280
281/**
282 * 0-terminated string expected.
283 *
284 * @param[out] dst where to store the result, allocated
285 * @return array entry for the result specification to use
286 */
287struct GNUNET_SQ_ResultSpec
288GNUNET_SQ_result_spec_string (char **dst)
289{
290 struct GNUNET_SQ_ResultSpec rs = {
291 .conv = &extract_utf8_string,
292 .cleaner = &clean_utf8_string,
293 .cls = dst,
294 .dst = dst,
295 .num_params = 1
296 };
297
298 return rs;
299}
300
301
302/**
303 * Extract data from a Postgres database @a result at row @a row.
304 *
305 * @param cls closure
306 * @param result where to extract data from
307 * @param column column to extract data from
308 * @param[in,out] dst_size where to store size of result, may be NULL
309 * @param[out] dst where to store the result
310 * @return
311 * #GNUNET_YES if all results could be extracted
312 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
313 */
314static int
315extract_rsa_pub (void *cls,
316 sqlite3_stmt *result,
317 unsigned int column,
318 size_t *dst_size,
319 void *dst)
320{
321 struct GNUNET_CRYPTO_RsaPublicKey **pk = dst;
322 int have;
323 const void *ret;
324
325 if (SQLITE_BLOB !=
326 sqlite3_column_type (result,
327 column))
328 {
329 GNUNET_break (0);
330 return GNUNET_SYSERR;
331 }
332 /* sqlite manual says to invoke 'sqlite3_column_blob()'
333 before calling sqlite3_column_bytes() */
334 ret = sqlite3_column_blob (result,
335 column);
336 have = sqlite3_column_bytes (result,
337 column);
338 if (have < 0)
339 {
340 GNUNET_break (0);
341 return GNUNET_SYSERR;
342 }
343
344 *pk = GNUNET_CRYPTO_rsa_public_key_decode (ret,
345 have);
346 if (NULL == *pk)
347 {
348 GNUNET_break (0);
349 return GNUNET_SYSERR;
350 }
351 return GNUNET_OK;
352}
353
354
355/**
356 * Function called to clean up memory allocated
357 * by a #GNUNET_PQ_ResultConverter.
358 *
359 * @param cls closure
360 */
361static void
362clean_rsa_pub (void *cls)
363{
364 struct GNUNET_CRYPTO_RsaPublicKey **pk = cls;
365
366 if (NULL != *pk)
367 {
368 GNUNET_CRYPTO_rsa_public_key_free (*pk);
369 *pk = NULL;
370 }
371}
372
373
374/**
375 * RSA public key expected.
376 *
377 * @param[out] rsa where to store the result
378 * @return array entry for the result specification to use
379 */
380struct GNUNET_SQ_ResultSpec
381GNUNET_SQ_result_spec_rsa_public_key (struct GNUNET_CRYPTO_RsaPublicKey **rsa)
382{
383 struct GNUNET_SQ_ResultSpec rs = {
384 .conv = &extract_rsa_pub,
385 .cleaner = &clean_rsa_pub,
386 .dst = rsa,
387 .cls = rsa,
388 .num_params = 1
389 };
390
391 return rs;
392}
393
394
395/**
396 * Extract data from a Postgres database @a result at row @a row.
397 *
398 * @param cls closure
399 * @param result where to extract data from
400 * @param column column to extract data from
401 * @param[in,out] dst_size where to store size of result, may be NULL
402 * @param[out] dst where to store the result
403 * @return
404 * #GNUNET_YES if all results could be extracted
405 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
406 */
407static int
408extract_rsa_sig (void *cls,
409 sqlite3_stmt *result,
410 unsigned int column,
411 size_t *dst_size,
412 void *dst)
413{
414 struct GNUNET_CRYPTO_RsaSignature **sig = dst;
415 int have;
416 const void *ret;
417
418 if (SQLITE_BLOB !=
419 sqlite3_column_type (result,
420 column))
421 {
422 GNUNET_break (0);
423 return GNUNET_SYSERR;
424 }
425 /* sqlite manual says to invoke 'sqlite3_column_blob()'
426 before calling sqlite3_column_bytes() */
427 ret = sqlite3_column_blob (result,
428 column);
429 have = sqlite3_column_bytes (result,
430 column);
431 if (have < 0)
432 {
433 GNUNET_break (0);
434 return GNUNET_SYSERR;
435 }
436
437 *sig = GNUNET_CRYPTO_rsa_signature_decode (ret,
438 have);
439 if (NULL == *sig)
440 {
441 GNUNET_break (0);
442 return GNUNET_SYSERR;
443 }
444 return GNUNET_OK;
445}
446
447
448/**
449 * Function called to clean up memory allocated
450 * by a #GNUNET_PQ_ResultConverter.
451 *
452 * @param cls result data to clean up
453 */
454static void
455clean_rsa_sig (void *cls)
456{
457 struct GNUNET_CRYPTO_RsaSignature **sig = cls;
458
459 if (NULL != *sig)
460 {
461 GNUNET_CRYPTO_rsa_signature_free (*sig);
462 *sig = NULL;
463 }
464}
465
466
467/**
468 * RSA signature expected.
469 *
470 * @param[out] sig where to store the result;
471 * @return array entry for the result specification to use
472 */
473struct GNUNET_SQ_ResultSpec
474GNUNET_SQ_result_spec_rsa_signature (struct GNUNET_CRYPTO_RsaSignature **sig)
475{
476 struct GNUNET_SQ_ResultSpec rs = {
477 .conv = &extract_rsa_sig,
478 .cleaner = &clean_rsa_sig,
479 .dst = sig,
480 .cls = sig,
481 .num_params = 1
482 };
483
484 return rs;
485}
486
487
488/**
489 * Extract absolute time value from a Postgres database @a result at row @a row.
490 *
491 * @param cls closure
492 * @param result where to extract data from
493 * @param column column to extract data from
494 * @param[in,out] dst_size where to store size of result, may be NULL
495 * @param[out] dst where to store the result
496 * @return
497 * #GNUNET_YES if all results could be extracted
498 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
499 */
500static int
501extract_abs_time (void *cls,
502 sqlite3_stmt *result,
503 unsigned int column,
504 size_t *dst_size,
505 void *dst)
506{
507 struct GNUNET_TIME_Absolute *u = dst;
508 struct GNUNET_TIME_Absolute t;
509
510 GNUNET_assert (sizeof(uint64_t) == *dst_size);
511 if (SQLITE_INTEGER !=
512 sqlite3_column_type (result,
513 column))
514 {
515 GNUNET_break (0);
516 return GNUNET_SYSERR;
517 }
518 t.abs_value_us = (uint64_t) sqlite3_column_int64 (result,
519 column);
520 if (INT64_MAX == t.abs_value_us)
521 t = GNUNET_TIME_UNIT_FOREVER_ABS;
522 *u = t;
523 return GNUNET_OK;
524}
525
526
527/**
528 * Absolute time expected.
529 *
530 * @param[out] at where to store the result
531 * @return array entry for the result specification to use
532 */
533struct GNUNET_SQ_ResultSpec
534GNUNET_SQ_result_spec_absolute_time (struct GNUNET_TIME_Absolute *at)
535{
536 struct GNUNET_SQ_ResultSpec rs = {
537 .conv = &extract_abs_time,
538 .dst = at,
539 .dst_size = sizeof(struct GNUNET_TIME_Absolute),
540 .num_params = 1
541 };
542
543 return rs;
544}
545
546
547/**
548 * Extract absolute time value in NBO from a Postgres database @a result at row @a row.
549 *
550 * @param cls closure
551 * @param result where to extract data from
552 * @param column column to extract data from
553 * @param[in,out] dst_size where to store size of result, may be NULL
554 * @param[out] dst where to store the result
555 * @return
556 * #GNUNET_YES if all results could be extracted
557 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
558 */
559static int
560extract_abs_time_nbo (void *cls,
561 sqlite3_stmt *result,
562 unsigned int column,
563 size_t *dst_size,
564 void *dst)
565{
566 struct GNUNET_TIME_AbsoluteNBO *u = dst;
567 struct GNUNET_TIME_Absolute t;
568
569 GNUNET_assert (sizeof(uint64_t) == *dst_size);
570 if (SQLITE_INTEGER !=
571 sqlite3_column_type (result,
572 column))
573 {
574 GNUNET_break (0);
575 return GNUNET_SYSERR;
576 }
577 t.abs_value_us = (uint64_t) sqlite3_column_int64 (result,
578 column);
579 if (INT64_MAX == t.abs_value_us)
580 t = GNUNET_TIME_UNIT_FOREVER_ABS;
581 *u = GNUNET_TIME_absolute_hton (t);
582 return GNUNET_OK;
583}
584
585
586/**
587 * Absolute time expected.
588 *
589 * @param[out] at where to store the result
590 * @return array entry for the result specification to use
591 */
592struct GNUNET_SQ_ResultSpec
593GNUNET_SQ_result_spec_absolute_time_nbo (struct GNUNET_TIME_AbsoluteNBO *at)
594{
595 struct GNUNET_SQ_ResultSpec rs = {
596 .conv = &extract_abs_time_nbo,
597 .dst = at,
598 .dst_size = sizeof(struct GNUNET_TIME_AbsoluteNBO),
599 .num_params = 1
600 };
601
602 return rs;
603}
604
605
606/**
607 * Extract 16-bit integer from a Postgres database @a result at row @a row.
608 *
609 * @param cls closure
610 * @param result where to extract data from
611 * @param column column to extract data from
612 * @param[in,out] dst_size where to store size of result, may be NULL
613 * @param[out] dst where to store the result
614 * @return
615 * #GNUNET_YES if all results could be extracted
616 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
617 */
618static int
619extract_uint16 (void *cls,
620 sqlite3_stmt *result,
621 unsigned int column,
622 size_t *dst_size,
623 void *dst)
624{
625 uint64_t v;
626 uint16_t *u = dst;
627
628 GNUNET_assert (sizeof(uint16_t) == *dst_size);
629 if (SQLITE_INTEGER !=
630 sqlite3_column_type (result,
631 column))
632 {
633 GNUNET_break (0);
634 return GNUNET_SYSERR;
635 }
636 v = (uint64_t) sqlite3_column_int64 (result,
637 column);
638 if (v > UINT16_MAX)
639 {
640 GNUNET_break (0);
641 return GNUNET_SYSERR;
642 }
643 *u = (uint16_t) v;
644 return GNUNET_OK;
645}
646
647
648/**
649 * uint16_t expected.
650 *
651 * @param[out] u16 where to store the result
652 * @return array entry for the result specification to use
653 */
654struct GNUNET_SQ_ResultSpec
655GNUNET_SQ_result_spec_uint16 (uint16_t *u16)
656{
657 struct GNUNET_SQ_ResultSpec rs = {
658 .conv = &extract_uint16,
659 .dst = u16,
660 .dst_size = sizeof(uint16_t),
661 .num_params = 1
662 };
663
664 return rs;
665}
666
667
668/**
669 * Extract 32-bit integer from a Postgres database @a result at row @a row.
670 *
671 * @param cls closure
672 * @param result where to extract data from
673 * @param column column to extract data from
674 * @param[in,out] dst_size where to store size of result, may be NULL
675 * @param[out] dst where to store the result
676 * @return
677 * #GNUNET_YES if all results could be extracted
678 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
679 */
680static int
681extract_uint32 (void *cls,
682 sqlite3_stmt *result,
683 unsigned int column,
684 size_t *dst_size,
685 void *dst)
686{
687 uint64_t v;
688 uint32_t *u = dst;
689
690 GNUNET_assert (sizeof(uint32_t) == *dst_size);
691 if (SQLITE_INTEGER !=
692 sqlite3_column_type (result,
693 column))
694 {
695 GNUNET_break (0);
696 return GNUNET_SYSERR;
697 }
698 v = (uint64_t) sqlite3_column_int64 (result,
699 column);
700 if (v > UINT32_MAX)
701 {
702 GNUNET_break (0);
703 return GNUNET_SYSERR;
704 }
705 *u = (uint32_t) v;
706 return GNUNET_OK;
707}
708
709
710/**
711 * uint32_t expected.
712 *
713 * @param[out] u32 where to store the result
714 * @return array entry for the result specification to use
715 */
716struct GNUNET_SQ_ResultSpec
717GNUNET_SQ_result_spec_uint32 (uint32_t *u32)
718{
719 struct GNUNET_SQ_ResultSpec rs = {
720 .conv = &extract_uint32,
721 .dst = u32,
722 .dst_size = sizeof(uint32_t),
723 .num_params = 1
724 };
725
726 return rs;
727}
728
729
730/**
731 * Extract 64-bit integer from a Postgres database @a result at row @a row.
732 *
733 * @param cls closure
734 * @param result where to extract data from
735 * @param column column to extract data from
736 * @param[in,out] dst_size where to store size of result, may be NULL
737 * @param[out] dst where to store the result
738 * @return
739 * #GNUNET_YES if all results could be extracted
740 * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
741 */
742static int
743extract_uint64 (void *cls,
744 sqlite3_stmt *result,
745 unsigned int column,
746 size_t *dst_size,
747 void *dst)
748{
749 uint64_t *u = dst;
750
751 GNUNET_assert (sizeof(uint64_t) == *dst_size);
752 if (SQLITE_INTEGER !=
753 sqlite3_column_type (result,
754 column))
755 {
756 GNUNET_break (0);
757 return GNUNET_SYSERR;
758 }
759 *u = (uint64_t) sqlite3_column_int64 (result,
760 column);
761 return GNUNET_OK;
762}
763
764
765/**
766 * uint64_t expected.
767 *
768 * @param[out] u64 where to store the result
769 * @return array entry for the result specification to use
770 */
771struct GNUNET_SQ_ResultSpec
772GNUNET_SQ_result_spec_uint64 (uint64_t *u64)
773{
774 struct GNUNET_SQ_ResultSpec rs = {
775 .conv = &extract_uint64,
776 .dst = u64,
777 .dst_size = sizeof(uint64_t),
778 .num_params = 1
779 };
780
781 return rs;
782}
783
784
785/* end of sq_result_helper.c */
diff --git a/src/lib/sq/test_sq.c b/src/lib/sq/test_sq.c
new file mode 100644
index 000000000..ecd9918d7
--- /dev/null
+++ b/src/lib/sq/test_sq.c
@@ -0,0 +1,290 @@
1/*
2 This file is part of GNUnet
3 (C) 2015, 2016, 2017 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 sq/test_sq.c
22 * @brief Tests for sqlite3 convenience API
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include "gnunet_util_lib.h"
27#include "gnunet_sq_lib.h"
28
29
30/**
31 * @brief Prepare a SQL statement
32 *
33 * @param dbh handle to the database
34 * @param zSql SQL statement, UTF-8 encoded
35 * @param[out] ppStmt set to the prepared statement
36 * @return 0 on success
37 */
38static int
39sq_prepare (sqlite3 *dbh,
40 const char *zSql,
41 sqlite3_stmt **ppStmt)
42{
43 char *dummy;
44 int result;
45
46 result = sqlite3_prepare_v2 (dbh,
47 zSql,
48 strlen (zSql),
49 ppStmt,
50 (const char **) &dummy);
51 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
52 "Prepared `%s' / %p: %d\n",
53 zSql,
54 *ppStmt,
55 result);
56 return result;
57}
58
59
60/**
61 * Run actual test queries.
62 *
63 * @return 0 on success
64 */
65static int
66run_queries (sqlite3 *dbh)
67{
68 struct GNUNET_CRYPTO_RsaPublicKey *pub;
69 struct GNUNET_CRYPTO_RsaPublicKey *pub2 = NULL;
70 struct GNUNET_CRYPTO_RsaSignature *sig;
71 struct GNUNET_CRYPTO_RsaSignature *sig2 = NULL;
72 struct GNUNET_TIME_Absolute abs_time = GNUNET_TIME_absolute_get ();
73 struct GNUNET_TIME_Absolute abs_time2;
74 struct GNUNET_TIME_Absolute forever = GNUNET_TIME_UNIT_FOREVER_ABS;
75 struct GNUNET_TIME_Absolute forever2;
76 struct GNUNET_HashCode hc;
77 struct GNUNET_HashCode hc2;
78 sqlite3_stmt *stmt;
79 struct GNUNET_CRYPTO_RsaPrivateKey *priv;
80 const char msg[] = "hello";
81 void *msg2;
82 struct GNUNET_HashCode hmsg;
83 size_t msg2_len;
84 uint16_t u16;
85 uint16_t u162;
86 uint32_t u32;
87 uint32_t u322;
88 uint64_t u64;
89 uint64_t u642;
90
91 priv = GNUNET_CRYPTO_rsa_private_key_create (1024);
92 pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv);
93 memset (&hmsg, 42, sizeof(hmsg));
94 sig = GNUNET_CRYPTO_rsa_sign_fdh (priv,
95 &hmsg);
96 u16 = 16;
97 u32 = 32;
98 u64 = 64;
99 /* FIXME: test GNUNET_SQ_result_spec_variable_size */
100
101 sq_prepare (dbh,
102 "INSERT INTO test_sq ("
103 " pub"
104 ",sig"
105 ",abs_time"
106 ",forever"
107 ",hash"
108 ",vsize"
109 ",u16"
110 ",u32"
111 ",u64"
112 ") VALUES "
113 "($1, $2, $3, $4, $5, $6,"
114 "$7, $8, $9);",
115 &stmt);
116 {
117 struct GNUNET_SQ_QueryParam params_insert[] = {
118 GNUNET_SQ_query_param_rsa_public_key (pub),
119 GNUNET_SQ_query_param_rsa_signature (sig),
120 GNUNET_SQ_query_param_absolute_time (&abs_time),
121 GNUNET_SQ_query_param_absolute_time (&forever),
122 GNUNET_SQ_query_param_auto_from_type (&hc),
123 GNUNET_SQ_query_param_fixed_size (msg, strlen (msg)),
124 GNUNET_SQ_query_param_uint16 (&u16),
125 GNUNET_SQ_query_param_uint32 (&u32),
126 GNUNET_SQ_query_param_uint64 (&u64),
127 GNUNET_SQ_query_param_end
128 };
129
130 GNUNET_assert (GNUNET_OK ==
131 GNUNET_SQ_bind (stmt,
132 params_insert));
133 if (SQLITE_DONE !=
134 sqlite3_step (stmt))
135 {
136 GNUNET_CRYPTO_rsa_signature_free (sig);
137 GNUNET_CRYPTO_rsa_private_key_free (priv);
138 GNUNET_CRYPTO_rsa_public_key_free (pub);
139 return 1;
140 }
141 sqlite3_reset (stmt);
142 }
143 sqlite3_finalize (stmt);
144
145 sq_prepare (dbh,
146 "SELECT"
147 " pub"
148 ",sig"
149 ",abs_time"
150 ",forever"
151 ",hash"
152 ",vsize"
153 ",u16"
154 ",u32"
155 ",u64"
156 " FROM test_sq"
157 " ORDER BY abs_time DESC "
158 " LIMIT 1;",
159 &stmt);
160 {
161 struct GNUNET_SQ_QueryParam params_select[] = {
162 GNUNET_SQ_query_param_end
163 };
164 struct GNUNET_SQ_ResultSpec results_select[] = {
165 GNUNET_SQ_result_spec_rsa_public_key (&pub2),
166 GNUNET_SQ_result_spec_rsa_signature (&sig2),
167 GNUNET_SQ_result_spec_absolute_time (&abs_time2),
168 GNUNET_SQ_result_spec_absolute_time (&forever2),
169 GNUNET_SQ_result_spec_auto_from_type (&hc2),
170 GNUNET_SQ_result_spec_variable_size (&msg2, &msg2_len),
171 GNUNET_SQ_result_spec_uint16 (&u162),
172 GNUNET_SQ_result_spec_uint32 (&u322),
173 GNUNET_SQ_result_spec_uint64 (&u642),
174 GNUNET_SQ_result_spec_end
175 };
176
177 GNUNET_assert (GNUNET_OK ==
178 GNUNET_SQ_bind (stmt,
179 params_select));
180 if (SQLITE_ROW !=
181 sqlite3_step (stmt))
182 {
183 GNUNET_break (0);
184 sqlite3_finalize (stmt);
185 GNUNET_CRYPTO_rsa_signature_free (sig);
186 GNUNET_CRYPTO_rsa_private_key_free (priv);
187 GNUNET_CRYPTO_rsa_public_key_free (pub);
188 return 1;
189 }
190 GNUNET_assert (GNUNET_OK ==
191 GNUNET_SQ_extract_result (stmt,
192 results_select));
193 GNUNET_break (abs_time.abs_value_us == abs_time2.abs_value_us);
194 GNUNET_break (forever.abs_value_us == forever2.abs_value_us);
195 GNUNET_break (0 ==
196 GNUNET_memcmp (&hc,
197 &hc2));
198 GNUNET_break (0 ==
199 GNUNET_CRYPTO_rsa_signature_cmp (sig,
200 sig2));
201 GNUNET_break (0 ==
202 GNUNET_CRYPTO_rsa_public_key_cmp (pub,
203 pub2));
204 GNUNET_break (strlen (msg) == msg2_len);
205 GNUNET_break (0 ==
206 strncmp (msg,
207 msg2,
208 msg2_len));
209 GNUNET_break (16 == u162);
210 GNUNET_break (32 == u322);
211 GNUNET_break (64 == u642);
212 GNUNET_SQ_cleanup_result (results_select);
213 sqlite3_reset (stmt);
214 }
215 sqlite3_finalize (stmt);
216
217 GNUNET_CRYPTO_rsa_signature_free (sig);
218 GNUNET_CRYPTO_rsa_private_key_free (priv);
219 GNUNET_CRYPTO_rsa_public_key_free (pub);
220 return 0;
221}
222
223
224int
225main (int argc,
226 const char *const argv[])
227{
228 sqlite3 *dbh;
229 int ret;
230
231 GNUNET_log_setup ("test-sq",
232 "WARNING",
233 NULL);
234 if (SQLITE_OK !=
235 sqlite3_open ("test.db",
236 &dbh))
237 {
238 fprintf (stderr,
239 "Cannot run test, sqlite3 initialization failed\n");
240 GNUNET_break (0);
241 return 77; /* Signal test was skipped... */
242 }
243
244 if (SQLITE_OK !=
245 sqlite3_exec (dbh,
246 "CREATE TEMPORARY TABLE IF NOT EXISTS test_sq ("
247 " pub BYTEA NOT NULL"
248 ",sig BYTEA NOT NULL"
249 ",abs_time INT8 NOT NULL"
250 ",forever INT8 NOT NULL"
251 ",hash BYTEA NOT NULL"
252 ",vsize VARCHAR NOT NULL"
253 ",u16 INT2 NOT NULL"
254 ",u32 INT4 NOT NULL"
255 ",u64 INT8 NOT NULL"
256 ")",
257 NULL, NULL, NULL))
258 {
259 fprintf (stderr,
260 "Failed to create table\n");
261 GNUNET_break (SQLITE_OK ==
262 sqlite3_close (dbh));
263 if (0 != unlink ("test.db"))
264 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
265 "unlink",
266 "test.db");
267 return 1;
268 }
269
270 ret = run_queries (dbh);
271 if (SQLITE_OK !=
272 sqlite3_exec (dbh,
273 "DROP TABLE test_sq",
274 NULL, NULL, NULL))
275 {
276 fprintf (stderr,
277 "Failed to drop table\n");
278 ret = 1;
279 }
280 GNUNET_break (SQLITE_OK ==
281 sqlite3_close (dbh));
282 if (0 != unlink ("test.db"))
283 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
284 "unlink",
285 "test.db");
286 return ret;
287}
288
289
290/* end of test_sq.c */