Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022, 2023, 2025 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_orders.c
18 : * @brief Implementation of the iterate_orders function for Postgres
19 : * @author Iván Ávalos
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <taler/taler_pq_lib.h>
24 : #include "merchant-database/iterate_orders.h"
25 : #include "helper.h"
26 :
27 : /**
28 : * Context used for TALER_MERCHANTDB_iterate_orders().
29 : */
30 : struct LookupOrdersContext
31 : {
32 : /**
33 : * Function to call with the results.
34 : */
35 : TALER_MERCHANTDB_OrdersCallback cb;
36 :
37 : /**
38 : * Closure for @a cb.
39 : */
40 : void *cb_cls;
41 :
42 : /**
43 : * Did database result extraction fail?
44 : */
45 : bool extract_failed;
46 : };
47 :
48 :
49 : /**
50 : * Function to be called with the results of a SELECT statement
51 : * that has returned @a num_results results about orders.
52 : *
53 : * @param[in,out] cls of type `struct LookupOrdersContext *`
54 : * @param result the postgres result
55 : * @param num_results the number of results in @a result
56 : */
57 : static void
58 164 : lookup_orders_cb (void *cls,
59 : PGresult *result,
60 : unsigned int num_results)
61 : {
62 164 : struct LookupOrdersContext *plc = cls;
63 :
64 3324 : for (unsigned int i = 0; i < num_results; i++)
65 : {
66 : char *order_id;
67 : uint64_t order_serial;
68 : struct GNUNET_TIME_Timestamp ts;
69 3160 : struct GNUNET_PQ_ResultSpec rs[] = {
70 3160 : GNUNET_PQ_result_spec_string ("order_id",
71 : &order_id),
72 3160 : GNUNET_PQ_result_spec_uint64 ("order_serial",
73 : &order_serial),
74 3160 : GNUNET_PQ_result_spec_timestamp ("creation_time",
75 : &ts),
76 : GNUNET_PQ_result_spec_end
77 : };
78 :
79 3160 : if (GNUNET_OK !=
80 3160 : GNUNET_PQ_extract_result (result,
81 : rs,
82 : i))
83 : {
84 0 : GNUNET_break (0);
85 0 : plc->extract_failed = true;
86 0 : return;
87 : }
88 3160 : plc->cb (plc->cb_cls,
89 : order_id,
90 : order_serial,
91 : ts);
92 3160 : GNUNET_PQ_cleanup_result (rs);
93 : }
94 : }
95 :
96 :
97 : enum GNUNET_DB_QueryStatus
98 164 : TALER_MERCHANTDB_iterate_orders (
99 : struct TALER_MERCHANTDB_PostgresContext *pg,
100 : const char *instance_id,
101 : const struct TALER_MERCHANTDB_OrderFilter *of,
102 : TALER_MERCHANTDB_OrdersCallback cb,
103 : void *cb_cls)
104 : {
105 164 : struct LookupOrdersContext plc = {
106 : .cb = cb,
107 : .cb_cls = cb_cls
108 : };
109 164 : uint64_t limit = (of->delta > 0) ? of->delta : -of->delta;
110 164 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
111 164 : struct GNUNET_PQ_QueryParam params[] = {
112 : /* $1 */
113 164 : GNUNET_PQ_query_param_uint64 (&limit),
114 164 : GNUNET_PQ_query_param_uint64 (&of->start_row),
115 164 : GNUNET_PQ_query_param_timestamp (&of->date),
116 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->paid)),
117 : /* $5 */
118 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->paid)),
119 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->refunded)),
120 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->refunded)),
121 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->wired)),
122 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->wired)),
123 : /* $10 */
124 164 : GNUNET_PQ_query_param_bool (NULL == of->session_id),
125 164 : NULL == of->session_id
126 164 : ? GNUNET_PQ_query_param_null ()
127 164 : : GNUNET_PQ_query_param_string (of->session_id),
128 164 : GNUNET_PQ_query_param_bool (NULL == of->fulfillment_url),
129 164 : NULL == of->fulfillment_url
130 164 : ? GNUNET_PQ_query_param_null ()
131 164 : : GNUNET_PQ_query_param_string (of->fulfillment_url),
132 164 : NULL == of->summary_filter
133 164 : ? GNUNET_PQ_query_param_null ()
134 164 : : GNUNET_PQ_query_param_string (of->summary_filter),
135 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_ALL == of->expired)),
136 164 : GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == of->expired)),
137 164 : GNUNET_PQ_query_param_timestamp (&now),
138 : GNUNET_PQ_query_param_end
139 : };
140 : enum GNUNET_DB_QueryStatus qs;
141 :
142 164 : GNUNET_assert (NULL != pg->current_merchant_id);
143 164 : GNUNET_assert (0 == strcmp (instance_id,
144 : pg->current_merchant_id));
145 164 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
146 : "Looking up orders, using filter paid: %d, refunded: %d, wired: %d, expired: %d\n",
147 : of->paid,
148 : of->refunded,
149 : of->wired,
150 : of->expired);
151 :
152 164 : if (of->delta > 0)
153 : {
154 123 : TMH_PQ_prepare_anon (pg,
155 : "(SELECT"
156 : " order_id"
157 : ",order_serial"
158 : ",creation_time"
159 : " FROM merchant_orders"
160 : " WHERE order_serial > $2"
161 : " AND"
162 : " creation_time > $3"
163 : " AND ($4 OR "
164 : " NOT CAST($5 AS BOOL))" /* unclaimed orders are never paid */
165 : " AND ($6 OR "
166 : " NOT CAST($7 AS BOOL))"/* unclaimed orders are never refunded */
167 : " AND ($8 OR "
168 : " NOT CAST($9 AS BOOL))" /* unclaimed orders are never wired */
169 : " AND"
170 : " (order_serial NOT IN"
171 : " (SELECT order_serial"
172 : " FROM merchant_contract_terms))" /* only select unclaimed orders */
173 : " AND ($10 OR "
174 : " ($11 = session_id))"
175 : " AND ($12 OR "
176 : " ($13 = fulfillment_url))"
177 : " AND ( ($14::TEXT IS NULL) OR "
178 : " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
179 : " AND ($15 OR"
180 : " (CAST($16 AS BOOL) = (pay_deadline < $17)))"
181 : " ORDER BY order_serial ASC"
182 : " LIMIT $1)"
183 : "UNION " /* union ensures elements are distinct! */
184 : "(SELECT"
185 : " order_id"
186 : ",order_serial"
187 : ",creation_time"
188 : " FROM merchant_contract_terms"
189 : " WHERE order_serial > $2"
190 : " AND"
191 : " creation_time > $3"
192 : " AND ($4 OR "
193 : " (CAST($5 AS BOOL) = paid))"
194 : " AND ($6 OR "
195 : " (CAST($7 AS BOOL) = (order_serial IN"
196 : " (SELECT order_serial "
197 : " FROM merchant_refunds))))"
198 : " AND ($8 OR"
199 : " (CAST($9 AS BOOL) = wired))"
200 : " AND ($10 OR "
201 : " ($11 = session_id))"
202 : " AND ($12 OR "
203 : " ($13 = fulfillment_url))"
204 : " AND ( ($14::TEXT IS NULL) OR "
205 : " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
206 : " AND ($15 OR"
207 : " (CAST($16 AS BOOL) = ((NOT paid) AND"
208 : " (pay_deadline < $17))))"
209 : " ORDER BY order_serial ASC"
210 : " LIMIT $1)"
211 : " ORDER BY order_serial ASC"
212 : " LIMIT $1");
213 : }
214 : else
215 : {
216 41 : TMH_PQ_prepare_anon (pg,
217 : "(SELECT"
218 : " order_id"
219 : ",order_serial"
220 : ",creation_time"
221 : " FROM merchant_orders"
222 : " WHERE order_serial < $2"
223 : " AND"
224 : " creation_time < $3"
225 : " AND ($4 OR "
226 : " NOT CAST($5 AS BOOL))" /* unclaimed orders are never paid */
227 : " AND ($6 OR "
228 : " NOT CAST($7 AS BOOL))"/* unclaimed orders are never refunded */
229 : " AND ($8 OR "
230 : " NOT CAST($9 AS BOOL))" /* unclaimed orders are never wired */
231 : " AND"
232 : " order_serial NOT IN"
233 : " (SELECT order_serial"
234 : " FROM merchant_contract_terms)" /* only select unclaimed orders */
235 : " AND ($10 OR "
236 : " ($11 = session_id))"
237 : " AND ($12 OR "
238 : " ($13 = fulfillment_url))"
239 : " AND ( ($14::TEXT IS NULL) OR "
240 : " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
241 : " AND ($15 OR"
242 : " (CAST($16 AS BOOL) = (pay_deadline < $17)))"
243 : " ORDER BY order_serial DESC"
244 : " LIMIT $1)"
245 : "UNION " /* union ensures elements are distinct! */
246 : "(SELECT"
247 : " order_id"
248 : ",order_serial"
249 : ",creation_time"
250 : " FROM merchant_contract_terms"
251 : " WHERE order_serial < $2"
252 : " AND"
253 : " creation_time < $3"
254 : " AND ($4 OR "
255 : " (CAST($5 AS BOOL) = paid))"
256 : " AND ($6 OR "
257 : " (CAST($7 AS BOOL) = (order_serial IN"
258 : " (SELECT order_serial "
259 : " FROM merchant_refunds))))"
260 : " AND ($8 OR"
261 : " (CAST($9 AS BOOL) = wired))"
262 : " AND ($10 OR "
263 : " ($11 = session_id))"
264 : " AND ($12 OR "
265 : " ($13 = fulfillment_url))"
266 : " AND ( ($14::TEXT IS NULL) OR "
267 : " (LOWER(contract_terms ->> 'summary') LIKE LOWER($14)) )"
268 : " AND ($15 OR"
269 : " (CAST($16 AS BOOL) = ((NOT paid) AND"
270 : " (pay_deadline < $17))))"
271 : " ORDER BY order_serial DESC"
272 : " LIMIT $1)"
273 : " ORDER BY order_serial DESC"
274 : " LIMIT $1");
275 : }
276 164 : qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
277 : "",
278 : params,
279 : &lookup_orders_cb,
280 : &plc);
281 164 : if (plc.extract_failed)
282 0 : return GNUNET_DB_STATUS_HARD_ERROR;
283 164 : return qs;
284 : }
|