Code

Initial import of PostRR -- the PostgreSQL Round-Robin Extension.
[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 <executor/spi.h>
43 #include <utils/array.h>
44 #include <utils/datetime.h>
45 #include <utils/timestamp.h>
46 #include <miscadmin.h> /* DateStyle, IntervalStyle */
48 #ifdef HAVE_INT64_TIMESTAMP
49 #       define TSTAMP_TO_INT64(t) (t)
50 #       define INT64_TO_TSTAMP(i) (i)
51 #else /* ! HAVE_INT64_TIMESTAMP */
52 #       define TSTAMP_TO_INT64(t) (int64)((t) * (double)USECS_PER_SEC)
53 #       define INT64_TO_TSTAMP(i) ((double)(i) / (double)USECS_PER_SEC)
54 #endif
56 /*
57  * data type
58  */
60 struct rrtimeslice {
61         Timestamp tstamp;
62         int32  tsid;
63         uint32 seq;
64 };
66 /*
67  * internal helper functions
68  */
70 static int32
71 rrtimeslice_set_spec(int32 len, int32 num)
72 {
73         int spi_rc;
75         char  query[256];
76         int32 typmod = 0;
78         if ((len <= 0) || (num <= 0))
79                 ereport(ERROR, (
80                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
81                                         errmsg("rrtimeslice(%i, %i) "
82                                                 "length/num may not be less than zero",
83                                                 len, num)
84                                 ));
86         if ((spi_rc = SPI_connect()) != SPI_OK_CONNECT)
87                 ereport(ERROR, (
88                                         errmsg("failed to store rrtimeslice spec: "
89                                                 "could not connect to SPI manager: %s",
90                                                 SPI_result_code_string(spi_rc))
91                                 ));
93         snprintf(query, sizeof(query),
94                         "SELECT tsid FROM postrr.rrtimeslices "
95                                 "WHERE tslen = %d AND tsnum = %d "
96                                 "LIMIT 1", len, num);
97         query[sizeof(query) - 1] = '\0';
99         spi_rc = pg_spi_get_int(query, 1, &typmod);
100         if (spi_rc == PG_SPI_OK) {
101                 SPI_finish();
102                 return typmod;
103         }
104         else if (spi_rc != PG_SPI_ERROR_NO_VALUES)
105                 pg_spi_ereport(ERROR, "store rrtimeslice spec", spi_rc);
107         snprintf(query, sizeof(query),
108                         "SELECT nextval('postrr.tsid'::regclass)");
109         query[sizeof(query) - 1] = '\0';
111         spi_rc = pg_spi_get_int(query, 1, &typmod);
112         if ((spi_rc != PG_SPI_OK) || (typmod <= 0))
113                 pg_spi_ereport(ERROR, "retrieve nextval(postrr.tsid)", spi_rc);
115         snprintf(query, sizeof(query),
116                         "INSERT INTO postrr.rrtimeslices(tsid, tslen, tsnum) "
117                         "VALUES (%d, %d, %d)", typmod, len, num);
118         query[sizeof(query) - 1] = '\0';
120         spi_rc = SPI_exec(query, /* max num rows = */ 1);
121         if (spi_rc != SPI_OK_INSERT)
122                 ereport(ERROR, (
123                                         errmsg("failed to store rrtimeslice spec: "
124                                                 "failed to execute query: %s",
125                                                 SPI_result_code_string(spi_rc))
126                                 ));
128         SPI_finish();
129         return typmod;
130 } /* rrtimeslice_set_spec */
132 static int
133 rrtimeslice_get_spec(int32 typmod, int32 *len, int32 *num)
135         int spi_rc;
137         char query[256];
139         if (typmod <= 0)
140                 return -1;
142         if ((spi_rc = SPI_connect()) != SPI_OK_CONNECT)
143                 ereport(ERROR, (
144                                         errmsg("failed to determine rrtimeslice spec: "
145                                                 "could not connect to SPI manager: %s",
146                                                 SPI_result_code_string(spi_rc))
147                                 ));
149         snprintf(query, sizeof(query),
150                         "SELECT tslen, tsnum FROM postrr.rrtimeslices "
151                                 "WHERE tsid = %d", typmod);
152         query[sizeof(query) - 1] = '\0';
154         spi_rc = pg_spi_get_int(query, 2, len, num);
155         if (spi_rc != PG_SPI_OK)
156                 pg_spi_ereport(ERROR, "determine rrtimeslice spec", spi_rc);
158         SPI_finish();
159         return 0;
160 } /* rrtimeslice_get_spec */
162 static int
163 rrtimeslice_apply_typmod(rrtimeslice_t *tslice, int32 typmod)
165         int64 tstamp;
166         int64 length;
167         int64 seq;
169         int32 len = 0;
170         int32 num = 0;
172         if (rrtimeslice_get_spec(typmod, &len, &num))
173                 return -1;
175         if ((len <= 0) || (num <= 0))
176                 ereport(ERROR, (
177                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
178                                         errmsg("rrtimeslice(%i, %i) "
179                                                 "length/num may not be less than zero",
180                                                 len, num)
181                                 ));
183         tstamp = TSTAMP_TO_INT64(tslice->tstamp);
185         length = len * USECS_PER_SEC;
186         if (tstamp % length != 0)
187                 tstamp = tstamp - (tstamp % length) + length;
188         seq    = tstamp % (length * num) / length;
189         seq    = seq % num;
191         tslice->tstamp = INT64_TO_TSTAMP(tstamp);
192         tslice->tsid   = typmod;
193         tslice->seq    = (uint32)seq;
194         return 0;
195 } /* rrtimeslice_apply_typmod */
197 /*
198  * prototypes for PostgreSQL functions
199  */
201 PG_FUNCTION_INFO_V1(rrtimeslice_validate);
203 PG_FUNCTION_INFO_V1(rrtimeslice_in);
204 PG_FUNCTION_INFO_V1(rrtimeslice_out);
205 PG_FUNCTION_INFO_V1(rrtimeslice_typmodin);
206 PG_FUNCTION_INFO_V1(rrtimeslice_typmodout);
208 PG_FUNCTION_INFO_V1(rrtimeslice_to_rrtimeslice);
210 PG_FUNCTION_INFO_V1(rrtimeslice_eq);
211 PG_FUNCTION_INFO_V1(rrtimeslice_ne);
212 PG_FUNCTION_INFO_V1(rrtimeslice_lt);
213 PG_FUNCTION_INFO_V1(rrtimeslice_gt);
214 PG_FUNCTION_INFO_V1(rrtimeslice_le);
215 PG_FUNCTION_INFO_V1(rrtimeslice_ge);
216 PG_FUNCTION_INFO_V1(rrtimeslice_cmp);
218 /*
219  * public API
220  */
222 Datum
223 rrtimeslice_validate(PG_FUNCTION_ARGS)
225         char   type_info[1024];
226         char  *result;
227         size_t req_len;
228         size_t len;
230         if (PG_NARGS() != 1)
231                 ereport(ERROR, (
232                                         errmsg("rrtimeslice_validate() expect one argument"),
233                                         errhint("Usage rrtimeslice_validate(expected_size)")
234                                 ));
236         req_len = (size_t)PG_GETARG_UINT32(0);
237         len = sizeof(rrtimeslice_t);
239         if (req_len != len)
240                 ereport(ERROR, (
241                                         errmsg("length of the rrtimeslice type "
242                                                 "does not match the expected length"),
243                                         errhint("Please report a bug against PostRR")
244                                 ));
246         snprintf(type_info, sizeof(type_info),
247                         "rrtimeslice validated successfully; type length = %zu", len);
248         type_info[sizeof(type_info) - 1] = '\0';
250         result = pstrdup(type_info);
251         PG_RETURN_CSTRING(result);
252 } /* rrtimeslice_validate */
254 Datum
255 rrtimeslice_in(PG_FUNCTION_ARGS)
257         rrtimeslice_t *tslice;
259         Timestamp tstamp = 0;
260         int32 typmod;
262         struct pg_tm tm;
263         fsec_t fsec = 0;
264         int tz = 0;
266         char *time_str;
267         int   pg_dt_err;
268         char  buf[MAXDATELEN + MAXDATEFIELDS];
269         char *field[MAXDATEFIELDS];
270         int   ftype[MAXDATEFIELDS];
271         int   num_fields = 0;
272         int   dtype = 0;
274         if (PG_NARGS() != 3)
275                 ereport(ERROR, (
276                                         errmsg("rrtimeslice_in() expects three arguments"),
277                                         errhint("Usage: rrtimeslice_in(col_name, oid, typmod)")
278                                 ));
280         tslice   = (rrtimeslice_t *)palloc0(sizeof(*tslice));
281         time_str = PG_GETARG_CSTRING(0);
282         typmod   = PG_GETARG_INT32(2);
284         pg_dt_err = ParseDateTime(time_str, buf, sizeof(buf),
285                         field, ftype, MAXDATEFIELDS, &num_fields);
287         if (! pg_dt_err)
288                 pg_dt_err = DecodeDateTime(field, ftype, num_fields,
289                                 &dtype, &tm, &fsec, &tz);
290         if (pg_dt_err)
291                 DateTimeParseError(pg_dt_err, time_str, "rrtimeslice");
293         switch (dtype) {
294                 case DTK_DATE:
295                         if (tm2timestamp(&tm, fsec, NULL, &tstamp))
296                                 ereport(ERROR, (
297                                                         errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
298                                                         errmsg("timestamp out of range: %s", time_str)
299                                                 ));
300                         break;
302                 case DTK_EPOCH:
303                         tstamp = SetEpochTimestamp();
304                         break;
306                 default:
307                         ereport(ERROR, (
308                                                 errmsg("unexpected dtype %d while "
309                                                         "parsing rrtimeslice: %s", dtype, time_str)
310                                         ));
311         }
313         tslice->tstamp = tstamp;
315         /* most likely, this won't happen â€¦ coerce_type
316          * (src/backend/parser/parse_coerce.c) does not pass that information to
317          * the input function but rather lets a length conversion cast do that
318          * work */
319         if (typmod > 0)
320                 rrtimeslice_apply_typmod(tslice, typmod);
322         PG_RETURN_RRTIMESLICE_P(tslice);
323 } /* rrtimeslice_in */
325 Datum
326 rrtimeslice_out(PG_FUNCTION_ARGS)
328         rrtimeslice_t *tslice;
330         Interval interval;
332         struct pg_tm tm;
333         fsec_t fsec = 0;
334         char *tz = NULL;
336         char  ts_str[MAXDATELEN + 1];
337         char  buf_ts[MAXDATELEN + 1];
338         char  buf_l[MAXDATELEN + 1];
339         char *result;
341         int32 len = 0;
342         int32 num = 0;
344         if (PG_NARGS() != 1)
345                 ereport(ERROR, (
346                                         errmsg("rrtimeslice_out() expects one argument"),
347                                         errhint("Usage: rrtimeslice_out(rrtimeslice)")
348                                 ));
350         tslice = PG_GETARG_RRTIMESLICE_P(0);
352         if (TIMESTAMP_NOT_FINITE(tslice->tstamp)
353                         || (timestamp2tm(tslice->tstamp, NULL, &tm, &fsec, NULL, NULL) != 0))
354                 ereport(ERROR, (
355                                         errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
356                                         errmsg("invalid (non-finite) timestamp")
357                                 ));
358         else
359                 EncodeDateTime(&tm, fsec, NULL, &tz, DateStyle, buf_ts);
361         if (! rrtimeslice_get_spec(tslice->tsid, &len, &num)) {
362                 memset(&interval, 0, sizeof(interval));
363                 interval.time = len * USECS_PER_SEC;
364                 fsec = 0;
366                 if (interval2tm(interval, &tm, &fsec))
367                         ereport(ERROR, (
368                                                 errmsg("could not convert interval to tm")
369                                         ));
370         }
371         else {
372                 strncpy(buf_l, "ERR", sizeof(buf_l));
373                 buf_l[sizeof(buf_l) - 1] = '\0';
374         }
376         EncodeInterval(&tm, fsec, IntervalStyle, buf_l);
378         snprintf(ts_str, sizeof(ts_str), "%s -%s (#%i)",
379                         buf_ts, buf_l, tslice->seq);
381         result = pstrdup(ts_str);
382         PG_RETURN_CSTRING(result);
383 } /* rrtimeslice_out */
385 Datum
386 rrtimeslice_typmodin(PG_FUNCTION_ARGS)
388         ArrayType *tm_array;
390         int32 *spec;
391         int    spec_elems = 0;
392         int32  typmod;
394         if (PG_NARGS() != 1)
395                 ereport(ERROR, (
396                                         errmsg("rrtimeslice_typmodin() expects one argument"),
397                                         errhint("Usage: rrtimeslice_typmodin(array)")
398                                 ));
400         tm_array = PG_GETARG_ARRAYTYPE_P(0);
402         spec = ArrayGetIntegerTypmods(tm_array, &spec_elems);
403         if (spec_elems != 2)
404                 ereport(ERROR, (
405                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
406                                         errmsg("invalid rrtimeslice type modifier"),
407                                         errhint("Usage: rrtimeslice(<slice_len>, <num>)")
408                                 ));
410         typmod = rrtimeslice_set_spec(spec[0], spec[1]);
411         PG_RETURN_INT32(typmod);
412 } /* rrtimeslice_typmodin */
414 Datum
415 rrtimeslice_typmodout(PG_FUNCTION_ARGS)
417         int32 typmod;
418         char  tm_str[1024];
419         char *result;
421         int32 len = 0;
422         int32 num = 0;
424         if (PG_NARGS() != 1)
425                 ereport(ERROR, (
426                                         errmsg("rrtimeslice_typmodout() expects one argument"),
427                                         errhint("Usage: rrtimeslice_typmodout(typmod)")
428                                 ));
430         typmod = PG_GETARG_INT32(0);
431         if (rrtimeslice_get_spec(typmod, &len, &num))
432                 tm_str[0] = '\0';
433         else if ((len <= 0) || (num <= 0))
434                 snprintf(tm_str, sizeof(tm_str), "(#ERR, #ERR)");
435         else
436                 snprintf(tm_str, sizeof(tm_str), "(%d, %d)", len, num);
438         result = pstrdup(tm_str);
439         PG_RETURN_CSTRING(result);
440 } /* rrtimeslice_typmodout */
442 Datum
443 rrtimeslice_to_rrtimeslice(PG_FUNCTION_ARGS)
445         rrtimeslice_t *tslice;
446         int32 typmod;
448         if (PG_NARGS() != 3)
449                 ereport(ERROR, (
450                                         errmsg("rrtimeslice_to_rrtimeslice() "
451                                                 "expects three arguments"),
452                                         errhint("Usage: rrtimeslice_to_rrtimeslice"
453                                                 "(rrtimeslice, typmod, is_explicit)")
454                                 ));
456         tslice = PG_GETARG_RRTIMESLICE_P(0);
457         typmod = PG_GETARG_INT32(1);
459         if (typmod > 0) {
460                 if ((! tslice->tsid) && (! tslice->seq))
461                         rrtimeslice_apply_typmod(tslice, typmod);
462                 else
463                         ereport(ERROR, (
464                                                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
465                                                 errmsg("invalid cast: cannot cast rrtimeslices "
466                                                         "with different typmod (yet)")
467                                         ));
468         }
470         PG_RETURN_RRTIMESLICE_P(tslice);
471 } /* rrtimeslice_to_rrtimeslice */
473 int
474 rrtimeslice_cmp_internal(rrtimeslice_t *ts1, rrtimeslice_t *ts2)
476         if ((! ts1) && (! ts2))
477                 return 0;
478         else if (! ts1)
479                 return -1;
480         else if (! ts2)
481                 return 1;
483         if (ts1->tsid && (! ts2->tsid))
484                 rrtimeslice_apply_typmod(ts2, ts1->tsid);
485         else if ((! ts1->tsid) && ts2->tsid)
486                 rrtimeslice_apply_typmod(ts1, ts2->tsid);
488         if (ts1->tsid != ts2->tsid) /* XXX: compare len/num */
489                 ereport(ERROR, (
490                                         errcode(ERRCODE_INVALID_PARAMETER_VALUE),
491                                         errmsg("invalid comparison: cannot compare "
492                                                 "rrtimeslices with different typmods (yet)")
493                                 ));
495         if (ts1->seq == ts2->seq)
496                 return 0;
498         return timestamp_cmp_internal(ts1->tstamp, ts2->tstamp);
499 } /* rrtimeslice_cmp_internal */
501 Datum
502 rrtimeslice_eq(PG_FUNCTION_ARGS)
504         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
505         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
507         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) == 0);
508 } /* rrtimeslice_eq */
510 Datum
511 rrtimeslice_ne(PG_FUNCTION_ARGS)
513         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
514         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
516         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) != 0);
517 } /* rrtimeslice_ne */
519 Datum
520 rrtimeslice_lt(PG_FUNCTION_ARGS)
522         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
523         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
525         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) < 0);
526 } /* rrtimeslice_lt */
528 Datum
529 rrtimeslice_le(PG_FUNCTION_ARGS)
531         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
532         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
534         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) <= 0);
535 } /* rrtimeslice_le */
537 Datum
538 rrtimeslice_gt(PG_FUNCTION_ARGS)
540         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
541         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
543         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) > 0);
544 } /* rrtimeslice_gt */
546 Datum
547 rrtimeslice_ge(PG_FUNCTION_ARGS)
549         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
550         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
552         PG_RETURN_BOOL(rrtimeslice_cmp_internal(ts1, ts2) >= 0);
553 } /* rrtimeslice_ge */
555 Datum
556 rrtimeslice_cmp(PG_FUNCTION_ARGS)
558         rrtimeslice_t *ts1 = PG_GETARG_RRTIMESLICE_P(0);
559         rrtimeslice_t *ts2 = PG_GETARG_RRTIMESLICE_P(1);
561         PG_RETURN_INT32(rrtimeslice_cmp_internal(ts1, ts2));
562 } /* rrtimeslice_ge */
564 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */