pg_lookup_statistics_amount_by_bucket.c (7258B)
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 lookup_statistics_amount_by_bucket_cb (void *cls, 67 PGresult *result, 68 unsigned int num_results) 69 { 70 struct LookupAmountStatisticsContext *tflc = cls; 71 struct TALER_Amount *amounts = NULL; 72 char *resp_range = NULL; 73 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 unsigned int amounts_len = 0; 78 79 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 struct GNUNET_PQ_ResultSpec rs[] = { 88 GNUNET_PQ_result_spec_uint64 ("bmeta_serial_id", 89 &bmeta_id), 90 GNUNET_PQ_result_spec_string ("description", 91 &description), 92 GNUNET_PQ_result_spec_uint64 ("bucket_start", 93 &bucket_start_epoch), 94 GNUNET_PQ_result_spec_uint64 ("bucket_end", 95 &bucket_end_epoch), 96 GNUNET_PQ_result_spec_string ("bucket_range", 97 &bucket_range), 98 TALER_PQ_result_spec_amount_with_currency ("cumulative_amount", 99 &cumulative_amount), 100 GNUNET_PQ_result_spec_end 101 }; 102 103 if (GNUNET_OK != 104 GNUNET_PQ_extract_result (result, 105 rs, 106 i)) 107 { 108 GNUNET_break (0); 109 tflc->extract_failed = true; 110 return; 111 } 112 /* Call callback if the bucket changed */ 113 if ( (NULL != resp_desc) && 114 ( (bmeta_id != bmeta_id_current) || 115 (bucket_start_epoch != cur_bucket_start_epoch) || 116 (0 != strcasecmp (resp_range, 117 bucket_range)) ) ) 118 { 119 struct GNUNET_TIME_Timestamp bucket_start; 120 struct GNUNET_TIME_Timestamp bucket_end; 121 122 bucket_start = GNUNET_TIME_timestamp_from_s (cur_bucket_start_epoch); 123 bucket_end = GNUNET_TIME_timestamp_from_s (cur_bucket_end_epoch); 124 tflc->cb (tflc->cb_cls, 125 resp_desc, 126 bucket_start, 127 bucket_end, 128 resp_range, 129 amounts_len, 130 amounts); 131 GNUNET_free (resp_range); 132 GNUNET_free (resp_desc); 133 GNUNET_array_grow (amounts, 134 amounts_len, 135 0); 136 } 137 if (NULL == resp_desc) 138 { 139 cur_bucket_end_epoch = bucket_end_epoch; 140 cur_bucket_start_epoch = bucket_start_epoch; 141 resp_range = GNUNET_strdup (bucket_range); 142 resp_desc = GNUNET_strdup (description); 143 bmeta_id_current = bmeta_id; 144 } 145 GNUNET_array_append (amounts, 146 amounts_len, 147 cumulative_amount); 148 GNUNET_PQ_cleanup_result (rs); 149 } 150 if (0 != amounts_len) 151 { 152 struct GNUNET_TIME_Timestamp bucket_start; 153 struct GNUNET_TIME_Timestamp bucket_end; 154 155 bucket_start = GNUNET_TIME_timestamp_from_s (cur_bucket_start_epoch); 156 bucket_end = GNUNET_TIME_timestamp_from_s (cur_bucket_end_epoch); 157 tflc->cb (tflc->cb_cls, 158 resp_desc, 159 bucket_start, 160 bucket_end, 161 resp_range, 162 amounts_len, 163 amounts); 164 GNUNET_array_grow (amounts, 165 amounts_len, 166 0); 167 GNUNET_free (resp_range); 168 GNUNET_free (resp_desc); 169 } 170 } 171 172 173 enum GNUNET_DB_QueryStatus 174 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 struct PostgresClosure *pg = cls; 182 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 struct GNUNET_PQ_QueryParam params[] = { 190 GNUNET_PQ_query_param_string (instance_id), 191 GNUNET_PQ_query_param_string (slug), 192 GNUNET_PQ_query_param_end 193 }; 194 enum GNUNET_DB_QueryStatus qs; 195 196 check_connection (pg); 197 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 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 if (context.extract_failed) 222 { 223 GNUNET_break (0); 224 return GNUNET_DB_STATUS_HARD_ERROR; 225 } 226 return qs; 227 }