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)
134 {
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)
164 {
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)
224 {
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)
256 {
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)
327 {
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)
387 {
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)
416 {
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)
444 {
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)
475 {
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)
503 {
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)
512 {
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)
521 {
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)
530 {
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)
539 {
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)
548 {
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)
557 {
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 : */