mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-23 22:41:34 +08:00
PG15 removes T_Expr nodeTag because it's an abstract type. https://github.com/postgres/postgres/commit/85399291
1273 lines
33 KiB
C
1273 lines
33 KiB
C
/*
|
|
* 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 <access/genam.h>
|
|
#include <access/heapam.h>
|
|
#include <access/htup_details.h>
|
|
#include <access/htup.h>
|
|
#include <access/reloptions.h>
|
|
#include <access/xact.h>
|
|
#include <catalog/indexing.h>
|
|
#include <catalog/namespace.h>
|
|
#include <catalog/pg_cast.h>
|
|
#include <catalog/pg_inherits.h>
|
|
#include <catalog/pg_operator.h>
|
|
#include <catalog/pg_type.h>
|
|
#include <commands/event_trigger.h>
|
|
#include <commands/tablecmds.h>
|
|
#include <fmgr.h>
|
|
#include <funcapi.h>
|
|
#include <nodes/makefuncs.h>
|
|
#include <parser/parse_coerce.h>
|
|
#include <parser/parse_func.h>
|
|
#include <parser/scansup.h>
|
|
#include <utils/acl.h>
|
|
#include <utils/builtins.h>
|
|
#include <utils/catcache.h>
|
|
#include <utils/date.h>
|
|
#include <utils/fmgroids.h>
|
|
#include <utils/fmgrprotos.h>
|
|
#include <utils/lsyscache.h>
|
|
#include <utils/relcache.h>
|
|
#include <utils/syscache.h>
|
|
|
|
#include "compat/compat.h"
|
|
#include "chunk.h"
|
|
#include "guc.h"
|
|
#include "hypertable_cache.h"
|
|
#include "utils.h"
|
|
#include "time_utils.h"
|
|
|
|
TS_FUNCTION_INFO_V1(ts_pg_timestamp_to_unix_microseconds);
|
|
|
|
/*
|
|
* Convert a Postgres TIMESTAMP to BIGINT microseconds relative the UNIX epoch.
|
|
*/
|
|
Datum
|
|
ts_pg_timestamp_to_unix_microseconds(PG_FUNCTION_ARGS)
|
|
{
|
|
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
|
|
|
|
if (TIMESTAMP_IS_NOBEGIN(timestamp))
|
|
PG_RETURN_INT64(TS_TIME_NOBEGIN);
|
|
|
|
if (TIMESTAMP_IS_NOEND(timestamp))
|
|
PG_RETURN_INT64(TS_TIME_NOEND);
|
|
|
|
if (timestamp < TS_TIMESTAMP_MIN)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range")));
|
|
|
|
if (timestamp >= TS_TIMESTAMP_END)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range")));
|
|
|
|
PG_RETURN_INT64(timestamp + TS_EPOCH_DIFF_MICROSECONDS);
|
|
}
|
|
|
|
TS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_timestamp);
|
|
TS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_timestamp_without_timezone);
|
|
TS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_date);
|
|
|
|
/*
|
|
* Convert BIGINT microseconds relative the UNIX epoch to a Postgres TIMESTAMP.
|
|
*/
|
|
Datum
|
|
ts_pg_unix_microseconds_to_timestamp(PG_FUNCTION_ARGS)
|
|
{
|
|
int64 microseconds = PG_GETARG_INT64(0);
|
|
|
|
if (TS_TIME_IS_NOBEGIN(microseconds, TIMESTAMPTZOID))
|
|
PG_RETURN_DATUM(ts_time_datum_get_nobegin(TIMESTAMPTZOID));
|
|
|
|
if (TS_TIME_IS_NOEND(microseconds, TIMESTAMPTZOID))
|
|
PG_RETURN_DATUM(ts_time_datum_get_noend(TIMESTAMPTZOID));
|
|
|
|
/*
|
|
* Test that the UNIX us timestamp is within bounds. Note that an int64 at
|
|
* UNIX epoch and microsecond precision cannot represent the upper limit
|
|
* of the supported date range (Julian end date), so INT64_MAX-1 is the
|
|
* natural upper bound for this function.
|
|
*/
|
|
if (microseconds < TS_TIMESTAMP_INTERNAL_MIN)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range")));
|
|
|
|
PG_RETURN_TIMESTAMPTZ(microseconds - TS_EPOCH_DIFF_MICROSECONDS);
|
|
}
|
|
|
|
Datum
|
|
ts_pg_unix_microseconds_to_date(PG_FUNCTION_ARGS)
|
|
{
|
|
int64 microseconds = PG_GETARG_INT64(0);
|
|
Datum res;
|
|
|
|
if (TS_TIME_IS_NOBEGIN(microseconds, DATEOID))
|
|
PG_RETURN_DATUM(ts_time_datum_get_nobegin(DATEOID));
|
|
|
|
if (TS_TIME_IS_NOEND(microseconds, DATEOID))
|
|
PG_RETURN_DATUM(ts_time_datum_get_noend(DATEOID));
|
|
|
|
res = DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(microseconds));
|
|
res = DirectFunctionCall1(timestamp_date, res);
|
|
PG_RETURN_DATUM(res);
|
|
}
|
|
|
|
static int64 ts_integer_to_internal(Datum time_val, Oid type_oid);
|
|
|
|
/* Convert valid timescale time column type to internal representation */
|
|
TSDLLEXPORT int64
|
|
ts_time_value_to_internal(Datum time_val, Oid type_oid)
|
|
{
|
|
Datum res, tz;
|
|
|
|
/* Handle custom time types. We currently only support binary coercible
|
|
* types */
|
|
if (!IS_VALID_TIME_TYPE(type_oid))
|
|
{
|
|
if (ts_type_is_int8_binary_compatible(type_oid))
|
|
return DatumGetInt64(time_val);
|
|
|
|
elog(ERROR, "unknown time type \"%s\"", format_type_be(type_oid));
|
|
}
|
|
|
|
if (IS_INTEGER_TYPE(type_oid))
|
|
{
|
|
/* Integer time types have no distinction between min, max and
|
|
* infinity. We don't want min and max to be turned into infinity for
|
|
* these types so check for those values first. */
|
|
if (TS_TIME_DATUM_IS_MIN(time_val, type_oid))
|
|
return ts_time_get_min(type_oid);
|
|
|
|
if (TS_TIME_DATUM_IS_MAX(time_val, type_oid))
|
|
return ts_time_get_max(type_oid);
|
|
}
|
|
|
|
if (TS_TIME_DATUM_IS_NOBEGIN(time_val, type_oid))
|
|
return ts_time_get_nobegin(type_oid);
|
|
|
|
if (TS_TIME_DATUM_IS_NOEND(time_val, type_oid))
|
|
return ts_time_get_noend(type_oid);
|
|
|
|
switch (type_oid)
|
|
{
|
|
case INT8OID:
|
|
case INT4OID:
|
|
case INT2OID:
|
|
return ts_integer_to_internal(time_val, type_oid);
|
|
case TIMESTAMPOID:
|
|
/*
|
|
* for timestamps, ignore timezones, make believe the timestamp is
|
|
* at UTC
|
|
*/
|
|
res = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, time_val);
|
|
|
|
return DatumGetInt64(res);
|
|
case TIMESTAMPTZOID:
|
|
res = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, time_val);
|
|
|
|
return DatumGetInt64(res);
|
|
case DATEOID:
|
|
tz = DirectFunctionCall1(date_timestamp, time_val);
|
|
res = DirectFunctionCall1(ts_pg_timestamp_to_unix_microseconds, tz);
|
|
|
|
return DatumGetInt64(res);
|
|
default:
|
|
elog(ERROR, "unknown time type \"%s\"", format_type_be(type_oid));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
TSDLLEXPORT int64
|
|
ts_interval_value_to_internal(Datum time_val, Oid type_oid)
|
|
{
|
|
switch (type_oid)
|
|
{
|
|
case INT8OID:
|
|
case INT4OID:
|
|
case INT2OID:
|
|
return ts_integer_to_internal(time_val, type_oid);
|
|
case INTERVALOID:
|
|
{
|
|
Interval *interval = DatumGetIntervalP(time_val);
|
|
if (interval->month != 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("months and years not supported"),
|
|
errdetail("An interval must be defined as a fixed duration (such as "
|
|
"weeks, days, hours, minutes, seconds, etc.).")));
|
|
return interval->time + (interval->day * USECS_PER_DAY);
|
|
}
|
|
default:
|
|
elog(ERROR, "unknown interval type \"%s\"", format_type_be(type_oid));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int64
|
|
ts_integer_to_internal(Datum time_val, Oid type_oid)
|
|
{
|
|
switch (type_oid)
|
|
{
|
|
case INT8OID:
|
|
return DatumGetInt64(time_val);
|
|
case INT4OID:
|
|
return (int64) DatumGetInt32(time_val);
|
|
case INT2OID:
|
|
return (int64) DatumGetInt16(time_val);
|
|
default:
|
|
elog(ERROR, "unknown interval type \"%s\"", format_type_be(type_oid));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int64
|
|
ts_time_value_to_internal_or_infinite(Datum time_val, Oid type_oid,
|
|
TimevalInfinity *is_infinite_out)
|
|
{
|
|
switch (type_oid)
|
|
{
|
|
case TIMESTAMPOID:
|
|
{
|
|
Timestamp ts = DatumGetTimestamp(time_val);
|
|
if (TIMESTAMP_NOT_FINITE(ts))
|
|
{
|
|
if (TIMESTAMP_IS_NOBEGIN(ts))
|
|
{
|
|
if (is_infinite_out != NULL)
|
|
*is_infinite_out = TimevalNegInfinity;
|
|
return PG_INT64_MIN;
|
|
}
|
|
else
|
|
{
|
|
if (is_infinite_out != NULL)
|
|
*is_infinite_out = TimevalPosInfinity;
|
|
return PG_INT64_MAX;
|
|
}
|
|
}
|
|
|
|
return ts_time_value_to_internal(time_val, type_oid);
|
|
}
|
|
case TIMESTAMPTZOID:
|
|
{
|
|
TimestampTz ts = DatumGetTimestampTz(time_val);
|
|
if (TIMESTAMP_NOT_FINITE(ts))
|
|
{
|
|
if (TIMESTAMP_IS_NOBEGIN(ts))
|
|
{
|
|
if (is_infinite_out != NULL)
|
|
*is_infinite_out = TimevalNegInfinity;
|
|
return PG_INT64_MIN;
|
|
}
|
|
else
|
|
{
|
|
if (is_infinite_out != NULL)
|
|
*is_infinite_out = TimevalPosInfinity;
|
|
return PG_INT64_MAX;
|
|
}
|
|
}
|
|
|
|
return ts_time_value_to_internal(time_val, type_oid);
|
|
}
|
|
case DATEOID:
|
|
{
|
|
DateADT d = DatumGetDateADT(time_val);
|
|
if (DATE_NOT_FINITE(d))
|
|
{
|
|
if (DATE_IS_NOBEGIN(d))
|
|
{
|
|
if (is_infinite_out != NULL)
|
|
*is_infinite_out = TimevalNegInfinity;
|
|
return PG_INT64_MIN;
|
|
}
|
|
else
|
|
{
|
|
if (is_infinite_out != NULL)
|
|
*is_infinite_out = TimevalPosInfinity;
|
|
return PG_INT64_MAX;
|
|
}
|
|
}
|
|
|
|
return ts_time_value_to_internal(time_val, type_oid);
|
|
}
|
|
}
|
|
|
|
return ts_time_value_to_internal(time_val, type_oid);
|
|
}
|
|
|
|
TS_FUNCTION_INFO_V1(ts_time_to_internal);
|
|
Datum
|
|
ts_time_to_internal(PG_FUNCTION_ARGS)
|
|
{
|
|
Datum time = PG_GETARG_DATUM(0);
|
|
Oid time_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
|
|
int64 res = ts_time_value_to_internal(time, time_type);
|
|
PG_RETURN_INT64(res);
|
|
}
|
|
|
|
static Datum ts_integer_to_internal_value(int64 value, Oid type);
|
|
|
|
/*
|
|
* convert int64 to Datum according to type
|
|
* internally we store all times as int64 in the
|
|
* same format postgres does
|
|
*/
|
|
TSDLLEXPORT Datum
|
|
ts_internal_to_time_value(int64 value, Oid type)
|
|
{
|
|
if (TS_TIME_IS_NOBEGIN(value, type))
|
|
return ts_time_datum_get_nobegin(type);
|
|
|
|
if (TS_TIME_IS_NOEND(value, type))
|
|
return ts_time_datum_get_noend(type);
|
|
|
|
switch (type)
|
|
{
|
|
case INT2OID:
|
|
case INT4OID:
|
|
case INT8OID:
|
|
return ts_integer_to_internal_value(value, type);
|
|
case TIMESTAMPOID:
|
|
case TIMESTAMPTZOID:
|
|
/* we continue ts_time_value_to_internal's incorrect handling of TIMESTAMPs for
|
|
* compatibility */
|
|
return DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(value));
|
|
case DATEOID:
|
|
return DirectFunctionCall1(ts_pg_unix_microseconds_to_date, Int64GetDatum(value));
|
|
default:
|
|
if (ts_type_is_int8_binary_compatible(type))
|
|
return Int64GetDatum(value);
|
|
elog(ERROR,
|
|
"unknown time type \"%s\" in ts_internal_to_time_value",
|
|
format_type_be(type));
|
|
pg_unreachable();
|
|
}
|
|
}
|
|
|
|
TSDLLEXPORT char *
|
|
ts_internal_to_time_string(int64 value, Oid type)
|
|
{
|
|
Datum time_datum = ts_internal_to_time_value(value, type);
|
|
Oid typoutputfunc;
|
|
bool typIsVarlena;
|
|
FmgrInfo typoutputinfo;
|
|
|
|
getTypeOutputInfo(type, &typoutputfunc, &typIsVarlena);
|
|
fmgr_info(typoutputfunc, &typoutputinfo);
|
|
return OutputFunctionCall(&typoutputinfo, time_datum);
|
|
}
|
|
|
|
TS_FUNCTION_INFO_V1(ts_pg_unix_microseconds_to_interval);
|
|
|
|
Datum
|
|
ts_pg_unix_microseconds_to_interval(PG_FUNCTION_ARGS)
|
|
{
|
|
int64 microseconds = PG_GETARG_INT64(0);
|
|
Interval *interval = palloc0(sizeof(*interval));
|
|
interval->day = microseconds / USECS_PER_DAY;
|
|
interval->time = microseconds % USECS_PER_DAY;
|
|
PG_RETURN_INTERVAL_P(interval);
|
|
}
|
|
|
|
TSDLLEXPORT Datum
|
|
ts_internal_to_interval_value(int64 value, Oid type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case INT2OID:
|
|
case INT4OID:
|
|
case INT8OID:
|
|
return ts_integer_to_internal_value(value, type);
|
|
case INTERVALOID:
|
|
return DirectFunctionCall1(ts_pg_unix_microseconds_to_interval, Int64GetDatum(value));
|
|
default:
|
|
elog(ERROR,
|
|
"unknown time type \"%s\" in ts_internal_to_interval_value",
|
|
format_type_be(type));
|
|
pg_unreachable();
|
|
}
|
|
}
|
|
|
|
static Datum
|
|
ts_integer_to_internal_value(int64 value, Oid type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case INT2OID:
|
|
return Int16GetDatum(value);
|
|
case INT4OID:
|
|
return Int32GetDatum(value);
|
|
case INT8OID:
|
|
return Int64GetDatum(value);
|
|
default:
|
|
elog(ERROR,
|
|
"unknown time type \"%s\" in ts_internal_to_time_value",
|
|
format_type_be(type));
|
|
pg_unreachable();
|
|
}
|
|
}
|
|
|
|
/* Returns approximate period in microseconds */
|
|
int64
|
|
ts_get_interval_period_approx(Interval *interval)
|
|
{
|
|
return interval->time +
|
|
((((int64) interval->month * DAYS_PER_MONTH) + interval->day) * USECS_PER_DAY);
|
|
}
|
|
|
|
#define DAYS_PER_WEEK 7
|
|
#define DAYS_PER_QUARTER 89
|
|
#define YEARS_PER_DECADE 10
|
|
#define YEARS_PER_CENTURY 100
|
|
#define YEARS_PER_MILLENNIUM 1000
|
|
|
|
/* Returns approximate period in microseconds */
|
|
int64
|
|
ts_date_trunc_interval_period_approx(text *units)
|
|
{
|
|
int decode_type, val;
|
|
char *lowunits =
|
|
downcase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false);
|
|
|
|
decode_type = DecodeUnits(0, lowunits, &val);
|
|
|
|
if (decode_type != UNITS)
|
|
return -1;
|
|
|
|
switch (val)
|
|
{
|
|
case DTK_WEEK:
|
|
return DAYS_PER_WEEK * USECS_PER_DAY;
|
|
case DTK_MILLENNIUM:
|
|
return YEARS_PER_MILLENNIUM * DAYS_PER_YEAR * USECS_PER_DAY;
|
|
case DTK_CENTURY:
|
|
return YEARS_PER_CENTURY * DAYS_PER_YEAR * USECS_PER_DAY;
|
|
case DTK_DECADE:
|
|
return YEARS_PER_DECADE * DAYS_PER_YEAR * USECS_PER_DAY;
|
|
case DTK_YEAR:
|
|
return 1 * DAYS_PER_YEAR * USECS_PER_DAY;
|
|
case DTK_QUARTER:
|
|
return DAYS_PER_QUARTER * USECS_PER_DAY;
|
|
case DTK_MONTH:
|
|
return DAYS_PER_MONTH * USECS_PER_DAY;
|
|
case DTK_DAY:
|
|
return USECS_PER_DAY;
|
|
case DTK_HOUR:
|
|
return USECS_PER_HOUR;
|
|
case DTK_MINUTE:
|
|
return USECS_PER_MINUTE;
|
|
case DTK_SECOND:
|
|
return USECS_PER_SEC;
|
|
case DTK_MILLISEC:
|
|
return USECS_PER_SEC / 1000;
|
|
case DTK_MICROSEC:
|
|
return 1;
|
|
default:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("timestamp units \"%s\" not supported", lowunits)));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Oid
|
|
ts_inheritance_parent_relid(Oid relid)
|
|
{
|
|
Relation catalog;
|
|
SysScanDesc scan;
|
|
ScanKeyData skey;
|
|
Oid parent = InvalidOid;
|
|
HeapTuple tuple;
|
|
|
|
catalog = table_open(InheritsRelationId, AccessShareLock);
|
|
ScanKeyInit(&skey,
|
|
Anum_pg_inherits_inhrelid,
|
|
BTEqualStrategyNumber,
|
|
F_OIDEQ,
|
|
ObjectIdGetDatum(relid));
|
|
scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true, NULL, 1, &skey);
|
|
tuple = systable_getnext(scan);
|
|
|
|
if (HeapTupleIsValid(tuple))
|
|
parent = ((Form_pg_inherits) GETSTRUCT(tuple))->inhparent;
|
|
|
|
systable_endscan(scan);
|
|
table_close(catalog, AccessShareLock);
|
|
|
|
return parent;
|
|
}
|
|
|
|
TSDLLEXPORT bool
|
|
ts_type_is_int8_binary_compatible(Oid sourcetype)
|
|
{
|
|
HeapTuple tuple;
|
|
Form_pg_cast castForm;
|
|
bool result;
|
|
|
|
tuple =
|
|
SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(sourcetype), ObjectIdGetDatum(INT8OID));
|
|
if (!HeapTupleIsValid(tuple))
|
|
return false; /* no cast */
|
|
castForm = (Form_pg_cast) GETSTRUCT(tuple);
|
|
result = castForm->castmethod == COERCION_METHOD_BINARY;
|
|
ReleaseSysCache(tuple);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Create a fresh struct pointer that will contain copied contents of the tuple.
|
|
* Note that this function uses GETSTRUCT, which will not work correctly for tuple types
|
|
* that might have variable lengths.
|
|
* Also note that the function assumes no NULLs in the tuple.
|
|
*/
|
|
static void *
|
|
ts_create_struct_from_tuple(HeapTuple tuple, MemoryContext mctx, size_t alloc_size,
|
|
size_t copy_size)
|
|
{
|
|
void *struct_ptr = MemoryContextAllocZero(mctx, alloc_size);
|
|
|
|
/*
|
|
* Make sure the function is not used when the tuple contains NULLs.
|
|
* Also compare the aligned sizes in the assert.
|
|
*/
|
|
Assert(copy_size == MAXALIGN(tuple->t_len - tuple->t_data->t_hoff));
|
|
memcpy(struct_ptr, GETSTRUCT(tuple), copy_size);
|
|
|
|
return struct_ptr;
|
|
}
|
|
|
|
void *
|
|
ts_create_struct_from_slot(TupleTableSlot *slot, MemoryContext mctx, size_t alloc_size,
|
|
size_t copy_size)
|
|
{
|
|
bool should_free;
|
|
HeapTuple tuple = ExecFetchSlotHeapTuple(slot, false, &should_free);
|
|
void *result = ts_create_struct_from_tuple(tuple, mctx, alloc_size, copy_size);
|
|
|
|
if (should_free)
|
|
heap_freetuple(tuple);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
ts_function_types_equal(Oid left[], Oid right[], int nargs)
|
|
{
|
|
int arg_index;
|
|
|
|
for (arg_index = 0; arg_index < nargs; arg_index++)
|
|
{
|
|
if (left[arg_index] != right[arg_index])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Oid
|
|
ts_get_function_oid(const char *funcname, const char *schema_name, int nargs, Oid arg_types[])
|
|
{
|
|
List *qualified_funcname =
|
|
list_make2(makeString(pstrdup(schema_name)), makeString(pstrdup(funcname)));
|
|
FuncCandidateList func_candidates;
|
|
|
|
func_candidates = FuncnameGetCandidates(qualified_funcname,
|
|
nargs,
|
|
NIL,
|
|
false,
|
|
#if PG14_GE
|
|
false, /* include_out_arguments */
|
|
#endif
|
|
false,
|
|
false);
|
|
while (func_candidates != NULL)
|
|
{
|
|
if (func_candidates->nargs == nargs &&
|
|
ts_function_types_equal(func_candidates->args, arg_types, nargs))
|
|
return func_candidates->oid;
|
|
func_candidates = func_candidates->next;
|
|
}
|
|
|
|
elog(ERROR,
|
|
"failed to find function %s with %d args in schema \"%s\"",
|
|
funcname,
|
|
nargs,
|
|
schema_name);
|
|
|
|
return InvalidOid;
|
|
}
|
|
|
|
/*
|
|
* Find a partitioning function with a given schema and name.
|
|
*
|
|
* The caller can optionally pass a filter function and a type of the argument
|
|
* that the partitioning function should take.
|
|
*/
|
|
Oid
|
|
ts_lookup_proc_filtered(const char *schema, const char *funcname, Oid *rettype, proc_filter filter,
|
|
void *filter_arg)
|
|
{
|
|
Oid namespace_oid = LookupExplicitNamespace(schema, false);
|
|
regproc func = InvalidOid;
|
|
CatCList *catlist;
|
|
int i;
|
|
|
|
/*
|
|
* We could use SearchSysCache3 to get by (name, args, namespace), but
|
|
* that would not allow us to check for functions that take either
|
|
* ANYELEMENTOID or a dimension-specific in the same search.
|
|
*/
|
|
catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname));
|
|
|
|
for (i = 0; i < catlist->n_members; i++)
|
|
{
|
|
HeapTuple proctup = &catlist->members[i]->tuple;
|
|
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
|
|
|
|
if (procform->pronamespace == namespace_oid &&
|
|
(filter == NULL || filter(procform, filter_arg)))
|
|
{
|
|
if (rettype)
|
|
*rettype = procform->prorettype;
|
|
|
|
func = procform->oid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ReleaseSysCacheList(catlist);
|
|
|
|
return func;
|
|
}
|
|
|
|
/*
|
|
* ts_get_operator
|
|
*
|
|
* finds an operator given an exact specification (name, namespace,
|
|
* left and right type IDs).
|
|
*/
|
|
Oid
|
|
ts_get_operator(const char *name, Oid namespace, Oid left, Oid right)
|
|
{
|
|
HeapTuple tup;
|
|
Oid opoid = InvalidOid;
|
|
|
|
tup = SearchSysCache4(OPERNAMENSP,
|
|
PointerGetDatum(name),
|
|
ObjectIdGetDatum(left),
|
|
ObjectIdGetDatum(right),
|
|
ObjectIdGetDatum(namespace));
|
|
if (HeapTupleIsValid(tup))
|
|
{
|
|
Form_pg_operator oprform = (Form_pg_operator) GETSTRUCT(tup);
|
|
opoid = oprform->oid;
|
|
ReleaseSysCache(tup);
|
|
}
|
|
|
|
return opoid;
|
|
}
|
|
|
|
/*
|
|
* ts_get_cast_func
|
|
*
|
|
* returns Oid of functions that implements cast from source to target
|
|
*/
|
|
Oid
|
|
ts_get_cast_func(Oid source, Oid target)
|
|
{
|
|
Oid result = InvalidOid;
|
|
HeapTuple casttup;
|
|
|
|
casttup = SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(source), ObjectIdGetDatum(target));
|
|
if (HeapTupleIsValid(casttup))
|
|
{
|
|
Form_pg_cast castform = (Form_pg_cast) GETSTRUCT(casttup);
|
|
|
|
result = castform->castfunc;
|
|
ReleaseSysCache(casttup);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
AppendRelInfo *
|
|
ts_get_appendrelinfo(PlannerInfo *root, Index rti, bool missing_ok)
|
|
{
|
|
ListCell *lc;
|
|
/* use append_rel_array if it has been setup */
|
|
if (root->append_rel_array)
|
|
{
|
|
if (root->append_rel_array[rti])
|
|
return root->append_rel_array[rti];
|
|
if (!missing_ok)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("no appendrelinfo found for index %d", rti)));
|
|
return NULL;
|
|
}
|
|
|
|
foreach (lc, root->append_rel_list)
|
|
{
|
|
AppendRelInfo *appinfo = lfirst(lc);
|
|
if (appinfo->child_relid == rti)
|
|
return appinfo;
|
|
}
|
|
if (!missing_ok)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("no appendrelinfo found for index %d", rti)));
|
|
return NULL;
|
|
}
|
|
|
|
#if PG12 || PG15_GE
|
|
/*
|
|
* Find an equivalence class member expression, all of whose Vars, come from
|
|
* the indicated relation.
|
|
*
|
|
* This function has been copied from find_em_expr_for_rel in
|
|
* contrib/postgres_fdw/postgres_fdw.c in postgres source.
|
|
* This function was moved to postgres main in PG13 so we only need this
|
|
* backport for PG12 in later versions we will use the postgres implementation.
|
|
* PG15 removes the function again from postgres code, so we use our own
|
|
* implementation for PG15+ again.
|
|
*/
|
|
Expr *
|
|
ts_find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
|
|
{
|
|
ListCell *lc_em;
|
|
|
|
foreach (lc_em, ec->ec_members)
|
|
{
|
|
EquivalenceMember *em = lfirst(lc_em);
|
|
|
|
if (bms_is_subset(em->em_relids, rel->relids) && !bms_is_empty(em->em_relids))
|
|
{
|
|
/*
|
|
* If there is more than one equivalence member whose Vars are
|
|
* taken entirely from this relation, we'll be content to choose
|
|
* any one of those.
|
|
*/
|
|
return em->em_expr;
|
|
}
|
|
}
|
|
|
|
/* We didn't find any suitable equivalence class expression */
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
ts_has_row_security(Oid relid)
|
|
{
|
|
HeapTuple tuple;
|
|
Form_pg_class classform;
|
|
bool relrowsecurity;
|
|
bool relforcerowsecurity;
|
|
|
|
/* Fetch relation's relrowsecurity and relforcerowsecurity flags */
|
|
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "cache lookup failed for relid %u", relid);
|
|
classform = (Form_pg_class) GETSTRUCT(tuple);
|
|
relrowsecurity = classform->relrowsecurity;
|
|
relforcerowsecurity = classform->relforcerowsecurity;
|
|
ReleaseSysCache(tuple);
|
|
return (relrowsecurity || relforcerowsecurity);
|
|
}
|
|
|
|
List *
|
|
ts_get_reloptions(Oid relid)
|
|
{
|
|
HeapTuple tuple;
|
|
Datum datum;
|
|
bool isnull;
|
|
List *options = NIL;
|
|
|
|
Assert(OidIsValid(relid));
|
|
|
|
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "cache lookup failed for relation %u", relid);
|
|
|
|
datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);
|
|
|
|
if (!isnull && PointerIsValid(DatumGetPointer(datum)))
|
|
options = untransformRelOptions(datum);
|
|
|
|
ReleaseSysCache(tuple);
|
|
|
|
return options;
|
|
}
|
|
|
|
/*
|
|
* Get the integer_now function for a dimension
|
|
*/
|
|
Oid
|
|
ts_get_integer_now_func(const Dimension *open_dim)
|
|
{
|
|
Oid rettype;
|
|
Oid now_func;
|
|
Oid argtypes[] = { 0 };
|
|
|
|
rettype = ts_dimension_get_partition_type(open_dim);
|
|
|
|
Assert(IS_INTEGER_TYPE(rettype));
|
|
|
|
if (strlen(NameStr(open_dim->fd.integer_now_func)) == 0 &&
|
|
strlen(NameStr(open_dim->fd.integer_now_func_schema)) == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_FUNCTION), (errmsg("integer_now function not set"))));
|
|
|
|
List *name = list_make2(makeString((char *) NameStr(open_dim->fd.integer_now_func_schema)),
|
|
makeString((char *) NameStr(open_dim->fd.integer_now_func)));
|
|
now_func = LookupFuncName(name, 0, argtypes, false);
|
|
|
|
if (get_func_rettype(now_func) != rettype)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
|
(errmsg("invalid integer_now function"),
|
|
errhint("return type of function does not match dimension type"))));
|
|
|
|
return now_func;
|
|
}
|
|
|
|
/* subtract passed in interval from the now.
|
|
* Arguments:
|
|
* now_func : function used to compute now.
|
|
* interval : integer value
|
|
* Returns:
|
|
* now_func() - interval
|
|
*/
|
|
int64
|
|
ts_sub_integer_from_now(int64 interval, Oid time_dim_type, Oid now_func)
|
|
{
|
|
Datum now;
|
|
int64 res;
|
|
|
|
AssertArg(IS_INTEGER_TYPE(time_dim_type));
|
|
|
|
now = OidFunctionCall0(now_func);
|
|
switch (time_dim_type)
|
|
{
|
|
case INT2OID:
|
|
res = DatumGetInt16(now) - interval;
|
|
if (res < PG_INT16_MIN || res > PG_INT16_MAX)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
|
|
errmsg("integer time overflow")));
|
|
return res;
|
|
case INT4OID:
|
|
res = DatumGetInt32(now) - interval;
|
|
if (res < PG_INT32_MIN || res > PG_INT32_MAX)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
|
|
errmsg("integer time overflow")));
|
|
return res;
|
|
case INT8OID:
|
|
{
|
|
bool overflow = pg_sub_s64_overflow(DatumGetInt64(now), interval, &res);
|
|
if (overflow)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
|
|
errmsg("integer time overflow")));
|
|
}
|
|
return res;
|
|
}
|
|
default:
|
|
pg_unreachable();
|
|
}
|
|
}
|
|
|
|
TS_FUNCTION_INFO_V1(ts_subtract_integer_from_now);
|
|
Datum
|
|
ts_subtract_integer_from_now(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid ht_relid = PG_GETARG_OID(0);
|
|
int64 lag = PG_GETARG_INT64(1);
|
|
Cache *hcache;
|
|
Hypertable *ht = ts_hypertable_cache_get_cache_and_entry(ht_relid, CACHE_FLAG_NONE, &hcache);
|
|
const Dimension *dim = hyperspace_get_open_dimension(ht->space, 0);
|
|
|
|
if (!dim)
|
|
elog(ERROR, "hypertable has no open partitioning dimension");
|
|
|
|
Oid partitioning_type = ts_dimension_get_partition_type(dim);
|
|
|
|
if (!IS_INTEGER_TYPE(partitioning_type))
|
|
elog(ERROR, "hypertable has no integer partitioning dimension");
|
|
|
|
Oid now_func = ts_get_integer_now_func(dim);
|
|
if (!OidIsValid(now_func))
|
|
elog(ERROR, "could not find valid integer_now function for hypertable");
|
|
|
|
int64 res = ts_sub_integer_from_now(lag, partitioning_type, now_func);
|
|
ts_cache_release(hcache);
|
|
return Int64GetDatum(res);
|
|
}
|
|
|
|
TS_FUNCTION_INFO_V1(ts_relation_size);
|
|
Datum
|
|
ts_relation_size(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
|
|
RelationSize relsize = { 0 };
|
|
TupleDesc tupdesc;
|
|
HeapTuple tuple;
|
|
Datum values[4] = { 0 };
|
|
bool nulls[4] = { false };
|
|
|
|
/* Build a tuple descriptor for our result type */
|
|
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("function returning record called in context "
|
|
"that cannot accept type record")));
|
|
|
|
if (!OidIsValid(relid))
|
|
PG_RETURN_NULL();
|
|
|
|
relsize = ts_relation_size_impl(relid);
|
|
|
|
tupdesc = BlessTupleDesc(tupdesc);
|
|
|
|
values[0] = Int64GetDatum(relsize.total_size);
|
|
values[1] = Int64GetDatum(relsize.heap_size);
|
|
values[2] = Int64GetDatum(relsize.index_size);
|
|
values[3] = Int64GetDatum(relsize.toast_size);
|
|
|
|
tuple = heap_form_tuple(tupdesc, values, nulls);
|
|
|
|
return HeapTupleGetDatum(tuple);
|
|
}
|
|
|
|
RelationSize
|
|
ts_relation_size_impl(Oid relid)
|
|
{
|
|
RelationSize relsize = { 0 };
|
|
Datum reloid = ObjectIdGetDatum(relid);
|
|
Relation rel;
|
|
|
|
/* Open relation earlier to keep a lock during all function calls */
|
|
rel = try_relation_open(relid, AccessShareLock);
|
|
|
|
if (!rel)
|
|
return relsize;
|
|
|
|
/* Get to total relation size to be our calculation base */
|
|
relsize.total_size = DatumGetInt64(DirectFunctionCall1(pg_total_relation_size, reloid));
|
|
|
|
/* Get the indexes size of the relation (don't consider TOAST indexes) */
|
|
relsize.index_size = DatumGetInt64(DirectFunctionCall1(pg_indexes_size, reloid));
|
|
|
|
/* If exists an associated TOAST calculate the total size (including indexes) */
|
|
if (OidIsValid(rel->rd_rel->reltoastrelid))
|
|
relsize.toast_size =
|
|
DatumGetInt64(DirectFunctionCall1(pg_total_relation_size,
|
|
ObjectIdGetDatum(rel->rd_rel->reltoastrelid)));
|
|
else
|
|
relsize.toast_size = 0;
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
/* Calculate the HEAP size based on the total size and indexes plus toast */
|
|
relsize.heap_size = relsize.total_size - (relsize.index_size + relsize.toast_size);
|
|
|
|
return relsize;
|
|
}
|
|
|
|
#define STR_VALUE(str) #str
|
|
#define NODE_CASE(name) \
|
|
case T_##name: \
|
|
return STR_VALUE(name)
|
|
|
|
/*
|
|
* Return a string with the name of the node.
|
|
*
|
|
*/
|
|
const char *
|
|
ts_get_node_name(Node *node)
|
|
{
|
|
/* tags are defined in nodes/nodes.h postgres source */
|
|
switch (nodeTag(node))
|
|
{
|
|
/*
|
|
* primitive nodes (primnodes.h)
|
|
*/
|
|
NODE_CASE(Alias);
|
|
NODE_CASE(RangeVar);
|
|
NODE_CASE(TableFunc);
|
|
NODE_CASE(IntoClause);
|
|
#if PG15_LT
|
|
/* PG15 removed T_Expr nodetag because it's an abstract type. */
|
|
NODE_CASE(Expr);
|
|
#endif
|
|
NODE_CASE(Var);
|
|
NODE_CASE(Const);
|
|
NODE_CASE(Param);
|
|
NODE_CASE(Aggref);
|
|
NODE_CASE(GroupingFunc);
|
|
NODE_CASE(WindowFunc);
|
|
NODE_CASE(SubscriptingRef);
|
|
NODE_CASE(FuncExpr);
|
|
NODE_CASE(NamedArgExpr);
|
|
NODE_CASE(OpExpr);
|
|
NODE_CASE(DistinctExpr);
|
|
NODE_CASE(NullIfExpr);
|
|
NODE_CASE(ScalarArrayOpExpr);
|
|
NODE_CASE(BoolExpr);
|
|
NODE_CASE(SubLink);
|
|
NODE_CASE(SubPlan);
|
|
NODE_CASE(AlternativeSubPlan);
|
|
NODE_CASE(FieldSelect);
|
|
NODE_CASE(FieldStore);
|
|
NODE_CASE(RelabelType);
|
|
NODE_CASE(CoerceViaIO);
|
|
NODE_CASE(ArrayCoerceExpr);
|
|
NODE_CASE(ConvertRowtypeExpr);
|
|
NODE_CASE(CollateExpr);
|
|
NODE_CASE(CaseExpr);
|
|
NODE_CASE(CaseWhen);
|
|
NODE_CASE(CaseTestExpr);
|
|
NODE_CASE(ArrayExpr);
|
|
NODE_CASE(RowExpr);
|
|
NODE_CASE(RowCompareExpr);
|
|
NODE_CASE(CoalesceExpr);
|
|
NODE_CASE(MinMaxExpr);
|
|
NODE_CASE(SQLValueFunction);
|
|
NODE_CASE(XmlExpr);
|
|
NODE_CASE(NullTest);
|
|
NODE_CASE(BooleanTest);
|
|
NODE_CASE(CoerceToDomain);
|
|
NODE_CASE(CoerceToDomainValue);
|
|
NODE_CASE(SetToDefault);
|
|
NODE_CASE(CurrentOfExpr);
|
|
NODE_CASE(NextValueExpr);
|
|
NODE_CASE(InferenceElem);
|
|
NODE_CASE(TargetEntry);
|
|
NODE_CASE(RangeTblRef);
|
|
NODE_CASE(JoinExpr);
|
|
NODE_CASE(FromExpr);
|
|
NODE_CASE(OnConflictExpr);
|
|
|
|
/*
|
|
* plan nodes (plannodes.h)
|
|
*/
|
|
NODE_CASE(Plan);
|
|
NODE_CASE(Result);
|
|
NODE_CASE(ProjectSet);
|
|
NODE_CASE(ModifyTable);
|
|
NODE_CASE(Append);
|
|
NODE_CASE(MergeAppend);
|
|
NODE_CASE(RecursiveUnion);
|
|
NODE_CASE(BitmapAnd);
|
|
NODE_CASE(BitmapOr);
|
|
NODE_CASE(Scan);
|
|
NODE_CASE(SeqScan);
|
|
NODE_CASE(SampleScan);
|
|
NODE_CASE(IndexScan);
|
|
NODE_CASE(IndexOnlyScan);
|
|
NODE_CASE(BitmapIndexScan);
|
|
NODE_CASE(BitmapHeapScan);
|
|
NODE_CASE(TidScan);
|
|
NODE_CASE(SubqueryScan);
|
|
NODE_CASE(FunctionScan);
|
|
NODE_CASE(ValuesScan);
|
|
NODE_CASE(TableFuncScan);
|
|
NODE_CASE(CteScan);
|
|
NODE_CASE(NamedTuplestoreScan);
|
|
NODE_CASE(WorkTableScan);
|
|
NODE_CASE(ForeignScan);
|
|
NODE_CASE(CustomScan);
|
|
NODE_CASE(Join);
|
|
NODE_CASE(NestLoop);
|
|
NODE_CASE(MergeJoin);
|
|
NODE_CASE(HashJoin);
|
|
NODE_CASE(Material);
|
|
NODE_CASE(Sort);
|
|
NODE_CASE(Group);
|
|
NODE_CASE(Agg);
|
|
NODE_CASE(WindowAgg);
|
|
NODE_CASE(Unique);
|
|
NODE_CASE(Gather);
|
|
NODE_CASE(GatherMerge);
|
|
NODE_CASE(Hash);
|
|
NODE_CASE(SetOp);
|
|
NODE_CASE(LockRows);
|
|
NODE_CASE(Limit);
|
|
|
|
/*
|
|
* planner nodes (pathnodes.h)
|
|
*/
|
|
NODE_CASE(IndexPath);
|
|
NODE_CASE(BitmapHeapPath);
|
|
NODE_CASE(BitmapAndPath);
|
|
NODE_CASE(BitmapOrPath);
|
|
NODE_CASE(TidPath);
|
|
NODE_CASE(SubqueryScanPath);
|
|
NODE_CASE(ForeignPath);
|
|
NODE_CASE(NestPath);
|
|
NODE_CASE(MergePath);
|
|
NODE_CASE(HashPath);
|
|
NODE_CASE(AppendPath);
|
|
NODE_CASE(MergeAppendPath);
|
|
NODE_CASE(GroupResultPath);
|
|
NODE_CASE(MaterialPath);
|
|
NODE_CASE(UniquePath);
|
|
NODE_CASE(GatherPath);
|
|
NODE_CASE(GatherMergePath);
|
|
NODE_CASE(ProjectionPath);
|
|
NODE_CASE(ProjectSetPath);
|
|
NODE_CASE(SortPath);
|
|
NODE_CASE(GroupPath);
|
|
NODE_CASE(UpperUniquePath);
|
|
NODE_CASE(AggPath);
|
|
NODE_CASE(GroupingSetsPath);
|
|
NODE_CASE(MinMaxAggPath);
|
|
NODE_CASE(WindowAggPath);
|
|
NODE_CASE(SetOpPath);
|
|
NODE_CASE(RecursiveUnionPath);
|
|
NODE_CASE(LockRowsPath);
|
|
NODE_CASE(ModifyTablePath);
|
|
NODE_CASE(LimitPath);
|
|
|
|
case T_Path:
|
|
switch (castNode(Path, node)->pathtype)
|
|
{
|
|
NODE_CASE(SeqScan);
|
|
NODE_CASE(SampleScan);
|
|
NODE_CASE(SubqueryScan);
|
|
NODE_CASE(FunctionScan);
|
|
NODE_CASE(TableFuncScan);
|
|
NODE_CASE(ValuesScan);
|
|
NODE_CASE(CteScan);
|
|
NODE_CASE(WorkTableScan);
|
|
default:
|
|
return psprintf("Path (%d)", castNode(Path, node)->pathtype);
|
|
}
|
|
|
|
case T_CustomPath:
|
|
return psprintf("CustomPath (%s)", castNode(CustomPath, node)->methods->CustomName);
|
|
|
|
default:
|
|
return psprintf("Node (%d)", nodeTag(node));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Implementation marked unused in PostgreSQL lsyscache.c
|
|
*/
|
|
int
|
|
ts_get_relnatts(Oid relid)
|
|
{
|
|
HeapTuple tp;
|
|
Form_pg_class reltup;
|
|
int result;
|
|
|
|
tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
|
if (!HeapTupleIsValid(tp))
|
|
return InvalidAttrNumber;
|
|
|
|
reltup = (Form_pg_class) GETSTRUCT(tp);
|
|
result = reltup->relnatts;
|
|
|
|
ReleaseSysCache(tp);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Wrap AlterTableInternal() for event trigger handling.
|
|
*
|
|
* AlterTableInternal can be called as a utility command, which is common in a
|
|
* SQL function that alters a table in some form when called in the form
|
|
* SELECT <cmd> INTO <table>. This is transformed into a process utility
|
|
* command (CREATE TABLE AS), which expects an event trigger context to be
|
|
* set up.
|
|
*
|
|
* The "cmd" parameter can be set to a higher-level command that caused the
|
|
* alter table to occur. If "cmd" is set to NULL, the "cmds" list will be used
|
|
* instead.
|
|
*/
|
|
void
|
|
ts_alter_table_with_event_trigger(Oid relid, Node *cmd, List *cmds, bool recurse)
|
|
{
|
|
if (cmd == NULL)
|
|
cmd = (Node *) cmds;
|
|
|
|
EventTriggerAlterTableStart(cmd);
|
|
AlterTableInternal(relid, cmds, recurse);
|
|
EventTriggerAlterTableEnd();
|
|
}
|
|
|
|
void
|
|
ts_copy_relation_acl(const Oid source_relid, const Oid target_relid, const Oid owner_id)
|
|
{
|
|
HeapTuple source_tuple;
|
|
bool is_null;
|
|
Datum acl_datum;
|
|
Relation class_rel;
|
|
|
|
/* We open it here since there is no point in trying to update the tuples
|
|
* if we cannot open the Relation catalog table */
|
|
class_rel = table_open(RelationRelationId, RowExclusiveLock);
|
|
|
|
source_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(source_relid));
|
|
Assert(HeapTupleIsValid(source_tuple));
|
|
|
|
/* We only bother about setting the ACL if the source relation ACL is
|
|
* non-null */
|
|
acl_datum = SysCacheGetAttr(RELOID, source_tuple, Anum_pg_class_relacl, &is_null);
|
|
|
|
if (!is_null)
|
|
{
|
|
HeapTuple target_tuple, newtuple;
|
|
Datum new_val[Natts_pg_class] = { 0 };
|
|
bool new_null[Natts_pg_class] = { false };
|
|
bool new_repl[Natts_pg_class] = { false };
|
|
Acl *acl = DatumGetAclP(acl_datum);
|
|
|
|
new_repl[Anum_pg_class_relacl - 1] = true;
|
|
new_val[Anum_pg_class_relacl - 1] = PointerGetDatum(acl);
|
|
|
|
/* Find the tuple for the target in `pg_class` */
|
|
target_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(target_relid));
|
|
Assert(HeapTupleIsValid(target_tuple));
|
|
|
|
/* Update the relacl for the target tuple to use the acl from the source */
|
|
newtuple = heap_modify_tuple(target_tuple,
|
|
RelationGetDescr(class_rel),
|
|
new_val,
|
|
new_null,
|
|
new_repl);
|
|
CatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple);
|
|
|
|
/* We need to update the shared dependencies as well to indicate that
|
|
* the target is dependent on any roles that the source is
|
|
* dependent on. */
|
|
Oid *newmembers;
|
|
int nnewmembers = aclmembers(acl, &newmembers);
|
|
|
|
/* The list of old members is intentionally empty since we are using
|
|
* updateAclDependencies to set the ACL for the target. We can use NULL
|
|
* because getOidListDiff, which is called from updateAclDependencies,
|
|
* can handle that. */
|
|
updateAclDependencies(RelationRelationId,
|
|
target_relid,
|
|
0,
|
|
owner_id,
|
|
0,
|
|
NULL,
|
|
nnewmembers,
|
|
newmembers);
|
|
|
|
heap_freetuple(newtuple);
|
|
ReleaseSysCache(target_tuple);
|
|
}
|
|
|
|
ReleaseSysCache(source_tuple);
|
|
table_close(class_rel, RowExclusiveLock);
|
|
}
|