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)
125 {
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)
187 {
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)
211 {
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)
267 {
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)
287 {
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)
315 {
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)
347 {
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)
362 {
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 : */