Skip to content

Commit

Permalink
Introduce experimental time_bucket_ng() function
Browse files Browse the repository at this point in the history
This patch adds time_bucket_ng() function to the experimental
schema. The "ng" part stands for "next generation". Unlike
current time_bucket() implementation the _ng version will support
months, years and timezones.

Current implementation doesn't claim to be complete. For instance,
it doesn't support timezones yet. The reasons to commit it in it's
current state are 1) to shorten the feedback loop 2) to start
experimenting with monthly buckets are soon as possible and
3) to reduce the unnecessary work of rebasing and resolving
conflicts.
  • Loading branch information
Aleksander Alekseev authored and afiskon committed Jun 16, 2021
1 parent 38e6c0a commit b255456
Show file tree
Hide file tree
Showing 7 changed files with 476 additions and 0 deletions.
1 change: 1 addition & 0 deletions sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ set(SOURCE_FILES
ddl_triggers.sql
bookend.sql
time_bucket.sql
time_bucket_ng.sql
version.sql
size_utils.sql
histogram.sql
Expand Down
29 changes: 29 additions & 0 deletions sql/time_bucket_ng.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.

-- time_bucket_ng() is an _experimental_ new version of time_bucket().
--
-- Unlike time_bucket(), time_bucket_ng() supports variable-sized buckets,
-- such as months and years, and also timezones. Note that the behavior
-- and the interface of this function are subjects to change. There could
-- be bugs, and the implementation doesn't claim to be complete. Use on
-- your own risk.
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE) RETURNS DATE
AS '@MODULE_PATHNAME@', 'time_bucket_ng' LANGUAGE C STABLE PARALLEL SAFE STRICT;

CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE, origin DATE) RETURNS DATE
AS '@MODULE_PATHNAME@', 'time_bucket_ng' LANGUAGE C STABLE PARALLEL SAFE STRICT;

-- utility functions
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMP) RETURNS TIMESTAMP
AS '@MODULE_PATHNAME@', 'time_bucket_ng_timestamp' LANGUAGE C STABLE PARALLEL SAFE STRICT;

CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMP, origin TIMESTAMP) RETURNS TIMESTAMP
AS '@MODULE_PATHNAME@', 'time_bucket_ng_timestamp' LANGUAGE C STABLE PARALLEL SAFE STRICT;

CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ) RETURNS TIMESTAMPTZ
AS '@MODULE_PATHNAME@', 'time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;

CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ) RETURNS TIMESTAMPTZ
AS '@MODULE_PATHNAME@', 'time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ set(SOURCES
subspace_store.c
tablespace.c
time_bucket.c
time_bucket_ng.c
time_utils.c
custom_type_cache.c
trigger.c
Expand Down
154 changes: 154 additions & 0 deletions src/time_bucket_ng.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* This file and its contents are licensed under the Apache License 2.0.
* Please see the included NOTICE for copyright information and
* LICENSE-APACHE for a copy of the license.
*/
#include <postgres.h>
#include <utils/date.h>
#include <utils/datetime.h>
#include <utils/fmgrprotos.h>

#include "time_bucket_ng.h"

TS_FUNCTION_INFO_V1(time_bucket_ng);
TS_FUNCTION_INFO_V1(time_bucket_ng_timestamp);
TS_FUNCTION_INFO_V1(time_bucket_ng_timestamptz);

TSDLLEXPORT Datum
time_bucket_ng_timestamp(PG_FUNCTION_ARGS)
{
DateADT result;
Datum interval = PG_GETARG_DATUM(0);
DateADT ts_date = DatumGetDateADT(DirectFunctionCall1(timestamp_date, PG_GETARG_DATUM(1)));

if (PG_NARGS() > 2)
{
DateADT origin = DatumGetDateADT(DirectFunctionCall1(timestamp_date, PG_GETARG_DATUM(2)));
result = DatumGetDateADT(DirectFunctionCall3(time_bucket_ng,
interval,
DateADTGetDatum(ts_date),
DateADTGetDatum(origin)));
}
else
{
result = DatumGetDateADT(
DirectFunctionCall2(time_bucket_ng, interval, DateADTGetDatum(ts_date)));
}

return DirectFunctionCall1(date_timestamp, DateADTGetDatum(result));
}

TSDLLEXPORT Datum
time_bucket_ng_timestamptz(PG_FUNCTION_ARGS)
{
DateADT result;
Datum interval = PG_GETARG_DATUM(0);
DateADT ts_date = DatumGetDateADT(DirectFunctionCall1(timestamptz_date, PG_GETARG_DATUM(1)));

if (PG_NARGS() > 2)
{
DateADT origin = DatumGetDateADT(DirectFunctionCall1(timestamptz_date, PG_GETARG_DATUM(2)));
result = DatumGetDateADT(DirectFunctionCall3(time_bucket_ng,
interval,
DateADTGetDatum(ts_date),
DateADTGetDatum(origin)));
}
else
{
result = DatumGetDateADT(
DirectFunctionCall2(time_bucket_ng, interval, DateADTGetDatum(ts_date)));
}

return DirectFunctionCall1(date_timestamptz, DateADTGetDatum(result));
}

TSDLLEXPORT Datum
time_bucket_ng(PG_FUNCTION_ARGS)
{
Interval *interval = PG_GETARG_INTERVAL_P(0);
DateADT date = PG_GETARG_DATEADT(1);
DateADT origin_date = 0; // 2000-01-01
int origin_year = 2000, origin_month = 1, origin_day = 1;
int year, month, day;
int delta, bucket_number;

if ((interval->time != 0) || ((interval->month != 0) && (interval->day != 0)))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported interval: use either days and weeks, or months and years")));
}

if ((interval->month == 0) && (interval->day == 0))
{
/*
* This will be fixed in furhter versions of time_bucket_ng().
* The reason why it's not yet implemented is that we want to start
* experimenting with variable-sized buckets as soon as possible.
* We know that fixed-sized buckets work OK and adding corresponding
* logic will be trivial.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported interval: at least one day expected")));
}

if (PG_NARGS() > 2)
{
origin_date = PG_GETARG_DATUM(2);
j2date(origin_date + POSTGRES_EPOCH_JDATE, &origin_year, &origin_month, &origin_day);
}

if ((origin_day != 1) && (interval->month != 0))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Invalid 'origin' day, 1st day of the month expected")));
}

if (DATE_NOT_FINITE(date))
PG_RETURN_DATEADT(date);

if (interval->month != 0)
{
/* Handle months and years */

j2date(date + POSTGRES_EPOCH_JDATE, &year, &month, &day);

if ((year < origin_year) || ((year == origin_year) && (month < origin_month)))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("date is before origin, please choose an origin that is before all "
"dates")));
}

delta = (year * 12 + month) - (origin_year * 12 + origin_month);
bucket_number = delta / interval->month;
year = origin_year + (bucket_number * interval->month) / 12;
month =
(((origin_year * 12 + (origin_month - 1)) + (bucket_number * interval->month)) % 12) +
1;
day = 1;

date = date2j(year, month, day) - POSTGRES_EPOCH_JDATE;
}
else
{
/* Handle days and weeks */

if (date < origin_date)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("date is before origin, please choose an origin that is before all "
"dates")));
}

delta = date - origin_date;
bucket_number = delta / interval->day;
date = bucket_number * interval->day;
}

PG_RETURN_DATEADT(date);
}
18 changes: 18 additions & 0 deletions src/time_bucket_ng.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* This file and its contents are licensed under the Apache License 2.0.
* Please see the included NOTICE for copyright information and
* LICENSE-APACHE for a copy of the license.
*/
#ifndef TIMESCALEDB_DATE_TRUNC_H
#define TIMESCALEDB_DATE_TRUNC_H

#include <postgres.h>
#include <fmgr.h>

#include "export.h"

extern TSDLLEXPORT Datum time_bucket_ng(PG_FUNCTION_ARGS);
extern TSDLLEXPORT Datum time_bucket_ng_timestamp(PG_FUNCTION_ARGS);
extern TSDLLEXPORT Datum time_bucket_ng_timestamptz(PG_FUNCTION_ARGS);

#endif /* TIMESCALEDB_DATE_TRUNC_H */

0 comments on commit b255456

Please sign in to comment.