mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-15 10:11:29 +08:00
Instead of using a user name to register the owner of a job, we use regrole. This allows renames to work properly since the underlying OID does not change when the owner name changes. We add a check when calling `DROP ROLE` that there is no job with that owner and generate an error if there is.
756 lines
22 KiB
C
756 lines
22 KiB
C
/*
|
|
* This file and its contents are licensed under the Timescale License.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-TIMESCALE for a copy of the license.
|
|
*/
|
|
|
|
#include <postgres.h>
|
|
#include <access/xact.h>
|
|
#include <common/int128.h>
|
|
#include <miscadmin.h>
|
|
#include <parser/parse_coerce.h>
|
|
#include <utils/acl.h>
|
|
|
|
#include <jsonb_utils.h>
|
|
#include <utils/builtins.h>
|
|
#include "bgw_policy/continuous_aggregate_api.h"
|
|
#include "bgw_policy/job.h"
|
|
#include "bgw_policy/policy_utils.h"
|
|
#include "bgw/job.h"
|
|
#include "ts_catalog/continuous_agg.h"
|
|
#include "continuous_aggs/materialize.h"
|
|
#include "dimension.h"
|
|
#include "hypertable_cache.h"
|
|
#include "time_utils.h"
|
|
#include "policy_utils.h"
|
|
#include "time_utils.h"
|
|
#include "bgw_policy/policies_v2.h"
|
|
#include "bgw/job_stat.h"
|
|
#include "bgw/timer.h"
|
|
|
|
/* Default max runtime for a continuous aggregate jobs is unlimited for now */
|
|
#define DEFAULT_MAX_RUNTIME \
|
|
DatumGetIntervalP(DirectFunctionCall3(interval_in, CStringGetDatum("0"), InvalidOid, -1))
|
|
|
|
/* infinite number of retries for continuous aggregate jobs */
|
|
#define DEFAULT_MAX_RETRIES (-1)
|
|
|
|
int32
|
|
policy_continuous_aggregate_get_mat_hypertable_id(const Jsonb *config)
|
|
{
|
|
bool found;
|
|
int32 mat_hypertable_id =
|
|
ts_jsonb_get_int32_field(config, POL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID, &found);
|
|
|
|
if (!found)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("could not find \"%s\" in config for job",
|
|
POL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID)));
|
|
|
|
return mat_hypertable_id;
|
|
}
|
|
|
|
static int64
|
|
get_time_from_interval(const Dimension *dim, Datum interval, Oid type)
|
|
{
|
|
Oid partitioning_type = ts_dimension_get_partition_type(dim);
|
|
|
|
if (IS_INTEGER_TYPE(type))
|
|
{
|
|
Oid now_func = ts_get_integer_now_func(dim);
|
|
int64 value = ts_interval_value_to_internal(interval, type);
|
|
|
|
Assert(now_func);
|
|
|
|
return ts_subtract_integer_from_now_saturating(now_func, value, partitioning_type);
|
|
}
|
|
else if (type == INTERVALOID)
|
|
{
|
|
Datum res = subtract_interval_from_now(DatumGetIntervalP(interval), partitioning_type);
|
|
return ts_time_value_to_internal(res, partitioning_type);
|
|
}
|
|
else
|
|
elog(ERROR, "unsupported offset type for continuous aggregate policy");
|
|
|
|
pg_unreachable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int64
|
|
get_time_from_config(const Dimension *dim, const Jsonb *config, const char *json_label,
|
|
bool *isnull)
|
|
{
|
|
Oid partitioning_type = ts_dimension_get_partition_type(dim);
|
|
*isnull = false;
|
|
|
|
if (IS_INTEGER_TYPE(partitioning_type))
|
|
{
|
|
bool found;
|
|
int64 interval_val = ts_jsonb_get_int64_field(config, json_label, &found);
|
|
|
|
if (!found)
|
|
{
|
|
*isnull = true;
|
|
return 0;
|
|
}
|
|
return get_time_from_interval(dim, Int64GetDatum(interval_val), INT8OID);
|
|
}
|
|
else
|
|
{
|
|
Interval *interval_val = ts_jsonb_get_interval_field(config, json_label);
|
|
|
|
if (!interval_val)
|
|
{
|
|
*isnull = true;
|
|
return 0;
|
|
}
|
|
|
|
return get_time_from_interval(dim, IntervalPGetDatum(interval_val), INTERVALOID);
|
|
}
|
|
}
|
|
|
|
int64
|
|
policy_refresh_cagg_get_refresh_start(const Dimension *dim, const Jsonb *config, bool *start_isnull)
|
|
{
|
|
int64 res = get_time_from_config(dim, config, POL_REFRESH_CONF_KEY_START_OFFSET, start_isnull);
|
|
|
|
/* interpret NULL as min value for that type */
|
|
if (*start_isnull)
|
|
return ts_time_get_min(ts_dimension_get_partition_type(dim));
|
|
return res;
|
|
}
|
|
|
|
int64
|
|
policy_refresh_cagg_get_refresh_end(const Dimension *dim, const Jsonb *config, bool *end_isnull)
|
|
{
|
|
int64 res = get_time_from_config(dim, config, POL_REFRESH_CONF_KEY_END_OFFSET, end_isnull);
|
|
|
|
if (*end_isnull)
|
|
return ts_time_get_end_or_max(ts_dimension_get_partition_type(dim));
|
|
return res;
|
|
}
|
|
|
|
/* returns false if a policy could not be found */
|
|
bool
|
|
policy_refresh_cagg_exists(int32 materialization_id)
|
|
{
|
|
Hypertable *mat_ht = ts_hypertable_get_by_id(materialization_id);
|
|
|
|
if (!mat_ht)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("configuration materialization hypertable id %d not found",
|
|
materialization_id)));
|
|
|
|
List *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,
|
|
INTERNAL_SCHEMA_NAME,
|
|
materialization_id);
|
|
if (jobs == NIL)
|
|
return false;
|
|
|
|
/* only 1 cont. aggregate policy job allowed */
|
|
Assert(list_length(jobs) == 1);
|
|
return true;
|
|
}
|
|
|
|
/* Compare passed in interval value with refresh_start parameter
|
|
* of cagg policy.
|
|
*/
|
|
bool
|
|
policy_refresh_cagg_refresh_start_lt(int32 materialization_id, Oid cmp_type, Datum cmp_interval)
|
|
{
|
|
Hypertable *mat_ht = ts_hypertable_get_by_id(materialization_id);
|
|
bool ret = false;
|
|
|
|
if (!mat_ht)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("configuration materialization hypertable id %d not found",
|
|
materialization_id)));
|
|
|
|
List *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,
|
|
INTERNAL_SCHEMA_NAME,
|
|
materialization_id);
|
|
if (jobs == NIL)
|
|
return false;
|
|
|
|
/* only 1 cont. aggregate policy job allowed */
|
|
BgwJob *cagg_job = linitial(jobs);
|
|
Jsonb *cagg_config = cagg_job->fd.config;
|
|
|
|
const Dimension *open_dim = get_open_dimension_for_hypertable(mat_ht);
|
|
Oid dim_type = ts_dimension_get_partition_type(open_dim);
|
|
if (IS_INTEGER_TYPE(dim_type))
|
|
{
|
|
bool found;
|
|
Assert(IS_INTEGER_TYPE(cmp_type));
|
|
int64 cmpval = ts_interval_value_to_internal(cmp_interval, cmp_type);
|
|
int64 refresh_start =
|
|
ts_jsonb_get_int64_field(cagg_config, POL_REFRESH_CONF_KEY_START_OFFSET, &found);
|
|
if (!found) /*this is a null value */
|
|
return false;
|
|
ret = (refresh_start < cmpval);
|
|
}
|
|
else
|
|
{
|
|
Assert(cmp_type == INTERVALOID);
|
|
Interval *refresh_start =
|
|
ts_jsonb_get_interval_field(cagg_config, POL_REFRESH_CONF_KEY_START_OFFSET);
|
|
if (refresh_start == NULL) /* NULL refresh_start */
|
|
return false;
|
|
Datum res =
|
|
DirectFunctionCall2(interval_lt, IntervalPGetDatum(refresh_start), cmp_interval);
|
|
ret = DatumGetBool(res);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Datum
|
|
policy_refresh_cagg_proc(PG_FUNCTION_ARGS)
|
|
{
|
|
if (PG_NARGS() != 2 || PG_ARGISNULL(0) || PG_ARGISNULL(1))
|
|
PG_RETURN_VOID();
|
|
|
|
TS_PREVENT_FUNC_IF_READ_ONLY();
|
|
policy_refresh_cagg_execute(PG_GETARG_INT32(0), PG_GETARG_JSONB_P(1));
|
|
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
Datum
|
|
policy_refresh_cagg_check(PG_FUNCTION_ARGS)
|
|
{
|
|
if (PG_ARGISNULL(0))
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("config must not be NULL")));
|
|
}
|
|
|
|
policy_refresh_cagg_read_and_validate_config(PG_GETARG_JSONB_P(0), NULL);
|
|
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
static void
|
|
json_add_dim_interval_value(JsonbParseState *parse_state, const char *json_label, Oid dim_type,
|
|
Datum value)
|
|
{
|
|
switch (dim_type)
|
|
{
|
|
case INTERVALOID:
|
|
ts_jsonb_add_interval(parse_state, json_label, DatumGetIntervalP(value));
|
|
break;
|
|
case INT2OID:
|
|
ts_jsonb_add_int64(parse_state, json_label, DatumGetInt16(value));
|
|
break;
|
|
case INT4OID:
|
|
ts_jsonb_add_int64(parse_state, json_label, DatumGetInt32(value));
|
|
break;
|
|
case INT8OID:
|
|
ts_jsonb_add_int64(parse_state, json_label, DatumGetInt64(value));
|
|
break;
|
|
default:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("unsupported interval argument type, expected type : %s",
|
|
format_type_be(dim_type))));
|
|
}
|
|
}
|
|
|
|
static Datum
|
|
convert_interval_arg(Oid dim_type, Datum interval, Oid *interval_type, const char *str_msg)
|
|
{
|
|
Oid convert_to = dim_type;
|
|
Datum converted;
|
|
|
|
if (IS_TIMESTAMP_TYPE(dim_type))
|
|
convert_to = INTERVALOID;
|
|
|
|
if (*interval_type != convert_to)
|
|
{
|
|
if (!can_coerce_type(1, interval_type, &convert_to, COERCION_IMPLICIT))
|
|
{
|
|
if (IS_INTEGER_TYPE(dim_type))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid parameter value for %s", str_msg),
|
|
errhint("Use time interval of type %s with the continuous aggregate.",
|
|
format_type_be(dim_type))));
|
|
else if (IS_TIMESTAMP_TYPE(dim_type))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid parameter value for %s", str_msg),
|
|
errhint("Use time interval with a continuous aggregate using "
|
|
"timestamp-based time "
|
|
"bucket.")));
|
|
}
|
|
}
|
|
|
|
converted = ts_time_datum_convert_arg(interval, interval_type, convert_to);
|
|
|
|
/* For integer types, first convert all types to int64 to get on a common
|
|
* type. Then check valid time ranges against the partition/dimension
|
|
* type */
|
|
switch (*interval_type)
|
|
{
|
|
case INT2OID:
|
|
converted = Int64GetDatum((int64) DatumGetInt16(converted));
|
|
break;
|
|
case INT4OID:
|
|
converted = Int64GetDatum((int64) DatumGetInt32(converted));
|
|
break;
|
|
case INT8OID:
|
|
break;
|
|
case INTERVALOID:
|
|
/* For timestamp types, we only support Interval, so nothing further
|
|
* to do. */
|
|
return converted;
|
|
default:
|
|
pg_unreachable();
|
|
break;
|
|
}
|
|
|
|
/* Cap at min and max */
|
|
if (DatumGetInt64(converted) < ts_time_get_min(dim_type))
|
|
converted = ts_time_get_min(dim_type);
|
|
else if (DatumGetInt64(converted) > ts_time_get_max(dim_type))
|
|
converted = ts_time_get_max(dim_type);
|
|
|
|
/* Convert to the desired integer type */
|
|
switch (dim_type)
|
|
{
|
|
case INT2OID:
|
|
converted = Int16GetDatum((int16) DatumGetInt64(converted));
|
|
break;
|
|
case INT4OID:
|
|
converted = Int32GetDatum((int32) DatumGetInt64(converted));
|
|
break;
|
|
case INT8OID:
|
|
/* Already int64, so nothing to do. */
|
|
break;
|
|
default:
|
|
pg_unreachable();
|
|
break;
|
|
}
|
|
|
|
*interval_type = dim_type;
|
|
|
|
return converted;
|
|
}
|
|
|
|
/*
|
|
* Convert an interval to a 128 integer value.
|
|
*
|
|
* Based on PostgreSQL's interval_cmp_value().
|
|
*/
|
|
static inline INT128
|
|
interval_to_int128(const Interval *interval)
|
|
{
|
|
INT128 span;
|
|
int64 dayfraction;
|
|
int64 days;
|
|
|
|
/*
|
|
* Separate time field into days and dayfraction, then add the month and
|
|
* day fields to the days part. We cannot overflow int64 days here.
|
|
*/
|
|
dayfraction = interval->time % USECS_PER_DAY;
|
|
days = interval->time / USECS_PER_DAY;
|
|
days += interval->month * INT64CONST(30);
|
|
days += interval->day;
|
|
|
|
/* Widen dayfraction to 128 bits */
|
|
span = int64_to_int128(dayfraction);
|
|
|
|
/* Scale up days to microseconds, forming a 128-bit product */
|
|
int128_add_int64_mul_int64(&span, days, USECS_PER_DAY);
|
|
|
|
return span;
|
|
}
|
|
|
|
int64
|
|
interval_to_int64(Datum interval, Oid type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case INT2OID:
|
|
return DatumGetInt16(interval);
|
|
case INT4OID:
|
|
return DatumGetInt32(interval);
|
|
case INT8OID:
|
|
return DatumGetInt64(interval);
|
|
case INTERVALOID:
|
|
{
|
|
const int64 max = ts_time_get_max(TIMESTAMPTZOID);
|
|
const int64 min = ts_time_get_min(TIMESTAMPTZOID);
|
|
INT128 bigres = interval_to_int128(DatumGetIntervalP(interval));
|
|
|
|
if (int128_compare(bigres, int64_to_int128(max)) >= 0)
|
|
return max;
|
|
else if (int128_compare(bigres, int64_to_int128(min)) <= 0)
|
|
return min;
|
|
else
|
|
return int128_to_int64(bigres);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
pg_unreachable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Enforce that a policy has a refresh window of at least two buckets to
|
|
* ensure we materialize at least one bucket each run.
|
|
*
|
|
* Why two buckets? Note that the policy probably won't execute at at time
|
|
* that exactly aligns with a bucket boundary, so a window of one bucket
|
|
* might not cover a full bucket that we want to materialize:
|
|
*
|
|
* Refresh window: [-----)
|
|
* Materialized buckets: |-----|-----|-----|
|
|
*/
|
|
static void
|
|
validate_window_size(const ContinuousAgg *cagg, const CaggPolicyConfig *config)
|
|
{
|
|
int64 start_offset;
|
|
int64 end_offset;
|
|
int64 bucket_width;
|
|
|
|
if (config->offset_start.isnull)
|
|
start_offset = ts_time_get_max(cagg->partition_type);
|
|
else
|
|
start_offset = interval_to_int64(config->offset_start.value, config->offset_start.type);
|
|
|
|
if (config->offset_end.isnull)
|
|
end_offset = ts_time_get_min(cagg->partition_type);
|
|
else
|
|
end_offset = interval_to_int64(config->offset_end.value, config->offset_end.type);
|
|
|
|
if (ts_continuous_agg_bucket_width_variable(cagg))
|
|
{
|
|
/*
|
|
* There are several cases of variable-sized buckets:
|
|
* 1. Monthly buckets
|
|
* 2. Buckets with timezones
|
|
* 3. Cases 1 and 2 at the same time
|
|
*
|
|
* For months we simply take 31 days as the worst case scenario and
|
|
* multiply this number by the number of months in the bucket. This
|
|
* reduces the task to days/hours/minutes scenario.
|
|
*
|
|
* Days/hours/minutes case is handled the same way as for fixed-sized
|
|
* buckets. The refresh window at least two buckets in size is adequate
|
|
* for such corner cases as DST.
|
|
*/
|
|
|
|
/* bucket_function should always be specified for variable-sized buckets */
|
|
Assert(cagg->bucket_function != NULL);
|
|
/* ... and bucket_function->bucket_width too */
|
|
Assert(cagg->bucket_function->bucket_width != NULL);
|
|
|
|
/* Make a temporary copy of bucket_width */
|
|
Interval interval = *cagg->bucket_function->bucket_width;
|
|
interval.day += 31 * interval.month;
|
|
interval.month = 0;
|
|
bucket_width = ts_interval_value_to_internal(IntervalPGetDatum(&interval), INTERVALOID);
|
|
}
|
|
else
|
|
{
|
|
bucket_width = ts_continuous_agg_bucket_width(cagg);
|
|
}
|
|
|
|
if (ts_time_saturating_add(end_offset, bucket_width * 2, INT8OID) > start_offset)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("policy refresh window too small"),
|
|
errdetail("The start and end offsets must cover at least"
|
|
" two buckets in the valid time range of type \"%s\".",
|
|
format_type_be(cagg->partition_type))));
|
|
}
|
|
|
|
static void
|
|
parse_offset_arg(const ContinuousAgg *cagg, Oid offset_type, NullableDatum arg,
|
|
CaggPolicyOffset *offset)
|
|
{
|
|
offset->isnull = arg.isnull;
|
|
|
|
if (!offset->isnull)
|
|
{
|
|
offset->value =
|
|
convert_interval_arg(cagg->partition_type, arg.value, &offset_type, offset->name);
|
|
offset->type = offset_type;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_cagg_policy_config(const ContinuousAgg *cagg, Oid start_offset_type,
|
|
NullableDatum start_offset, Oid end_offset_type, NullableDatum end_offset,
|
|
CaggPolicyConfig *config)
|
|
{
|
|
MemSet(config, 0, sizeof(CaggPolicyConfig));
|
|
config->partition_type = cagg->partition_type;
|
|
/* This might seem backwards, but since we are dealing with offsets, start
|
|
* actually translates to max and end to min for maximum window. */
|
|
config->offset_start.value = ts_time_datum_get_max(config->partition_type);
|
|
config->offset_end.value = ts_time_datum_get_min(config->partition_type);
|
|
config->offset_start.type = config->offset_end.type =
|
|
IS_TIMESTAMP_TYPE(cagg->partition_type) ? INTERVALOID : cagg->partition_type;
|
|
config->offset_start.name = POL_REFRESH_CONF_KEY_START_OFFSET;
|
|
config->offset_end.name = POL_REFRESH_CONF_KEY_END_OFFSET;
|
|
parse_offset_arg(cagg, start_offset_type, start_offset, &config->offset_start);
|
|
parse_offset_arg(cagg, end_offset_type, end_offset, &config->offset_end);
|
|
|
|
Assert(config->offset_start.type == config->offset_end.type);
|
|
validate_window_size(cagg, config);
|
|
}
|
|
|
|
Datum
|
|
policy_refresh_cagg_add_internal(Oid cagg_oid, Oid start_offset_type, NullableDatum start_offset,
|
|
Oid end_offset_type, NullableDatum end_offset,
|
|
Interval refresh_interval, bool if_not_exists, bool fixed_schedule,
|
|
TimestampTz initial_start, const char *timezone)
|
|
{
|
|
NameData application_name;
|
|
NameData proc_name, proc_schema, check_name, check_schema, owner;
|
|
ContinuousAgg *cagg;
|
|
CaggPolicyConfig policyconf;
|
|
int32 job_id;
|
|
Oid owner_id;
|
|
List *jobs;
|
|
JsonbParseState *parse_state = NULL;
|
|
|
|
/* Verify that the owner can create a background worker */
|
|
owner_id = ts_cagg_permissions_check(cagg_oid, GetUserId());
|
|
ts_bgw_job_validate_job_owner(owner_id);
|
|
|
|
cagg = ts_continuous_agg_find_by_relid(cagg_oid);
|
|
if (!cagg)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("\"%s\" is not a continuous aggregate", get_rel_name(cagg_oid))));
|
|
|
|
if (!start_offset.isnull)
|
|
start_offset.isnull =
|
|
ts_if_offset_is_infinity(start_offset.value, start_offset_type, true /* is_start */);
|
|
if (!end_offset.isnull)
|
|
end_offset.isnull =
|
|
ts_if_offset_is_infinity(end_offset.value, end_offset_type, false /* is_start */);
|
|
|
|
parse_cagg_policy_config(cagg,
|
|
start_offset_type,
|
|
start_offset,
|
|
end_offset_type,
|
|
end_offset,
|
|
&policyconf);
|
|
|
|
/* Make sure there is only 1 refresh policy on the cagg */
|
|
jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,
|
|
INTERNAL_SCHEMA_NAME,
|
|
cagg->data.mat_hypertable_id);
|
|
|
|
if (jobs != NIL)
|
|
{
|
|
Assert(list_length(jobs) == 1);
|
|
if (!if_not_exists)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("continuous aggregate policy already exists for \"%s\"",
|
|
get_rel_name(cagg_oid)),
|
|
errdetail("Only one continuous aggregate policy can be created per continuous "
|
|
"aggregate and a policy with job id %d already exists for \"%s\".",
|
|
((BgwJob *) linitial(jobs))->fd.id,
|
|
get_rel_name(cagg_oid))));
|
|
BgwJob *existing = linitial(jobs);
|
|
|
|
if (policy_config_check_hypertable_lag_equality(existing->fd.config,
|
|
POL_REFRESH_CONF_KEY_START_OFFSET,
|
|
cagg->partition_type,
|
|
policyconf.offset_start.type,
|
|
policyconf.offset_start.value) &&
|
|
policy_config_check_hypertable_lag_equality(existing->fd.config,
|
|
POL_REFRESH_CONF_KEY_END_OFFSET,
|
|
cagg->partition_type,
|
|
policyconf.offset_end.type,
|
|
policyconf.offset_end.value))
|
|
{
|
|
/* If all arguments are the same, do nothing */
|
|
ereport(NOTICE,
|
|
(errmsg("continuous aggregate policy already exists for \"%s\", "
|
|
"skipping",
|
|
get_rel_name(cagg_oid))));
|
|
PG_RETURN_INT32(-1);
|
|
}
|
|
else
|
|
{
|
|
ereport(WARNING,
|
|
(errmsg("continuous aggregate policy already exists for \"%s\"",
|
|
get_rel_name(cagg_oid)),
|
|
errdetail("A policy already exists with different arguments."),
|
|
errhint("Remove the existing policy before adding a new one.")));
|
|
PG_RETURN_INT32(-1);
|
|
}
|
|
}
|
|
|
|
/* Next, insert a new job into jobs table */
|
|
namestrcpy(&application_name, "Refresh Continuous Aggregate Policy");
|
|
namestrcpy(&proc_name, POLICY_REFRESH_CAGG_PROC_NAME);
|
|
namestrcpy(&proc_schema, INTERNAL_SCHEMA_NAME);
|
|
namestrcpy(&check_name, POLICY_REFRESH_CAGG_CHECK_NAME);
|
|
namestrcpy(&check_schema, INTERNAL_SCHEMA_NAME);
|
|
namestrcpy(&owner, GetUserNameFromId(owner_id, false));
|
|
|
|
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
|
|
ts_jsonb_add_int32(parse_state,
|
|
POL_REFRESH_CONF_KEY_MAT_HYPERTABLE_ID,
|
|
cagg->data.mat_hypertable_id);
|
|
if (!policyconf.offset_start.isnull)
|
|
json_add_dim_interval_value(parse_state,
|
|
POL_REFRESH_CONF_KEY_START_OFFSET,
|
|
policyconf.offset_start.type,
|
|
policyconf.offset_start.value);
|
|
else
|
|
ts_jsonb_add_null(parse_state, POL_REFRESH_CONF_KEY_START_OFFSET);
|
|
if (!policyconf.offset_end.isnull)
|
|
json_add_dim_interval_value(parse_state,
|
|
POL_REFRESH_CONF_KEY_END_OFFSET,
|
|
policyconf.offset_end.type,
|
|
policyconf.offset_end.value);
|
|
else
|
|
ts_jsonb_add_null(parse_state, POL_REFRESH_CONF_KEY_END_OFFSET);
|
|
JsonbValue *result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
|
|
Jsonb *config = JsonbValueToJsonb(result);
|
|
|
|
job_id = ts_bgw_job_insert_relation(&application_name,
|
|
&refresh_interval,
|
|
DEFAULT_MAX_RUNTIME,
|
|
DEFAULT_MAX_RETRIES,
|
|
&refresh_interval,
|
|
&proc_schema,
|
|
&proc_name,
|
|
&check_schema,
|
|
&check_name,
|
|
owner_id,
|
|
true,
|
|
fixed_schedule,
|
|
cagg->data.mat_hypertable_id,
|
|
config,
|
|
initial_start,
|
|
timezone);
|
|
|
|
PG_RETURN_INT32(job_id);
|
|
}
|
|
|
|
Datum
|
|
policy_refresh_cagg_add(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid cagg_oid, start_offset_type, end_offset_type;
|
|
Interval refresh_interval;
|
|
bool if_not_exists;
|
|
NullableDatum start_offset, end_offset;
|
|
|
|
cagg_oid = PG_GETARG_OID(0);
|
|
|
|
if (PG_ARGISNULL(3))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("cannot use NULL refresh_schedule_interval")));
|
|
|
|
start_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
|
|
start_offset.value = PG_GETARG_DATUM(1);
|
|
start_offset.isnull = PG_ARGISNULL(1);
|
|
end_offset_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
|
|
end_offset.value = PG_GETARG_DATUM(2);
|
|
end_offset.isnull = PG_ARGISNULL(2);
|
|
refresh_interval = *PG_GETARG_INTERVAL_P(3);
|
|
if_not_exists = PG_GETARG_BOOL(4);
|
|
TimestampTz initial_start = PG_ARGISNULL(5) ? DT_NOBEGIN : PG_GETARG_TIMESTAMPTZ(5);
|
|
bool fixed_schedule = !PG_ARGISNULL(5);
|
|
text *timezone = PG_ARGISNULL(6) ? NULL : PG_GETARG_TEXT_PP(6);
|
|
char *valid_timezone = NULL;
|
|
|
|
Datum retval;
|
|
/* if users pass in -infinity for initial_start, then use the current_timestamp instead */
|
|
if (fixed_schedule)
|
|
{
|
|
ts_bgw_job_validate_schedule_interval(&refresh_interval);
|
|
if (TIMESTAMP_NOT_FINITE(initial_start))
|
|
initial_start = ts_timer_get_current_timestamp();
|
|
}
|
|
|
|
if (timezone != NULL)
|
|
valid_timezone = ts_bgw_job_validate_timezone(PG_GETARG_DATUM(6));
|
|
|
|
retval = policy_refresh_cagg_add_internal(cagg_oid,
|
|
start_offset_type,
|
|
start_offset,
|
|
end_offset_type,
|
|
end_offset,
|
|
refresh_interval,
|
|
if_not_exists,
|
|
fixed_schedule,
|
|
initial_start,
|
|
valid_timezone);
|
|
if (!TIMESTAMP_NOT_FINITE(initial_start))
|
|
{
|
|
int32 job_id = DatumGetInt32(retval);
|
|
ts_bgw_job_stat_upsert_next_start(job_id, initial_start);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
Datum
|
|
policy_refresh_cagg_remove_internal(Oid cagg_oid, bool if_exists)
|
|
{
|
|
int32 mat_htid;
|
|
|
|
ContinuousAgg *cagg = ts_continuous_agg_find_by_relid(cagg_oid);
|
|
|
|
if (!cagg)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("\"%s\" is not a continuous aggregate", get_rel_name(cagg_oid))));
|
|
|
|
ts_cagg_permissions_check(cagg_oid, GetUserId());
|
|
mat_htid = cagg->data.mat_hypertable_id;
|
|
List *jobs = ts_bgw_job_find_by_proc_and_hypertable_id(POLICY_REFRESH_CAGG_PROC_NAME,
|
|
INTERNAL_SCHEMA_NAME,
|
|
mat_htid);
|
|
if (jobs == NIL)
|
|
{
|
|
if (!if_exists)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
(errmsg("continuous aggregate policy not found for \"%s\"",
|
|
get_rel_name(cagg_oid)))));
|
|
else
|
|
{
|
|
ereport(NOTICE,
|
|
(errmsg("continuous aggregate policy not found for \"%s\", skipping",
|
|
get_rel_name(cagg_oid))));
|
|
PG_RETURN_BOOL(false);
|
|
}
|
|
}
|
|
Assert(list_length(jobs) == 1);
|
|
BgwJob *job = linitial(jobs);
|
|
|
|
ts_bgw_job_delete_by_id(job->fd.id);
|
|
PG_RETURN_BOOL(true);
|
|
}
|
|
|
|
Datum
|
|
policy_refresh_cagg_remove(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid cagg_oid = PG_GETARG_OID(0);
|
|
bool if_not_exists = PG_GETARG_BOOL(1); /* Deprecating this argument */
|
|
bool if_exists;
|
|
|
|
/* For backward compatibility, we use IF_NOT_EXISTS when IF_EXISTS is not given */
|
|
if_exists = PG_ARGISNULL(2) ? if_not_exists : PG_GETARG_BOOL(2);
|
|
(void) policy_refresh_cagg_remove_internal(cagg_oid, if_exists);
|
|
PG_RETURN_VOID();
|
|
}
|