Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce experimental time_bucket_ng() function #3211

Merged
merged 1 commit into from Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions sql/CMakeLists.txt
Expand Up @@ -37,6 +37,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
@@ -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 at
-- your own risk.
CREATE OR REPLACE FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts DATE) RETURNS DATE
AS '@MODULE_PATHNAME@', 'ts_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@', 'ts_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@', 'ts_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@', 'ts_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@', 'ts_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@', 'ts_time_bucket_ng_timestamptz' LANGUAGE C STABLE PARALLEL SAFE STRICT;
1 change: 1 addition & 0 deletions src/CMakeLists.txt
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
152 changes: 152 additions & 0 deletions src/time_bucket_ng.c
@@ -0,0 +1,152 @@
/*
* 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(ts_time_bucket_ng);
TS_FUNCTION_INFO_V1(ts_time_bucket_ng_timestamp);
TS_FUNCTION_INFO_V1(ts_time_bucket_ng_timestamptz);

TSDLLEXPORT Datum
ts_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)));
afiskon marked this conversation as resolved.
Show resolved Hide resolved

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

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

TSDLLEXPORT Datum
ts_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)));
afiskon marked this conversation as resolved.
Show resolved Hide resolved

if (PG_NARGS() > 2)
{
DateADT origin = DatumGetDateADT(DirectFunctionCall1(timestamptz_date, PG_GETARG_DATUM(2)));
result = DatumGetDateADT(DirectFunctionCall3(ts_time_bucket_ng,
afiskon marked this conversation as resolved.
Show resolved Hide resolved
interval,
DateADTGetDatum(ts_date),
DateADTGetDatum(origin)));
}
else
{
result = DatumGetDateADT(
DirectFunctionCall2(ts_time_bucket_ng, interval, DateADTGetDatum(ts_date)));
}

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

TSDLLEXPORT Datum
ts_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_INVALID_PARAMETER_VALUE),
errmsg("interval must be either days and weeks, or months and years")));
}

if ((interval->month == 0) && (interval->day == 0))
{
/*
* This will be fixed in future versions of ts_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_INVALID_PARAMETER_VALUE),
errmsg("interval must be at least one day")));
}

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_INVALID_PARAMETER_VALUE),
errmsg("origin must be the first day of the month")));
}

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_INVALID_PARAMETER_VALUE),
errmsg("origin must be before the given date")));
}

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_INVALID_PARAMETER_VALUE),
errmsg("origin must be before the given date")));
}

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
@@ -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 ts_time_bucket_ng(PG_FUNCTION_ARGS);
extern TSDLLEXPORT Datum ts_time_bucket_ng_timestamp(PG_FUNCTION_ARGS);
extern TSDLLEXPORT Datum ts_time_bucket_ng_timestamptz(PG_FUNCTION_ARGS);

#endif /* TIMESCALEDB_DATE_TRUNC_H */