Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 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 backenddb/pg_lookup_statistics_amount_by_bucket.c
18 : * @brief Implementation of the lookup_statistics_amount_by_bucket function for Postgres
19 : * @author Martin Schanzenbach
20 : */
21 : #include "platform.h"
22 : #include <taler/taler_error_codes.h>
23 : #include <taler/taler_dbevents.h>
24 : #include <taler/taler_pq_lib.h>
25 : #include "pg_lookup_statistics_amount_by_bucket.h"
26 : #include "pg_helper.h"
27 : #include "taler_merchantdb_plugin.h"
28 :
29 :
30 : /**
31 : * Context used for TMH_PG_lookup_statistics_amount().
32 : */
33 : struct LookupAmountStatisticsContext
34 : {
35 : /**
36 : * Function to call with the results.
37 : */
38 : TALER_MERCHANTDB_AmountByBucketStatisticsCallback cb;
39 :
40 : /**
41 : * Closure for @a cb.
42 : */
43 : void *cb_cls;
44 :
45 : /**
46 : * Did database result extraction fail?
47 : */
48 : bool extract_failed;
49 :
50 : /**
51 : * Postgres context for array lookups
52 : */
53 : struct PostgresClosure *pg;
54 : };
55 :
56 :
57 : /**
58 : * Function to be called with the results of a SELECT statement
59 : * that has returned @a num_results results about token families.
60 : *
61 : * @param[in,out] cls of type `struct LookupTokenFamiliesContext *`
62 : * @param result the postgres result
63 : * @param num_results the number of results in @a result
64 : */
65 : static void
66 2 : lookup_statistics_amount_by_bucket_cb (void *cls,
67 : PGresult *result,
68 : unsigned int num_results)
69 : {
70 2 : struct LookupAmountStatisticsContext *tflc = cls;
71 2 : struct TALER_Amount *amounts = NULL;
72 2 : char *resp_range = NULL;
73 2 : char *resp_desc = NULL;
74 : uint64_t cur_bucket_start_epoch;
75 : uint64_t cur_bucket_end_epoch;
76 : uint64_t bmeta_id_current;
77 2 : unsigned int amounts_len = 0;
78 :
79 14 : for (unsigned int i = 0; i < num_results; i++)
80 : {
81 : struct TALER_Amount cumulative_amount;
82 : char *description;
83 : char *bucket_range;
84 : uint64_t bmeta_id;
85 : uint64_t bucket_start_epoch;
86 : uint64_t bucket_end_epoch;
87 12 : struct GNUNET_PQ_ResultSpec rs[] = {
88 12 : GNUNET_PQ_result_spec_uint64 ("bmeta_serial_id",
89 : &bmeta_id),
90 12 : GNUNET_PQ_result_spec_string ("description",
91 : &description),
92 12 : GNUNET_PQ_result_spec_uint64 ("bucket_start",
93 : &bucket_start_epoch),
94 12 : GNUNET_PQ_result_spec_uint64 ("bucket_end",
95 : &bucket_end_epoch),
96 12 : GNUNET_PQ_result_spec_string ("bucket_range",
97 : &bucket_range),
98 12 : TALER_PQ_result_spec_amount_with_currency ("cumulative_amount",
99 : &cumulative_amount),
100 : GNUNET_PQ_result_spec_end
101 : };
102 :
103 12 : if (GNUNET_OK !=
104 12 : GNUNET_PQ_extract_result (result,
105 : rs,
106 : i))
107 : {
108 0 : GNUNET_break (0);
109 0 : tflc->extract_failed = true;
110 0 : return;
111 : }
112 : /* Call callback if the bucket changed */
113 12 : if ( (NULL != resp_desc) &&
114 10 : ( (bmeta_id != bmeta_id_current) ||
115 10 : (bucket_start_epoch != cur_bucket_start_epoch) ||
116 2 : (0 != strcasecmp (resp_range,
117 : bucket_range)) ) )
118 : {
119 : struct GNUNET_TIME_Timestamp bucket_start;
120 : struct GNUNET_TIME_Timestamp bucket_end;
121 :
122 10 : bucket_start = GNUNET_TIME_timestamp_from_s (cur_bucket_start_epoch);
123 10 : bucket_end = GNUNET_TIME_timestamp_from_s (cur_bucket_end_epoch);
124 10 : tflc->cb (tflc->cb_cls,
125 : resp_desc,
126 : bucket_start,
127 : bucket_end,
128 : resp_range,
129 : amounts_len,
130 : amounts);
131 10 : GNUNET_free (resp_range);
132 10 : GNUNET_free (resp_desc);
133 10 : GNUNET_array_grow (amounts,
134 : amounts_len,
135 : 0);
136 : }
137 12 : if (NULL == resp_desc)
138 : {
139 12 : cur_bucket_end_epoch = bucket_end_epoch;
140 12 : cur_bucket_start_epoch = bucket_start_epoch;
141 12 : resp_range = GNUNET_strdup (bucket_range);
142 12 : resp_desc = GNUNET_strdup (description);
143 12 : bmeta_id_current = bmeta_id;
144 : }
145 12 : GNUNET_array_append (amounts,
146 : amounts_len,
147 : cumulative_amount);
148 12 : GNUNET_PQ_cleanup_result (rs);
149 : }
150 2 : if (0 != amounts_len)
151 : {
152 : struct GNUNET_TIME_Timestamp bucket_start;
153 : struct GNUNET_TIME_Timestamp bucket_end;
154 :
155 2 : bucket_start = GNUNET_TIME_timestamp_from_s (cur_bucket_start_epoch);
156 2 : bucket_end = GNUNET_TIME_timestamp_from_s (cur_bucket_end_epoch);
157 2 : tflc->cb (tflc->cb_cls,
158 : resp_desc,
159 : bucket_start,
160 : bucket_end,
161 : resp_range,
162 : amounts_len,
163 : amounts);
164 2 : GNUNET_array_grow (amounts,
165 : amounts_len,
166 : 0);
167 2 : GNUNET_free (resp_range);
168 2 : GNUNET_free (resp_desc);
169 : }
170 : }
171 :
172 :
173 : enum GNUNET_DB_QueryStatus
174 2 : TMH_PG_lookup_statistics_amount_by_bucket (
175 : void *cls,
176 : const char *instance_id,
177 : const char *slug,
178 : TALER_MERCHANTDB_AmountByBucketStatisticsCallback cb,
179 : void *cb_cls)
180 : {
181 2 : struct PostgresClosure *pg = cls;
182 2 : struct LookupAmountStatisticsContext context = {
183 : .cb = cb,
184 : .cb_cls = cb_cls,
185 : /* Can be overwritten by the lookup_statistics_amount_by_bucket_cb */
186 : .extract_failed = false,
187 : .pg = pg,
188 : };
189 2 : struct GNUNET_PQ_QueryParam params[] = {
190 2 : GNUNET_PQ_query_param_string (instance_id),
191 2 : GNUNET_PQ_query_param_string (slug),
192 : GNUNET_PQ_query_param_end
193 : };
194 : enum GNUNET_DB_QueryStatus qs;
195 :
196 2 : check_connection (pg);
197 2 : PREPARE (pg,
198 : "lookup_statistics_amount_by_bucket",
199 : "SELECT"
200 : " bmeta_serial_id"
201 : ",description"
202 : ",bucket_start"
203 : ",bucket_range::TEXT"
204 : ",merchant_statistics_bucket_end(bucket_start, bucket_range) AS bucket_end"
205 : ",(cumulative_value,cumulative_frac,curr)::taler_amount_currency AS cumulative_amount"
206 : " FROM merchant_statistic_bucket_amount"
207 : " JOIN merchant_statistic_bucket_meta"
208 : " USING (bmeta_serial_id)"
209 : " JOIN merchant_instances"
210 : " USING (merchant_serial)"
211 : " WHERE merchant_instances.merchant_id=$1"
212 : " AND merchant_statistic_bucket_meta.slug=$2"
213 : " AND merchant_statistic_bucket_meta.stype='amount'");
214 2 : qs = GNUNET_PQ_eval_prepared_multi_select (
215 : pg->conn,
216 : "lookup_statistics_amount_by_bucket",
217 : params,
218 : &lookup_statistics_amount_by_bucket_cb,
219 : &context);
220 : /* If there was an error inside the cb, return a hard error. */
221 2 : if (context.extract_failed)
222 : {
223 0 : GNUNET_break (0);
224 0 : return GNUNET_DB_STATUS_HARD_ERROR;
225 : }
226 2 : return qs;
227 : }
|