Code

CData: Added support for undefined values.
[postrr.git] / src / cdata.c
1 /*
2  * PostRR - src/cdata.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 /*
29  * A PostgreSQL data-type providing consolidated data points.
30  */
32 #include "postrr.h"
34 #include <errno.h>
35 #include <string.h>
36 #include <math.h>
38 #include <postgres.h>
39 #include <fmgr.h>
41 /* Postgres utilities */
42 #include <catalog/pg_type.h>
43 #include <utils/array.h>
45 enum {
46         CF_AVG = 0,
47         CF_MIN = 1,
48         CF_MAX = 2
49 };
51 #define CF_TO_STR(cf) \
52         (((cf) == CF_AVG) \
53                 ? "AVG" \
54                 : ((cf) == CF_MIN) \
55                         ? "MIN" \
56                         : ((cf) == CF_MAX) \
57                                 ? "MAX" : "UNKNOWN")
59 /*
60  * data type
61  */
63 struct cdata {
64         float8 value;
65         int32 undef_num;
66         int32 val_num;
67         int32 cf;
68 };
70 /*
71  * prototypes for PostgreSQL functions
72  */
74 PG_FUNCTION_INFO_V1(cdata_validate);
76 PG_FUNCTION_INFO_V1(cdata_in);
77 PG_FUNCTION_INFO_V1(cdata_out);
78 PG_FUNCTION_INFO_V1(cdata_typmodin);
79 PG_FUNCTION_INFO_V1(cdata_typmodout);
81 PG_FUNCTION_INFO_V1(cdata_to_cdata);
82 PG_FUNCTION_INFO_V1(int32_to_cdata);
83 PG_FUNCTION_INFO_V1(cdata_to_float8);
85 PG_FUNCTION_INFO_V1(cdata_update);
87 /*
88  * public API
89  */
91 Datum
92 cdata_validate(PG_FUNCTION_ARGS)
93 {
94         char   type_info[1024];
95         char  *result;
96         size_t req_len;
97         size_t len;
99         if (PG_NARGS() != 1)
100                 ereport(ERROR, (
101                                         errmsg("cdata_validate() expect one argument"),
102                                         errhint("Usage cdata_validate(expected_size)")
103                                 ));
105         req_len = (size_t)PG_GETARG_UINT32(0);
106         len = sizeof(cdata_t);
108         if (req_len != len)
109                 ereport(ERROR, (
110                                         errmsg("length of the cdata type "
111                                                 "does not match the expected length"),
112                                         errhint("Please report a bug against PostRR")
113                                 ));
115         snprintf(type_info, sizeof(type_info),
116                         "cdata validated successfully; type length = %zu", len);
117         type_info[sizeof(type_info) - 1] = '\0';
119         result = pstrdup(type_info);
120         PG_RETURN_CSTRING(result);
121 } /* cdata_validate */
123 Datum
124 cdata_in(PG_FUNCTION_ARGS)
126         cdata_t *data;
127         int32 typmod;
129         char *val_str, *orig;
130         char *endptr = NULL;
132         if (PG_NARGS() != 3)
133                 ereport(ERROR, (
134                                         errmsg("cdata_in() expects three arguments"),
135                                         errhint("Usage: cdata_in(col_name, oid, typmod)")
136                                 ));
138         data = (cdata_t *)palloc0(sizeof(*data));
140         val_str = PG_GETARG_CSTRING(0);
141         typmod  = PG_GETARG_INT32(2);
143         orig = val_str;
144         while ((*val_str != '\0') && isspace((int)*val_str))
145                 ++val_str;
147         if (*val_str == '\0')
148                 ereport(ERROR, (
149                                         errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
150                                         errmsg("invalid input syntax for cdata: \"%s\"", orig)
151                                 ));
153         errno = 0;
154         data->value   = strtod(val_str, &endptr);
155         data->val_num = 1;
157         if (isnan(data->value)) {
158                 data->undef_num = 1;
159         }
161         if ((endptr == val_str) || errno)
162                 ereport(ERROR, (
163                                         errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
164                                         errmsg("invalid input syntax for cdata: \"%s\"", orig)
165                                 ));
167         while ((*endptr != '\0') && isspace((int)*endptr))
168                 ++endptr;
170         if (*endptr != '\0')
171                 ereport(ERROR, (
172                                         errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
173                                         errmsg("invalid input syntax for cdata: \"%s\"", orig),
174                                         errdetail("garbage found after number: \"%s\"", endptr)
175                                 ));
177         if (typmod > 0)
178                 data->cf = typmod;
179         else
180                 data->cf = 0;
182         PG_RETURN_CDATA_P(data);
183 } /* cdata_in */
185 Datum
186 cdata_out(PG_FUNCTION_ARGS)
188         cdata_t *data;
190         char  cd_str[1024];
191         char *result;
193         if (PG_NARGS() != 1)
194                 ereport(ERROR, (
195                                         errmsg("cdata_out() expects one argument"),
196                                         errhint("Usage: cdata_out(cdata)")
197                                 ));
199         data = PG_GETARG_CDATA_P(0);
201         snprintf(cd_str, sizeof(cd_str), "%g (%s U:%i/%i)",
202                         data->value, CF_TO_STR(data->cf),
203                         data->undef_num, data->val_num);
205         result = pstrdup(cd_str);
206         PG_RETURN_CSTRING(result);
207 } /* cdata_out */
209 Datum
210 cdata_typmodin(PG_FUNCTION_ARGS)
212         ArrayType *tm_array;
214         Datum *elem_values;
215         int    n;
216         char  *cf_str;
217         int32  typmod = CF_AVG;
219         if (PG_NARGS() != 1)
220                 ereport(ERROR, (
221                                         errmsg("cdata_typmodin() expects one argument"),
222                                         errhint("Usage: cdata_typmodin(array)")
223                                 ));
225         tm_array = PG_GETARG_ARRAYTYPE_P(0);
227         if (ARR_ELEMTYPE(tm_array) != CSTRINGOID)
228                 ereport(ERROR, (
229                                         errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
230                                         errmsg("typmod array must be type cstring[]")
231                                 ));
233         if (ARR_NDIM(tm_array) != 1)
234                 ereport(ERROR, (
235                                         errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
236                                         errmsg("typmod array must be one-dimensional")
237                                 ));
239         deconstruct_array(tm_array, CSTRINGOID,
240                         /* elmlen = */ -2, /* elmbyval = */ false, /* elmalign = */ 'c',
241                         &elem_values, /* nullsp = */ NULL, &n);
243         if (n != 1)
244                 ereport(ERROR, (
245                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
246                                         errmsg("cdata typmod array must have one element")
247                                 ));
249         cf_str = DatumGetCString(elem_values[0]);
250         if (! strcasecmp(cf_str, "AVG"))
251                 typmod = CF_AVG;
252         else if (! strcasecmp(cf_str, "MIN"))
253                 typmod = CF_MIN;
254         else if (! strcasecmp(cf_str, "MAX"))
255                 typmod = CF_MAX;
256         else
257                 ereport(ERROR, (
258                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
259                                         errmsg("invalid cdata typmod: %s", cf_str)
260                                 ));
262         PG_RETURN_INT32(typmod);
263 } /* cdata_typmodin */
265 Datum
266 cdata_typmodout(PG_FUNCTION_ARGS)
268         int32 typmod;
269         char  tm_str[32];
270         char *result;
272         if (PG_NARGS() != 1)
273                 ereport(ERROR, (
274                                         errmsg("cdata_typmodout() expects one argument"),
275                                         errhint("Usage: cdata_typmodout(typmod)")
276                                 ));
278         typmod = PG_GETARG_INT32(0);
279         snprintf(tm_str, sizeof(tm_str), "('%s')", CF_TO_STR(typmod));
280         tm_str[sizeof(tm_str) - 1] = '\0';
281         result = pstrdup(tm_str);
282         PG_RETURN_CSTRING(result);
283 } /* cdata_typmodout */
285 Datum
286 cdata_to_cdata(PG_FUNCTION_ARGS)
288         cdata_t *data;
289         int32 typmod;
291         if (PG_NARGS() != 3)
292                 ereport(ERROR, (
293                                         errmsg("cdata_to_cdata() "
294                                                 "expects three arguments"),
295                                         errhint("Usage: cdata_to_cdata"
296                                                 "(cdata, typmod, is_explicit)")
297                                 ));
299         data   = PG_GETARG_CDATA_P(0);
300         typmod = PG_GETARG_INT32(1);
302         if ((data->cf >= 0) && (data->cf != typmod) && (data->val_num > 1))
303                 ereport(ERROR, (
304                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
305                                         errmsg("invalid cast: cannot cast cdata "
306                                                 "with different typmod (yet)")
307                                 ));
309         data->cf = typmod;
310         PG_RETURN_CDATA_P(data);
311 } /* cdata_to_cdata */
313 Datum
314 int32_to_cdata(PG_FUNCTION_ARGS)
316         int32 i_val;
317         int32 typmod;
319         cdata_t *data;
321         if (PG_NARGS() != 3)
322                 ereport(ERROR, (
323                                         errmsg("int32_to_cdata() expects three arguments"),
324                                         errhint("Usage: int32_to_cdata"
325                                                 "(integer, typmod, is_explicit)")
326                                 ));
328         i_val  = PG_GETARG_INT32(0);
329         typmod = PG_GETARG_INT32(1);
331         data = (cdata_t *)palloc0(sizeof(*data));
333         data->value     = (float8)i_val;
334         data->undef_num = 0;
335         data->val_num   = 1;
337         if (typmod >= 0)
338                 data->cf = typmod;
339         else
340                 data->cf = CF_AVG;
342         PG_RETURN_CDATA_P(data);
343 } /* int32_to_cdata */
345 Datum
346 cdata_to_float8(PG_FUNCTION_ARGS)
348         cdata_t *data;
350         if (PG_NARGS() != 1)
351                 ereport(ERROR, (
352                                         errmsg("cdata_to_float8() expects one argument"),
353                                         errhint("Usage: cdata_to_float8(cdata)")
354                                 ));
356         data = PG_GETARG_CDATA_P(0);
357         PG_RETURN_FLOAT8(data->value);
358 } /* cdata_to_float8 */
360 Datum
361 cdata_update(PG_FUNCTION_ARGS)
363         cdata_t *data;
364         cdata_t *update;
366         float8 value;
367         float8 u_value;
369         int32 val_num;
370         int32 u_val_num;
372         if (PG_NARGS() != 2)
373                 ereport(ERROR, (
374                                         errmsg("cdata_update() expects two arguments"),
375                                         errhint("Usage: cdata_update(cdata, cdata)")
376                                 ));
378         data   = PG_GETARG_CDATA_P(0);
379         update = PG_GETARG_CDATA_P(1);
381         if (! data)
382                 PG_RETURN_CDATA_P(update);
384         if (! update)
385                 PG_RETURN_CDATA_P(data);
387         if ((data->cf != update->cf) && (update->val_num > 1))
388                 ereport(ERROR, (
389                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
390                                         errmsg("invalid update value: incompatible "
391                                                 "consolidation function")
392                                 ));
394         value     = data->value;
395         u_value   = update->value;
397         val_num   = data->val_num - data->undef_num;
398         u_val_num = update->val_num - update->undef_num;
400         data->undef_num += update->undef_num;
401         data->val_num   += update->val_num;
403         if (isnan(value) || isnan(u_value)) {
404                 data->value = isnan(value) ? u_value : value;
405                 PG_RETURN_CDATA_P(data);
406         }
408         switch (data->cf) {
409                 case CF_AVG:
410                         data->value = ((value * val_num) + (u_value * u_val_num))
411                                 / (val_num + u_val_num);
412                         break;
413                 case CF_MIN:
414                         data->value = (value < u_value) ? value : u_value;
415                         break;
416                 case CF_MAX:
417                         data->value = (value >= u_value) ? value : u_value;
418                         break;
419                 default:
420                         ereport(ERROR, (
421                                                 errcode(ERRCODE_DATA_CORRUPTED),
422                                                 errmsg("unknown consolidation function %d",
423                                                         data->cf)
424                                         ));
425                         break;
426         }
427         PG_RETURN_CDATA_P(data);
428 } /* cdata_update */
430 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */