Code

b08662292178e979c71c1d1c9e41bcb9855d6fe8
[postrr.git] / src / rrtimeslice.c
1 /*
2  * PostRR - src/rrtimeslice.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 a timeslice implementing round-robin
30  * features.
31  */
33 #include "postrr.h"
34 #include "utils/pg_spi.h"
36 #include <string.h>
38 #include <postgres.h>
39 #include <fmgr.h>
41 /* Postgres utilities */
42 #include <access/hash.h>
43 #include <executor/spi.h>
44 #include <utils/array.h>
45 #include <utils/datetime.h>
46 #include <utils/timestamp.h>
47 #include <miscadmin.h> /* DateStyle, IntervalStyle */
49 #ifdef HAVE_INT64_TIMESTAMP
50 #       define TSTAMP_TO_INT64(t) (t)
51 #       define INT64_TO_TSTAMP(i) (i)
52 #else /* ! HAVE_INT64_TIMESTAMP */
53 #       define TSTAMP_TO_INT64(t) (int64)((t) * (double)USECS_PER_SEC)
54 #       define INT64_TO_TSTAMP(i) ((double)(i) / (double)USECS_PER_SEC)
55 #endif
57 /*
58  * data type
59  */
61 struct rrtimeslice {
62         Timestamp tstamp;
63         int32  tsid;
64         uint32 seq;
65 };
67 /*
68  * internal helper functions
69  */
71 static int32
72 rrtimeslice_set_spec(int32 len, int32 num)
73 {
74         int spi_rc;
76         char  query[256];
77         int32 typmod = 0;
79         if ((len <= 0) || (num <= 0))
80                 ereport(ERROR, (
81                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
82                                         errmsg("rrtimeslice(%i, %i) "
83                                                 "length/num may not be less than zero",
84                                                 len, num)
85                                 ));
87         if ((spi_rc = SPI_connect()) != SPI_OK_CONNECT)
88                 ereport(ERROR, (
89                                         errmsg("failed to store rrtimeslice spec: "
90                                                 "could not connect to SPI manager: %s",
91                                                 SPI_result_code_string(spi_rc))
92                                 ));
94         snprintf(query, sizeof(query),
95                         "SELECT tsid FROM postrr.rrtimeslices "
96                                 "WHERE tslen = %d AND tsnum = %d "
97                                 "LIMIT 1", len, num);
98         query[sizeof(query) - 1] = '\0';
100         spi_rc = pg_spi_get_int(query, 1, &typmod);
101         if (spi_rc == PG_SPI_OK) {
102                 SPI_finish();
103                 return typmod;
104         }
105         else if (spi_rc != PG_SPI_ERROR_NO_VALUES)
106                 pg_spi_ereport(ERROR, "store rrtimeslice spec", spi_rc);
108         snprintf(query, sizeof(query),
109                         "SELECT nextval('postrr.tsid'::regclass)");
110         query[sizeof(query) - 1] = '\0';
112         spi_rc = pg_spi_get_int(query, 1, &typmod);
113         if ((spi_rc != PG_SPI_OK) || (typmod <= 0))
114                 pg_spi_ereport(ERROR, "retrieve nextval(postrr.tsid)", spi_rc);
116         snprintf(query, sizeof(query),
117                         "INSERT INTO postrr.rrtimeslices(tsid, tslen, tsnum) "
118                         "VALUES (%d, %d, %d)", typmod, len, num);
119         query[sizeof(query) - 1] = '\0';
121         spi_rc = SPI_exec(query, /* max num rows = */ 1);
122         if (spi_rc != SPI_OK_INSERT)
123                 ereport(ERROR, (
124                                         errmsg("failed to store rrtimeslice spec: "
125                                                 "failed to execute query: %s",
126                                                 SPI_result_code_string(spi_rc))
127                                 ));
129         SPI_finish();
130         return typmod;
131 } /* rrtimeslice_set_spec */
133 static int
134 rrtimeslice_get_spec(int32 typmod, int32 *len, int32 *num)
136         int spi_rc;
138         char query[256];
140         if (typmod <= 0)
141                 return -1;
143         if ((spi_rc = SPI_connect()) != SPI_OK_CONNECT)
144                 ereport(ERROR, (
145                                         errmsg("failed to determine rrtimeslice spec: "
146                                                 "could not connect to SPI manager: %s",
147                                                 SPI_result_code_string(spi_rc))
148                                 ));
150         snprintf(query, sizeof(query),
151                         "SELECT tslen, tsnum FROM postrr.rrtimeslices "
152                                 "WHERE tsid = %d", typmod);
153         query[sizeof(query) - 1] = '\0';
155         spi_rc = pg_spi_get_int(query, 2, len, num);
156         if (spi_rc != PG_SPI_OK)
157                 pg_spi_ereport(ERROR, "determine rrtimeslice spec", spi_rc);
159         SPI_finish();
160         return 0;
161 } /* rrtimeslice_get_spec */
163 static int
164 rrtimeslice_apply_typmod(rrtimeslice_t *tslice, int32 typmod)
166         int64 tstamp;
167         int64 length;
168         int64 seq;
170         int32 len = 0;
171         int32 num = 0;
173         if (rrtimeslice_get_spec(typmod, &len, &num))
174                 return -1;
176         if ((len <= 0) || (num <= 0))
177                 ereport(ERROR, (
178                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
179                                         errmsg("rrtimeslice(%i, %i) "
180                                                 "length/num may not be less than zero",
181                                                 len, num)
182                                 ));
184         tstamp = TSTAMP_TO_INT64(tslice->tstamp);
186         length = len * USECS_PER_SEC;
187         if (tstamp % length != 0)
188                 tstamp = tstamp - (tstamp % length) + length;
189         seq    = tstamp % (length * num) / length;
190         seq    = seq % num;
192         tslice->tstamp = INT64_TO_TSTAMP(tstamp);
193         tslice->tsid   = typmod;
194         tslice->seq    = (uint32)seq;
195         return 0;
196 } /* rrtimeslice_apply_typmod */
198 /*
199  * prototypes for PostgreSQL functions
200  */
202 PG_FUNCTION_INFO_V1(rrtimeslice_validate);
204 PG_FUNCTION_INFO_V1(rrtimeslice_in);
205 PG_FUNCTION_INFO_V1(rrtimeslice_out);
206 PG_FUNCTION_INFO_V1(rrtimeslice_typmodin);
207 PG_FUNCTION_INFO_V1(rrtimeslice_typmodout);
209 PG_FUNCTION_INFO_V1(rrtimeslice_to_rrtimeslice);
211 PG_FUNCTION_INFO_V1(rrtimeslice_eq);
212 PG_FUNCTION_INFO_V1(rrtimeslice_ne);
213 PG_FUNCTION_INFO_V1(rrtimeslice_lt);
214 PG_FUNCTION_INFO_V1(rrtimeslice_gt);
215 PG_FUNCTION_INFO_V1(rrtimeslice_le);
216 PG_FUNCTION_INFO_V1(rrtimeslice_ge);
217 PG_FUNCTION_INFO_V1(rrtimeslice_cmp);
218 PG_FUNCTION_INFO_V1(rrtimeslice_hash);
220 /*
221  * public API
222  */
224 Datum
225 rrtimeslice_validate(PG_FUNCTION_ARGS)
227         char   type_info[1024];
228         char  *result;
229         size_t req_len;
230         size_t len;
232         if (PG_NARGS() != 1)
233                 ereport(ERROR, (
234                                         errmsg("rrtimeslice_validate() expect one argument"),
235                                         errhint("Usage rrtimeslice_validate(expected_size)")
236                                 ));
238         req_len = (size_t)PG_GETARG_UINT32(0);
239         len = sizeof(rrtimeslice_t);
241         if (req_len != len)
242                 ereport(ERROR, (
243                                         errmsg("length of the rrtimeslice type "
244                                                 "does not match the expected length"),
245                                         errhint("Please report a bug against PostRR")
246                                 ));
248         snprintf(type_info, sizeof(type_info),
249                         "rrtimeslice validated successfully; type length = %zu", len);
250         type_info[sizeof(type_info) - 1] = '\0';
252         result = pstrdup(type_info);
253         PG_RETURN_CSTRING(result);
254 } /* rrtimeslice_validate */
256 Datum
257 rrtimeslice_in(PG_FUNCTION_ARGS)
259         rrtimeslice_t *tslice;
261         Timestamp tstamp = 0;
262         int32 typmod;
264         struct pg_tm tm;
265         fsec_t fsec = 0;
266         int tz = 0;
268         char *time_str;
269         int   pg_dt_err;
270         char  buf[MAXDATELEN + MAXDATEFIELDS];
271         char *field[MAXDATEFIELDS];
272         int   ftype[MAXDATEFIELDS];
273         int   num_fields = 0;
274         int   dtype = 0;
276         if (PG_NARGS() != 3)
277                 ereport(ERROR, (
278                                         errmsg("rrtimeslice_in() expects three arguments"),
279                                         errhint("Usage: rrtimeslice_in(col_name, oid, typmod)")
280                                 ));
282         tslice   = (rrtimeslice_t *)palloc0(sizeof(*tslice));
283         time_str = PG_GETARG_CSTRING(0);
284         typmod   = PG_GETARG_INT32(2);
286         pg_dt_err = ParseDateTime(time_str, buf, sizeof(buf),
287                         field, ftype, MAXDATEFIELDS, &num_fields);
289         if (! pg_dt_err)
290                 pg_dt_err = DecodeDateTime(field, ftype, num_fields,
291                                 &dtype, &tm, &fsec, &tz);
292         if (pg_dt_err)
293                 DateTimeParseError(pg_dt_err, time_str, "rrtimeslice");
295         switch (dtype) {
296                 case DTK_DATE:
297                         if (tm2timestamp(&tm, fsec, NULL, &tstamp))
298                                 ereport(ERROR, (
299                                                         errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
300                                                         errmsg("timestamp out of range: %s", time_str)
301                                                 ));
302                         break;
304                 case DTK_EPOCH:
305                         tstamp = SetEpochTimestamp();
306                         break;
308                 default:
309                         ereport(ERROR, (
310                                                 errmsg("unexpected dtype %d while "
311                                                         "parsing rrtimeslice: %s", dtype, time_str)
312                                         ));
313         }
315         tslice->tstamp = tstamp;
317         /* most likely, this won't happen … coerce_type
318          * (src/backend/parser/parse_coerce.c) does not pass that information to
319          * the input function but rather lets a length conversion cast do that
320          * work */
321         if (typmod > 0)
322                 rrtimeslice_apply_typmod(tslice, typmod);
324         PG_RETURN_RRTIMESLICE_P(tslice);
325 } /* rrtimeslice_in */
327 Datum
328 rrtimeslice_out(PG_FUNCTION_ARGS)
330         rrtimeslice_t *tslice;
332         Interval interval;
334         struct pg_tm tm;
335         fsec_t fsec = 0;
336         char *tz = NULL;
338         char  ts_str[MAXDATELEN + 1];
339         char  buf_ts[MAXDATELEN + 1];
340         char  buf_l[MAXDATELEN + 1];
341         char *result;
343         int32 len = 0;
344         int32 num = 0;
346         if (PG_NARGS() != 1)
347                 ereport(ERROR, (
348                                         errmsg("rrtimeslice_out() expects one argument"),
349                                         errhint("Usage: rrtimeslice_out(rrtimeslice)")
350                                 ));
352         tslice = PG_GETARG_RRTIMESLICE_P(0);
354         if (TIMESTAMP_NOT_FINITE(tslice->tstamp)
355                         || (timestamp2tm(tslice->tstamp, NULL, &tm, &fsec, NULL, NULL) != 0))
356                 ereport(ERROR, (
357                                         errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
358                                         errmsg("invalid (non-finite) timestamp")
359                                 ));
360         else
361                 EncodeDateTime(&tm, fsec, NULL, &tz, DateStyle, buf_ts);
363         if (! rrtimeslice_get_spec(tslice->tsid, &len, &num)) {
364                 memset(&interval, 0, sizeof(interval));
365                 interval.time = len * USECS_PER_SEC;
366                 fsec = 0;
368                 if (interval2tm(interval, &tm, &fsec))
369                         ereport(ERROR, (
370                                                 errmsg("could not convert interval to tm")
371                                         ));
372         }
373         else {
374                 strncpy(buf_l, "ERR", sizeof(buf_l));
375                 buf_l[sizeof(buf_l) - 1] = '\0';
376         }
378         EncodeInterval(&tm, fsec, IntervalStyle, buf_l);
380         snprintf(ts_str, sizeof(ts_str), "%s -%s (#%i)",
381                         buf_ts, buf_l, tslice->seq);
383         result = pstrdup(ts_str);
384         PG_RETURN_CSTRING(result);
385 } /* rrtimeslice_out */
387 Datum
388 rrtimeslice_typmodin(PG_FUNCTION_ARGS)
390         ArrayType *tm_array;
392         int32 *spec;
393         int    spec_elems = 0;
394         int32  typmod;
396         if (PG_NARGS() != 1)
397                 ereport(ERROR, (
398                                         errmsg("rrtimeslice_typmodin() expects one argument"),
399                                         errhint("Usage: rrtimeslice_typmodin(array)")
400                                 ));
402         tm_array = PG_GETARG_ARRAYTYPE_P(0);
404         spec = ArrayGetIntegerTypmods(tm_array, &spec_elems);
405         if (spec_elems != 2)
406                 ereport(ERROR, (
407                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
408                                         errmsg("invalid rrtimeslice type modifier"),
409                                         errhint("Usage: rrtimeslice(<slice_len>, <num>)")
410                                 ));
412         typmod = rrtimeslice_set_spec(spec[0], spec[1]);
413         PG_RETURN_INT32(typmod);
414 } /* rrtimeslice_typmodin */
416 Datum
417 rrtimeslice_typmodout(PG_FUNCTION_ARGS)
419         int32 typmod;
420         char  tm_str[1024];
421         char *result;
423         int32 len = 0;
424         int32 num = 0;
426         if (PG_NARGS() != 1)
427                 ereport(ERROR, (
428                                         errmsg("rrtimeslice_typmodout() expects one argument"),
429                                         errhint("Usage: rrtimeslice_typmodout(typmod)")
430                                 ));
432         typmod = PG_GETARG_INT32(0);
433         if (rrtimeslice_get_spec(typmod, &len, &num))
434                 tm_str[0] = '\0';
435         else if ((len <= 0) || (num <= 0))
436                 snprintf(tm_str, sizeof(tm_str), "(#ERR, #ERR)");
437         else
438                 snprintf(tm_str, sizeof(tm_str), "(%d, %d)", len, num);
440         result = pstrdup(tm_str);
441         PG_RETURN_CSTRING(result);
442 } /* rrtimeslice_typmodout */
444 Datum
445 rrtimeslice_to_rrtimeslice(PG_FUNCTION_ARGS)
447         rrtimeslice_t *tslice;
448         int32 typmod;
450         if (PG_NARGS() != 3)
451                 ereport(ERROR, (
452                                         errmsg("rrtimeslice_to_rrtimeslice() "
453                                                 "expects three arguments"),
454                                         errhint("Usage: rrtimeslice_to_rrtimeslice"
455                                                 "(rrtimeslice, typmod, is_explicit)")
456                                 ));
458         tslice = PG_GETARG_RRTIMESLICE_P(0);
459         typmod = PG_GETARG_INT32(1);
461         if (typmod > 0) {
462                 if ((! tslice->tsid) && (! tslice->seq))
463                         rrtimeslice_apply_typmod(tslice, typmod);
464                 else
465                         ereport(ERROR, (
466                                                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
467                                                 errmsg("invalid cast: cannot cast rrtimeslices "
468                                                         "with different typmod (yet)")
469                                         ));
470         }
472         PG_RETURN_RRTIMESLICE_P(tslice);
473 } /* rrtimeslice_to_rrtimeslice */
475 int
476 rrtimeslice_cmp_internal(rrtimeslice_t *ts1, rrtimeslice_t *ts2)
478         if ((! ts1) && (! ts2))
479                 return 0;
480         else if (! ts1)
481                 return -1;
482         else if (! ts2)
483                 return 1;
485         if (ts1->tsid && (! ts2->tsid))
486                 rrtimeslice_apply_typmod(ts2, ts1->tsid);
487         else if ((! ts1->tsid) && ts2->tsid)
488                 rrtimeslice_apply_typmod(ts1, ts2->tsid);
490         if (ts1->tsid != ts2->tsid) /* XXX: compare len/num */
491                 ereport(ERROR, (
492                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
493                                         errmsg("invalid comparison: cannot compare "
494                                                 "rrtimeslices with different typmods (yet)")
495                                 ));
497         if (ts1->seq == ts2->seq)
498                 return 0;
500         return timestamp_cmp_internal(ts1->tstamp, ts2->tstamp);
501 } /* rrtimeslice_cmp_internal */
503 Datum
504 rrtimeslice_eq(PG_FUNCTION_ARGS)
506         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
507         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
509         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) == 0);
510 } /* rrtimeslice_eq */
512 Datum
513 rrtimeslice_ne(PG_FUNCTION_ARGS)
515         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
516         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
518         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) != 0);
519 } /* rrtimeslice_ne */
521 Datum
522 rrtimeslice_lt(PG_FUNCTION_ARGS)
524         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
525         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
527         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) < 0);
528 } /* rrtimeslice_lt */
530 Datum
531 rrtimeslice_le(PG_FUNCTION_ARGS)
533         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
534         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
536         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) <= 0);
537 } /* rrtimeslice_le */
539 Datum
540 rrtimeslice_gt(PG_FUNCTION_ARGS)
542         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
543         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
545         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) > 0);
546 } /* rrtimeslice_gt */
548 Datum
549 rrtimeslice_ge(PG_FUNCTION_ARGS)
551         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
552         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
554         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) >= 0);
555 } /* rrtimeslice_ge */
557 Datum
558 rrtimeslice_cmp(PG_FUNCTION_ARGS)
560         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
561         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
563         PG_RETURN_INT32(rrtimeslice_cmp_internal(ts1, ts2));
564 } /* rrtimeslice_ge */
566 Datum
567 rrtimeslice_hash(PG_FUNCTION_ARGS)
569         rrtimeslice_t *ts = PG_GETARG_RRTIMESLICE_P(0);
570         return hash_uint32(ts->seq);
571 } /* rrtimeslice_hash */
573 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */