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