Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/backenddb/iterate_transfers.c
18 : * @brief Implementation of the iterate_transfers function for Postgres
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <taler/taler_pq_lib.h>
23 : #include "merchant-database/iterate_transfers.h"
24 : #include "helper.h"
25 :
26 :
27 : /**
28 : * Hard upper bound on the number of records returned by a single
29 : * call, regardless of the limit requested by the client.
30 : */
31 : #define MAX_RECORDS 50000
32 :
33 :
34 : /**
35 : * Closure for #lookup_transfers_cb().
36 : */
37 : struct LookupTransfersContext
38 : {
39 : /**
40 : * Function to call on results.
41 : */
42 : TALER_MERCHANTDB_TransferCallback cb;
43 :
44 : /**
45 : * Closure for @e cb.
46 : */
47 : void *cb_cls;
48 :
49 : /**
50 : * Postgres context.
51 : */
52 : struct TALER_MERCHANTDB_PostgresContext *pg;
53 :
54 : /**
55 : * Transaction status (set).
56 : */
57 : enum GNUNET_DB_QueryStatus qs;
58 :
59 : };
60 :
61 :
62 : /**
63 : * Function to be called with the results of a SELECT statement
64 : * that has returned @a num_results results.
65 : *
66 : * @param cls of type `struct LookupTransfersContext *`
67 : * @param result the postgres result
68 : * @param num_results the number of results in @a result
69 : */
70 : static void
71 13 : lookup_transfers_cb (void *cls,
72 : PGresult *result,
73 : unsigned int num_results)
74 : {
75 13 : struct LookupTransfersContext *ltc = cls;
76 :
77 31 : for (unsigned int i = 0; i<num_results; i++)
78 : {
79 : struct TALER_Amount credit_amount;
80 : struct TALER_WireTransferIdentifierRawP wtid;
81 : struct TALER_FullPayto payto_uri;
82 18 : struct TALER_FullPayto exchange_payto_uri = {
83 : .full_payto = NULL
84 : };
85 : char *exchange_url;
86 : uint64_t transfer_serial_id;
87 18 : uint64_t expected_transfer_serial_id = 0;
88 : struct GNUNET_TIME_Absolute execution_time;
89 : bool expected;
90 18 : struct GNUNET_PQ_ResultSpec rs[] = {
91 18 : TALER_PQ_result_spec_amount_with_currency ("credit_amount",
92 : &credit_amount),
93 18 : GNUNET_PQ_result_spec_auto_from_type ("wtid",
94 : &wtid),
95 18 : GNUNET_PQ_result_spec_string ("payto_uri",
96 : &payto_uri.full_payto),
97 18 : GNUNET_PQ_result_spec_string ("exchange_url",
98 : &exchange_url),
99 18 : GNUNET_PQ_result_spec_allow_null (
100 : GNUNET_PQ_result_spec_string ("exchange_payto_uri",
101 : &exchange_payto_uri.full_payto),
102 : NULL),
103 18 : GNUNET_PQ_result_spec_uint64 ("credit_serial",
104 : &transfer_serial_id),
105 18 : GNUNET_PQ_result_spec_allow_null (
106 : GNUNET_PQ_result_spec_uint64 ("expected_credit_serial",
107 : &expected_transfer_serial_id),
108 : NULL),
109 18 : GNUNET_PQ_result_spec_absolute_time ("execution_time",
110 : &execution_time),
111 18 : GNUNET_PQ_result_spec_bool ("expected",
112 : &expected),
113 : GNUNET_PQ_result_spec_end
114 : };
115 :
116 18 : if (GNUNET_OK !=
117 18 : GNUNET_PQ_extract_result (result,
118 : rs,
119 : i))
120 : {
121 0 : GNUNET_break (0);
122 0 : ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
123 0 : return;
124 : }
125 18 : ltc->cb (ltc->cb_cls,
126 : &credit_amount,
127 : &wtid,
128 : payto_uri,
129 : exchange_payto_uri,
130 : exchange_url,
131 : transfer_serial_id,
132 : expected_transfer_serial_id,
133 : execution_time,
134 : expected);
135 18 : GNUNET_PQ_cleanup_result (rs);
136 : }
137 13 : ltc->qs = num_results;
138 : }
139 :
140 :
141 : enum GNUNET_DB_QueryStatus
142 13 : TALER_MERCHANTDB_iterate_transfers (
143 : struct TALER_MERCHANTDB_PostgresContext *pg,
144 : const char *instance_id,
145 : struct TALER_FullPayto payto_uri,
146 : struct GNUNET_TIME_Timestamp before,
147 : struct GNUNET_TIME_Timestamp after,
148 : int64_t limit,
149 : uint64_t offset,
150 : enum TALER_EXCHANGE_YesNoAll expected,
151 : TALER_MERCHANTDB_TransferCallback cb,
152 : void *cb_cls)
153 : {
154 13 : uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
155 : TALER_MERCHANTDB_abs_limit (limit));
156 26 : bool by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
157 13 : (! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
158 13 : struct LookupTransfersContext ltc = {
159 : .cb = cb,
160 : .cb_cls = cb_cls,
161 : .pg = pg
162 : };
163 13 : struct GNUNET_PQ_QueryParam params[] = {
164 13 : GNUNET_PQ_query_param_timestamp (&before),
165 13 : GNUNET_PQ_query_param_timestamp (&after),
166 13 : GNUNET_PQ_query_param_uint64 (&offset),
167 13 : GNUNET_PQ_query_param_uint64 (&plimit),
168 13 : NULL == payto_uri.full_payto
169 8 : ? GNUNET_PQ_query_param_null () /* NULL: do not filter by payto URI */
170 13 : : GNUNET_PQ_query_param_string (payto_uri.full_payto),
171 13 : GNUNET_PQ_query_param_bool (! by_time), /* $6: filter by time? */
172 13 : GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_ALL == expected), /* filter by expected? */
173 13 : GNUNET_PQ_query_param_bool (TALER_EXCHANGE_YNA_YES == expected),
174 :
175 : GNUNET_PQ_query_param_end
176 : };
177 : enum GNUNET_DB_QueryStatus qs;
178 :
179 13 : GNUNET_assert (NULL != pg->current_merchant_id);
180 13 : GNUNET_assert (0 == strcmp (instance_id,
181 : pg->current_merchant_id));
182 13 : if (limit > 0)
183 : {
184 5 : TMH_PQ_prepare_anon (pg,
185 : "SELECT"
186 : " mt.credit_amount"
187 : ",mt.wtid"
188 : ",mac.payto_uri"
189 : ",mt.exchange_url"
190 : ",mt.credit_serial"
191 : ",mt.execution_time"
192 : ",mt.expected"
193 : /* 'expected' means the exchange's report and our
194 : bank statement agree on the amount; keep gating
195 : the serial on it, but not the account, which we
196 : know as soon as the exchange reported it. */
197 : ",CASE WHEN mt.expected"
198 : " THEN met.expected_credit_serial"
199 : " ELSE NULL END AS expected_credit_serial"
200 : ",met.exchange_payto_uri"
201 : " FROM merchant_transfers mt"
202 : " JOIN merchant_accounts mac"
203 : " USING (account_serial)"
204 : /* (wtid, exchange_url, account_serial) is UNIQUE on
205 : merchant_expected_transfers, so this matches at
206 : most one row and cannot fan out the result. */
207 : " LEFT JOIN merchant_expected_transfers met"
208 : " ON mt.wtid = met.wtid"
209 : " AND mt.account_serial = met.account_serial"
210 : " AND mt.exchange_url = met.exchange_url"
211 : " WHERE ( $6 OR "
212 : " (mt.execution_time < $1 AND"
213 : " mt.execution_time >= $2) )"
214 : " AND ( (CAST($5 AS TEXT) IS NULL) OR "
215 : " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
216 : " =REGEXP_REPLACE($5,'\\?.*','')) )"
217 : " AND ( $7 OR "
218 : " (mt.expected = $8) )"
219 : " AND (mt.credit_serial > $3)"
220 : " ORDER BY mt.credit_serial ASC"
221 : " LIMIT $4");
222 : }
223 : else
224 : {
225 8 : TMH_PQ_prepare_anon (pg,
226 : "SELECT"
227 : " mt.credit_amount"
228 : ",mt.wtid"
229 : ",mac.payto_uri"
230 : ",mt.exchange_url"
231 : ",mt.credit_serial"
232 : ",mt.execution_time"
233 : ",mt.expected"
234 : /* 'expected' means the exchange's report and our
235 : bank statement agree on the amount; keep gating
236 : the serial on it, but not the account, which we
237 : know as soon as the exchange reported it. */
238 : ",CASE WHEN mt.expected"
239 : " THEN met.expected_credit_serial"
240 : " ELSE NULL END AS expected_credit_serial"
241 : ",met.exchange_payto_uri"
242 : " FROM merchant_transfers mt"
243 : " JOIN merchant_accounts mac"
244 : " USING (account_serial)"
245 : /* (wtid, exchange_url, account_serial) is UNIQUE on
246 : merchant_expected_transfers, so this matches at
247 : most one row and cannot fan out the result. */
248 : " LEFT JOIN merchant_expected_transfers met"
249 : " ON mt.wtid = met.wtid"
250 : " AND mt.account_serial = met.account_serial"
251 : " AND mt.exchange_url = met.exchange_url"
252 : " WHERE ( $6 OR "
253 : " (mt.execution_time < $1 AND"
254 : " mt.execution_time >= $2) )"
255 : " AND ( (CAST($5 AS TEXT) IS NULL) OR "
256 : " (REGEXP_REPLACE(mac.payto_uri,'\\?.*','')"
257 : " =REGEXP_REPLACE($5,'\\?.*','')) )"
258 : " AND ( $7 OR "
259 : " (mt.expected = $8) )"
260 : " AND (mt.credit_serial < $3)"
261 : " ORDER BY mt.credit_serial DESC"
262 : " LIMIT $4");
263 : }
264 13 : qs = GNUNET_PQ_eval_prepared_multi_select (
265 : pg->conn,
266 : "",
267 : params,
268 : &lookup_transfers_cb,
269 : <c);
270 13 : if (0 >= qs)
271 1 : return qs;
272 12 : return ltc.qs;
273 : }
|