timescaledb/tsl/src/chunk_api.c

1630 lines
48 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/htup_details.h>
#include <access/htup.h>
#include <access/multixact.h>
#include <access/visibilitymap.h>
#include <access/xact.h>
#include <catalog/indexing.h>
#include <catalog/pg_class.h>
#include <catalog/pg_inherits.h>
#include <catalog/pg_namespace.h>
#include <catalog/pg_operator.h>
#include <catalog/pg_type.h>
#include <commands/vacuum.h>
#include <fmgr.h>
#include <funcapi.h>
#include <miscadmin.h>
#include <utils/array.h>
#include <utils/builtins.h>
#include <utils/jsonb.h>
#include <utils/lsyscache.h>
#include <utils/syscache.h>
#include <catalog.h>
#include <compat.h>
#include <chunk.h>
#include <chunk_data_node.h>
#include <errors.h>
#include <hypercube.h>
#include <hypertable.h>
#include <hypertable_cache.h>
#include "remote/async.h"
#include "remote/dist_txn.h"
#include "remote/stmt_params.h"
#include "remote/dist_commands.h"
#include "remote/tuplefactory.h"
#include "chunk_api.h"
/*
* These values come from the pg_type table.
*/
#define FLOAT4_TYPELEN sizeof(float4)
#define FLOAT4_TYPEBYVAL true
#define FLOAT4_TYPEALIGN 'i'
#define CSTRING_TYPELEN -2
#define CSTRING_TYPEBYVAL false
#define CSTRING_TYPEALIGN 'c'
#define INT4_TYPELEN sizeof(int32)
#define INT4_TYPEBYVAL true
#define INT4_TYPEALIGN 'i'
#define OID_TYPELEN sizeof(Oid)
#define OID_TYPEBYVAL true
#define OID_TYPEALIGN 'i'
#define CSTRING_ARY_TYPELEN -1
/*
* Convert a hypercube to a JSONB value.
*
* For instance, a two-dimensional hypercube, with dimensions "time" and
* "device", might look as follows:
*
* {"time": [1514419200000000, 1515024000000000],
* "device": [-9223372036854775808, 1073741823]}
*/
static JsonbValue *
hypercube_to_jsonb_value(Hypercube *hc, Hyperspace *hs, JsonbParseState **ps)
{
int i;
Assert(hs->num_dimensions == hc->num_slices);
pushJsonbValue(ps, WJB_BEGIN_OBJECT, NULL);
for (i = 0; i < hc->num_slices; i++)
{
JsonbValue k, v;
char *dim_name = NameStr(hs->dimensions[i].fd.column_name);
Datum range_start =
DirectFunctionCall1(int8_numeric, Int64GetDatum(hc->slices[i]->fd.range_start));
Datum range_end =
DirectFunctionCall1(int8_numeric, Int64GetDatum(hc->slices[i]->fd.range_end));
Assert(hs->dimensions[i].fd.id == hc->slices[i]->fd.dimension_id);
k.type = jbvString;
k.val.string.len = strlen(dim_name);
k.val.string.val = dim_name;
pushJsonbValue(ps, WJB_KEY, &k);
pushJsonbValue(ps, WJB_BEGIN_ARRAY, NULL);
v.type = jbvNumeric;
v.val.numeric = DatumGetNumeric(range_start);
pushJsonbValue(ps, WJB_ELEM, &v);
v.val.numeric = DatumGetNumeric(range_end);
pushJsonbValue(ps, WJB_ELEM, &v);
pushJsonbValue(ps, WJB_END_ARRAY, NULL);
}
return pushJsonbValue(ps, WJB_END_OBJECT, NULL);
}
/*
* Create a hypercube from a JSONB object.
*
* Takes a JSONB object with a hypercube's dimensional constraints and outputs
* a Hypercube. The JSONB is the same format as output by
* hypercube_to_jsonb_value() above, i.e.:
*
* {"time": [1514419200000000, 1515024000000000],
* "device": [-9223372036854775808, 1073741823]}
*/
static Hypercube *
hypercube_from_jsonb(Jsonb *json, Hyperspace *hs, const char **parse_error)
{
JsonbIterator *it;
JsonbIteratorToken type;
JsonbValue v;
Hypercube *hc = NULL;
const char *err = NULL;
it = JsonbIteratorInit(&json->root);
type = JsonbIteratorNext(&it, &v, false);
if (type != WJB_BEGIN_OBJECT)
{
err = "invalid JSON format";
goto out_err;
}
if (v.val.object.nPairs != hs->num_dimensions)
{
err = "invalid number of hypercube dimensions";
goto out_err;
}
hc = ts_hypercube_alloc(hs->num_dimensions);
while ((type = JsonbIteratorNext(&it, &v, false)))
{
int i;
Dimension *dim;
DimensionSlice *slice;
int64 range[2];
const char *name;
if (type == WJB_END_OBJECT)
break;
if (type != WJB_KEY)
{
err = "invalid JSON format";
goto out_err;
}
name = pnstrdup(v.val.string.val, v.val.string.len);
dim = ts_hyperspace_get_dimension_by_name(hs, DIMENSION_TYPE_ANY, name);
if (NULL == dim)
{
err = psprintf("dimension \"%s\" does not exist in hypertable", name);
goto out_err;
}
type = JsonbIteratorNext(&it, &v, false);
if (type != WJB_BEGIN_ARRAY)
{
err = "invalid JSON format";
goto out_err;
}
if (v.val.array.nElems != 2)
{
err = psprintf("unexpected number of dimensional bounds for dimension \"%s\"", name);
goto out_err;
}
for (i = 0; i < 2; i++)
{
type = JsonbIteratorNext(&it, &v, false);
if (type != WJB_ELEM)
{
err = "invalid JSON format";
goto out_err;
}
if (v.type != jbvNumeric)
{
err = psprintf("constraint for dimension \"%s\" is not numeric", name);
goto out_err;
}
range[i] =
DatumGetInt64(DirectFunctionCall1(numeric_int8, NumericGetDatum(v.val.numeric)));
}
type = JsonbIteratorNext(&it, &v, false);
if (type != WJB_END_ARRAY)
{
err = "invalid JSON format";
goto out_err;
}
slice = ts_dimension_slice_create(dim->fd.id, range[0], range[1]);
ts_hypercube_add_slice(hc, slice);
}
out_err:
if (NULL != parse_error)
*parse_error = err;
if (NULL != err)
return NULL;
return hc;
}
enum Anum_create_chunk
{
Anum_create_chunk_id = 1,
Anum_create_chunk_hypertable_id,
Anum_create_chunk_schema_name,
Anum_create_chunk_table_name,
Anum_create_chunk_relkind,
Anum_create_chunk_slices,
Anum_create_chunk_created,
_Anum_create_chunk_max,
};
#define Natts_create_chunk (_Anum_create_chunk_max - 1)
static HeapTuple
chunk_form_tuple(Chunk *chunk, Hypertable *ht, TupleDesc tupdesc, bool created)
{
Datum values[Natts_create_chunk];
bool nulls[Natts_create_chunk] = { false };
JsonbParseState *ps = NULL;
JsonbValue *jv = hypercube_to_jsonb_value(chunk->cube, ht->space, &ps);
if (NULL == jv)
return NULL;
values[AttrNumberGetAttrOffset(Anum_create_chunk_id)] = Int32GetDatum(chunk->fd.id);
values[AttrNumberGetAttrOffset(Anum_create_chunk_hypertable_id)] =
Int32GetDatum(chunk->fd.hypertable_id);
values[AttrNumberGetAttrOffset(Anum_create_chunk_schema_name)] =
NameGetDatum(&chunk->fd.schema_name);
values[AttrNumberGetAttrOffset(Anum_create_chunk_table_name)] =
NameGetDatum(&chunk->fd.table_name);
values[AttrNumberGetAttrOffset(Anum_create_chunk_relkind)] = CharGetDatum(chunk->relkind);
values[AttrNumberGetAttrOffset(Anum_create_chunk_slices)] =
JsonbPGetDatum(JsonbValueToJsonb(jv));
values[AttrNumberGetAttrOffset(Anum_create_chunk_created)] = BoolGetDatum(created);
return heap_form_tuple(tupdesc, values, nulls);
}
Datum
chunk_show(PG_FUNCTION_ARGS)
{
Oid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
Chunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);
Cache *hcache = ts_hypertable_cache_pin();
Hypertable *ht =
ts_hypertable_cache_get_entry(hcache, chunk->hypertable_relid, CACHE_FLAG_NONE);
TupleDesc tupdesc;
HeapTuple tuple;
Assert(NULL != chunk);
Assert(NULL != ht);
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")));
/*
* We use the create_chunk tuple for show_chunk, because they only differ
* in the created column at the end. That column will not be included here
* since it is not part of the tuple descriptor.
*/
tuple = chunk_form_tuple(chunk, ht, tupdesc, false);
ts_cache_release(hcache);
if (NULL == tuple)
ereport(ERROR,
(errcode(ERRCODE_TS_INTERNAL_ERROR), errmsg("could not create tuple from chunk")));
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
Datum
chunk_create(PG_FUNCTION_ARGS)
{
Oid hypertable_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
Jsonb *slices = PG_ARGISNULL(1) ? NULL : PG_GETARG_JSONB_P(1);
const char *schema_name = PG_ARGISNULL(2) ? NULL : PG_GETARG_CSTRING(2);
const char *table_name = PG_ARGISNULL(3) ? NULL : PG_GETARG_CSTRING(3);
Cache *hcache = ts_hypertable_cache_pin();
Hypertable *ht = ts_hypertable_cache_get_entry(hcache, hypertable_relid, CACHE_FLAG_NONE);
Hypercube *hc;
Chunk *chunk;
TupleDesc tupdesc;
HeapTuple tuple;
bool created;
const char *parse_err;
AclResult acl_result;
Assert(NULL != ht);
acl_result = pg_class_aclcheck(hypertable_relid, GetUserId(), ACL_INSERT);
if (acl_result != ACLCHECK_OK)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied for table \"%s\"", get_rel_name(hypertable_relid)),
errdetail("Insert privileges required on \"%s\" to create chunks.",
get_rel_name(hypertable_relid))));
if (NULL == slices)
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid slices")));
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")));
hc = hypercube_from_jsonb(slices, ht->space, &parse_err);
if (NULL == hc)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid hypercube for hypertable \"%s\"", get_rel_name(hypertable_relid)),
errdetail("%s", parse_err)));
chunk = ts_chunk_find_or_create_without_cuts(ht, hc, schema_name, table_name, &created);
Assert(NULL != chunk);
tuple = chunk_form_tuple(chunk, ht, tupdesc, created);
ts_cache_release(hcache);
if (NULL == tuple)
ereport(ERROR,
(errcode(ERRCODE_TS_INTERNAL_ERROR), errmsg("could not create tuple from chunk")));
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
#define CREATE_CHUNK_FUNCTION_NAME "create_chunk"
#define CHUNK_CREATE_STMT \
"SELECT * FROM " INTERNAL_SCHEMA_NAME "." CREATE_CHUNK_FUNCTION_NAME "($1, $2, $3, $4)"
#define ESTIMATE_JSON_STR_SIZE(num_dims) (60 * (num_dims))
static Oid create_chunk_argtypes[4] = { REGCLASSOID, JSONBOID, NAMEOID, NAMEOID };
/*
* Fill in / get the TupleDesc for the result type of the create_chunk()
* function.
*/
static void
get_create_chunk_result_type(TupleDesc *tupdesc)
{
Oid funcoid = ts_get_function_oid(CREATE_CHUNK_FUNCTION_NAME,
INTERNAL_SCHEMA_NAME,
4,
create_chunk_argtypes);
if (get_func_result_type(funcoid, NULL, tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
}
static void
get_result_datums(Datum *values, bool *nulls, unsigned int numvals, AttInMetadata *attinmeta,
PGresult *res)
{
unsigned int i;
memset(nulls, 0, sizeof(bool) * numvals);
for (i = 0; i < numvals; i++)
{
if (PQgetisnull(res, 0, i))
nulls[i] = true;
else
values[i] = InputFunctionCall(&attinmeta->attinfuncs[i],
PQgetvalue(res, 0, i),
attinmeta->attioparams[i],
attinmeta->atttypmods[i]);
}
}
static const char *
chunk_api_dimension_slices_json(const Chunk *chunk, const Hypertable *ht)
{
JsonbParseState *ps = NULL;
JsonbValue *jv = hypercube_to_jsonb_value(chunk->cube, ht->space, &ps);
Jsonb *hcjson = JsonbValueToJsonb(jv);
return JsonbToCString(NULL, &hcjson->root, ESTIMATE_JSON_STR_SIZE(ht->space->num_dimensions));
}
/*
* Create a replica of a chunk on all its assigned data nodes.
*/
void
chunk_api_create_on_data_nodes(Chunk *chunk, Hypertable *ht)
{
AsyncRequestSet *reqset = async_request_set_create();
const char *params[4] = {
quote_qualified_identifier(NameStr(ht->fd.schema_name), NameStr(ht->fd.table_name)),
chunk_api_dimension_slices_json(chunk, ht),
NameStr(chunk->fd.schema_name),
NameStr(chunk->fd.table_name),
};
AsyncResponseResult *res;
ListCell *lc;
TupleDesc tupdesc;
AttInMetadata *attinmeta;
get_create_chunk_result_type(&tupdesc);
attinmeta = TupleDescGetAttInMetadata(tupdesc);
foreach (lc, chunk->data_nodes)
{
ChunkDataNode *cdn = lfirst(lc);
TSConnectionId id = remote_connection_id(cdn->foreign_server_oid, GetUserId());
TSConnection *conn = remote_dist_txn_get_connection(id, REMOTE_TXN_NO_PREP_STMT);
AsyncRequest *req;
req = async_request_send_with_params(conn,
CHUNK_CREATE_STMT,
stmt_params_create_from_values(params, 4),
FORMAT_TEXT);
async_request_attach_user_data(req, cdn);
async_request_set_add(reqset, req);
}
while ((res = async_request_set_wait_ok_result(reqset)) != NULL)
{
PGresult *pgres = async_response_result_get_pg_result(res);
ChunkDataNode *cdn = async_response_result_get_user_data(res);
Datum values[Natts_create_chunk];
bool nulls[Natts_create_chunk];
const char *schema_name, *table_name;
bool created;
Assert(Natts_create_chunk == tupdesc->natts);
get_result_datums(values, nulls, tupdesc->natts, attinmeta, pgres);
created = DatumGetBool(values[AttrNumberGetAttrOffset(Anum_create_chunk_created)]);
/*
* Sanity check the result. Use error rather than an assert since this
* is the result of a remote call to a data node that could potentially
* run a different version of the remote function than we'd expect.
*/
if (!created)
elog(ERROR, "chunk creation failed on data node \"%s\"", NameStr(cdn->fd.node_name));
if (nulls[AttrNumberGetAttrOffset(Anum_create_chunk_id)] ||
nulls[AttrNumberGetAttrOffset(Anum_create_chunk_schema_name)] ||
nulls[AttrNumberGetAttrOffset(Anum_create_chunk_table_name)])
elog(ERROR, "unexpected chunk creation result on data node");
schema_name =
DatumGetCString(values[AttrNumberGetAttrOffset(Anum_create_chunk_schema_name)]);
table_name = DatumGetCString(values[AttrNumberGetAttrOffset(Anum_create_chunk_table_name)]);
if (namestrcmp(&chunk->fd.schema_name, schema_name) != 0 ||
namestrcmp(&chunk->fd.table_name, table_name) != 0)
elog(ERROR, "remote chunk has mismatching schema or table name");
cdn->fd.node_chunk_id =
DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_create_chunk_id)]);
}
}
enum Anum_chunk_relstats
{
Anum_chunk_relstats_chunk_id = 1,
Anum_chunk_relstats_hypertable_id,
Anum_chunk_relstats_num_pages,
Anum_chunk_relstats_num_tuples,
Anum_chunk_relstats_num_allvisible,
_Anum_chunk_relstats_max,
};
/*
* Construct a tuple for the get_chunk_relstats SQL function.
*/
static HeapTuple
chunk_get_single_stats_tuple(Chunk *chunk, TupleDesc tupdesc)
{
HeapTuple ctup;
Form_pg_class pgcform;
Datum values[_Anum_chunk_relstats_max];
bool nulls[_Anum_chunk_relstats_max] = { false };
ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(chunk->table_id));
if (!HeapTupleIsValid(ctup))
elog(ERROR,
"pg_class entry for chunk \"%s.%s\" not found",
NameStr(chunk->fd.schema_name),
NameStr(chunk->fd.table_name));
pgcform = (Form_pg_class) GETSTRUCT(ctup);
values[AttrNumberGetAttrOffset(Anum_chunk_relstats_chunk_id)] = Int32GetDatum(chunk->fd.id);
values[AttrNumberGetAttrOffset(Anum_chunk_relstats_hypertable_id)] =
Int32GetDatum(chunk->fd.hypertable_id);
values[AttrNumberGetAttrOffset(Anum_chunk_relstats_num_pages)] =
Int32GetDatum(pgcform->relpages);
values[AttrNumberGetAttrOffset(Anum_chunk_relstats_num_tuples)] =
Float4GetDatum(pgcform->reltuples);
values[AttrNumberGetAttrOffset(Anum_chunk_relstats_num_allvisible)] =
Int32GetDatum(pgcform->relallvisible);
ReleaseSysCache(ctup);
return heap_form_tuple(tupdesc, values, nulls);
}
enum Anum_chunk_colstats
{
Anum_chunk_colstats_chunk_id = 1,
Anum_chunk_colstats_hypertable_id,
Anum_chunk_colstats_column_id,
Anum_chunk_colstats_nullfrac,
Anum_chunk_colstats_width,
Anum_chunk_colstats_distinct,
Anum_chunk_colstats_slot_kinds,
Anum_chunk_colstats_slot_op_strings,
Anum_chunk_colstats_slot_collations,
Anum_chunk_colstats_slot1_numbers,
Anum_chunk_colstats_slot2_numbers,
Anum_chunk_colstats_slot3_numbers,
Anum_chunk_colstats_slot4_numbers,
Anum_chunk_colstats_slot5_numbers,
Anum_chunk_colstats_slot_valtype_strings,
Anum_chunk_colstats_slot1_values,
Anum_chunk_colstats_slot2_values,
Anum_chunk_colstats_slot3_values,
Anum_chunk_colstats_slot4_values,
Anum_chunk_colstats_slot5_values,
_Anum_chunk_colstats_max,
};
/*
* It's not safe to send OIDs for operations or types between postgres instances for user defined
* types. Instead convert the operation OID to strings and then look up the local OID for the
* corresponding names on the other node. A single op OID will need 6 strings {op_name,
* op_namespace, larg_name, larg_namespace, rarg_name, rarg_namespace}
*/
#define STRINGS_PER_TYPE_OID 2
#define STRINGS_PER_OP_OID 6
enum StringArrayTypeIdx
{
ENCODED_TYPE_NAME = 0,
ENCODED_TYPE_NAMESPACE
};
enum OpArrayTypeIdx
{
ENCODED_OP_NAME = 0,
ENCODED_OP_NAMESPACE,
ENCODED_OP_LARG_NAME,
ENCODED_OP_LARG_NAMESPACE,
ENCODED_OP_RARG_NAME,
ENCODED_OP_RARG_NAMESPACE,
};
#define LargSubarrayForOpArray(op_string_array) (&op_string_array[ENCODED_OP_LARG_NAME])
#define RargSubarrayForOpArray(op_string_array) (&op_string_array[ENCODED_OP_RARG_NAME])
static void
convert_type_oid_to_strings(Oid type_id, Datum *result_strings)
{
Form_pg_type type;
Form_pg_namespace namespace;
HeapTuple namespace_tuple;
HeapTuple type_tuple;
type_tuple = SearchSysCache1(TYPEOID, type_id);
Assert(HeapTupleIsValid(type_tuple));
type = (Form_pg_type) GETSTRUCT(type_tuple);
result_strings[ENCODED_TYPE_NAME] = PointerGetDatum(pstrdup(type->typname.data));
namespace_tuple = SearchSysCache1(NAMESPACEOID, type->typnamespace);
Assert(HeapTupleIsValid(namespace_tuple));
namespace = (Form_pg_namespace) GETSTRUCT(namespace_tuple);
result_strings[ENCODED_TYPE_NAMESPACE] = PointerGetDatum(pstrdup(namespace->nspname.data));
ReleaseSysCache(namespace_tuple);
ReleaseSysCache(type_tuple);
}
static void
convert_op_oid_to_strings(Oid op_id, Datum *result_strings)
{
Form_pg_operator operator;
Form_pg_namespace namespace;
HeapTuple operator_tuple;
HeapTuple namespace_tuple;
operator_tuple = SearchSysCache1(OPEROID, op_id);
Assert(HeapTupleIsValid(operator_tuple));
operator=(Form_pg_operator) GETSTRUCT(operator_tuple);
result_strings[ENCODED_OP_NAME] = PointerGetDatum(pstrdup(operator->oprname.data));
namespace_tuple = SearchSysCache1(NAMESPACEOID, operator->oprnamespace);
Assert(HeapTupleIsValid(namespace_tuple));
namespace = (Form_pg_namespace) GETSTRUCT(namespace_tuple);
result_strings[ENCODED_OP_NAMESPACE] = PointerGetDatum(pstrdup(namespace->nspname.data));
ReleaseSysCache(namespace_tuple);
convert_type_oid_to_strings(operator->oprleft, LargSubarrayForOpArray(result_strings));
convert_type_oid_to_strings(operator->oprright, RargSubarrayForOpArray(result_strings));
ReleaseSysCache(operator_tuple);
}
static Oid
convert_strings_to_type_id(Datum *input_strings)
{
Oid arg_namespace = GetSysCacheOid1Compat(NAMESPACENAME,
Anum_pg_namespace_oid,
input_strings[ENCODED_TYPE_NAMESPACE]);
Oid result;
Assert(arg_namespace != InvalidOid);
result = GetSysCacheOid2Compat(TYPENAMENSP,
Anum_pg_type_oid,
input_strings[ENCODED_TYPE_NAME],
ObjectIdGetDatum(arg_namespace));
Assert(result != InvalidOid);
return result;
}
static Oid
convert_strings_to_op_id(Datum *input_strings)
{
Oid proc_namespace = GetSysCacheOid1Compat(NAMESPACENAME,
Anum_pg_namespace_oid,
input_strings[ENCODED_OP_NAMESPACE]);
Oid larg = convert_strings_to_type_id(LargSubarrayForOpArray(input_strings));
Oid rarg = convert_strings_to_type_id(RargSubarrayForOpArray(input_strings));
Oid result;
Assert(proc_namespace != InvalidOid);
result = GetSysCacheOid4Compat(OPERNAMENSP,
Anum_pg_operator_oid,
input_strings[ENCODED_OP_NAME],
ObjectIdGetDatum(larg),
ObjectIdGetDatum(rarg),
ObjectIdGetDatum(proc_namespace));
Assert(result != InvalidOid);
return result;
}
static void
collect_colstat_slots(const HeapTuple tuple, const Form_pg_statistic formdata, Datum *values,
bool *nulls)
{
/* Fetching the values and/or numbers fields for a slot requires knowledge of which fields are
* present for each kind of stats. Note that this doesn't support custom statistics.
*/
static const int statistic_kind_slot_fields[STATISTIC_KIND_BOUNDS_HISTOGRAM + 1] = {
0,
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS, /* MCV */
ATTSTATSSLOT_VALUES, /* HISTOGRAM */
ATTSTATSSLOT_NUMBERS, /* CORRELATION */
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS, /* MCELEM */
ATTSTATSSLOT_NUMBERS, /* DECHIST */
ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS, /* RANGE_LENGTH_HISTOGRAM */
ATTSTATSSLOT_VALUES /* BOUNDS_HISTOGRAM */
};
int i;
Datum slotkind[STATISTIC_NUM_SLOTS];
Datum op_strings[STRINGS_PER_OP_OID * STATISTIC_NUM_SLOTS];
Datum slot_collation[STATISTIC_NUM_SLOTS];
Datum value_type_strings[STRINGS_PER_TYPE_OID * STATISTIC_NUM_SLOTS];
ArrayType *array;
int nopstrings = 0;
int nvalstrings = 0;
for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
{
int16 kind = ((int16 *) (&formdata->stakind1))[i];
Datum slot_op = ObjectIdGetDatum(((Oid *) &formdata->staop1)[i]);
AttStatsSlot stat_slot;
int slot_fields;
const int numbers_idx = AttrNumberGetAttrOffset(Anum_chunk_colstats_slot1_numbers) + i;
const int values_idx = AttrNumberGetAttrOffset(Anum_chunk_colstats_slot1_values) + i;
#if PG12_GE
slot_collation[i] = ObjectIdGetDatum(((Oid *) &formdata->stacoll1)[i]);
#else
slot_collation[i] = 0;
#endif
slotkind[i] = ObjectIdGetDatum(kind);
if (kind == InvalidOid)
{
nulls[numbers_idx] = true;
nulls[values_idx] = true;
continue;
}
convert_op_oid_to_strings(slot_op, op_strings + nopstrings);
nopstrings += STRINGS_PER_OP_OID;
if (kind > STATISTIC_KIND_BOUNDS_HISTOGRAM)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unable to fetch user defined statistics from data nodes")));
slot_fields = statistic_kind_slot_fields[kind];
get_attstatsslot(&stat_slot, tuple, kind, InvalidOid, slot_fields);
if (slot_fields & ATTSTATSSLOT_NUMBERS)
{
Datum *stanumbers = palloc(sizeof(Datum) * stat_slot.nnumbers);
int j;
for (j = 0; j < stat_slot.nnumbers; j++)
stanumbers[j] = Float4GetDatum(stat_slot.numbers[j]);
array = construct_array(stanumbers,
stat_slot.nnumbers,
FLOAT4OID,
FLOAT4_TYPELEN,
FLOAT4_TYPEBYVAL,
FLOAT4_TYPEALIGN);
values[numbers_idx] = PointerGetDatum(array);
}
else
nulls[numbers_idx] = true;
if (slot_fields & ATTSTATSSLOT_VALUES)
{
Datum *encoded_value_ary = palloc0(sizeof(Datum) * stat_slot.nvalues);
HeapTuple type_tuple = SearchSysCache1(TYPEOID, stat_slot.valuetype);
Form_pg_type type;
int k;
Assert(HeapTupleIsValid(type_tuple));
type = (Form_pg_type) GETSTRUCT(type_tuple);
convert_type_oid_to_strings(stat_slot.valuetype, value_type_strings + nvalstrings);
nvalstrings += STRINGS_PER_TYPE_OID;
for (k = 0; k < stat_slot.nvalues; ++k)
encoded_value_ary[k] = OidFunctionCall1(type->typoutput, stat_slot.values[k]);
array = construct_array(encoded_value_ary,
stat_slot.nvalues,
CSTRINGOID,
CSTRING_TYPELEN,
CSTRING_TYPEBYVAL,
CSTRING_TYPEALIGN);
values[values_idx] = PointerGetDatum(array);
ReleaseSysCache(type_tuple);
}
else
nulls[values_idx] = true;
free_attstatsslot(&stat_slot);
}
array = construct_array(slotkind,
STATISTIC_NUM_SLOTS,
INT4OID,
INT4_TYPELEN,
INT4_TYPEBYVAL,
INT4_TYPEALIGN);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_kinds)] = PointerGetDatum(array);
array = construct_array(op_strings,
nopstrings,
CSTRINGOID,
CSTRING_TYPELEN,
CSTRING_TYPEBYVAL,
CSTRING_TYPEALIGN);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_op_strings)] = PointerGetDatum(array);
array = construct_array(slot_collation,
STATISTIC_NUM_SLOTS,
OIDOID,
OID_TYPELEN,
OID_TYPEBYVAL,
OID_TYPEALIGN);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_collations)] = PointerGetDatum(array);
array = construct_array(value_type_strings,
nvalstrings,
CSTRINGOID,
CSTRING_TYPELEN,
CSTRING_TYPEBYVAL,
CSTRING_TYPEALIGN);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_valtype_strings)] =
PointerGetDatum(array);
}
/*
* Construct a tuple for the get_chunk_relstats SQL function.
*/
static HeapTuple
chunk_get_single_colstats_tuple(Chunk *chunk, int column, TupleDesc tupdesc)
{
HeapTuple ctup;
Form_pg_statistic pgsform;
Datum values[_Anum_chunk_colstats_max];
bool nulls[_Anum_chunk_colstats_max] = { false };
bool dropped;
if (DatumGetBool(DirectFunctionCall1(row_security_active, ObjectIdGetDatum(chunk->table_id))))
return NULL;
ctup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(chunk->table_id), column);
if (!HeapTupleIsValid(ctup))
return NULL;
dropped = ((Form_pg_attribute) GETSTRUCT(ctup))->attisdropped;
ReleaseSysCache(ctup);
if (dropped)
return NULL;
if (!DatumGetBool(DirectFunctionCall3(has_column_privilege_id_attnum,
ObjectIdGetDatum(chunk->table_id),
Int16GetDatum(column),
PointerGetDatum(cstring_to_text("SELECT")))))
return NULL;
ctup = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(chunk->table_id), column, false);
/* pg_statistics will not have an entry for an unanalyzed table */
if (!HeapTupleIsValid(ctup))
return NULL;
pgsform = (Form_pg_statistic) GETSTRUCT(ctup);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_chunk_id)] = Int32GetDatum(chunk->fd.id);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_hypertable_id)] =
Int32GetDatum(chunk->fd.hypertable_id);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_column_id)] = Int32GetDatum(column);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_nullfrac)] =
Float4GetDatum(pgsform->stanullfrac);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_width)] = Int32GetDatum(pgsform->stawidth);
values[AttrNumberGetAttrOffset(Anum_chunk_colstats_distinct)] =
Float4GetDatum(pgsform->stadistinct);
collect_colstat_slots(ctup, pgsform, values, nulls);
ReleaseSysCache(ctup);
return heap_form_tuple(tupdesc, values, nulls);
}
static void
chunk_update_colstats(Chunk *chunk, int16 attnum, float nullfract, int32 width, float distinct,
ArrayType *kind_array, ArrayType *collations, Oid *slot_ops,
ArrayType **slot_numbers, Oid *value_kinds, ArrayType **slot_values)
{
Relation rel;
Relation sd;
Datum values[Natts_pg_statistic];
bool nulls[Natts_pg_statistic];
bool replaces[Natts_pg_statistic];
HeapTuple stup;
HeapTuple oldtup;
int i, k;
int *slot_kinds;
rel = try_relation_open(chunk->table_id, ShareUpdateExclusiveLock);
/* If a vacuum is running we might not be able to grab the lock, so just
* raise an error and let the user try again. */
if (NULL == rel)
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("unable to acquire table lock to update column statistics on \"%s\"",
NameStr(chunk->fd.table_name))));
sd = relation_open(StatisticRelationId, RowExclusiveLock);
memset(nulls, false, Natts_pg_statistic);
memset(replaces, true, Natts_pg_statistic);
values[AttrNumberGetAttrOffset(Anum_pg_statistic_starelid)] = ObjectIdGetDatum(rel->rd_id);
values[AttrNumberGetAttrOffset(Anum_pg_statistic_staattnum)] = Int16GetDatum(attnum);
values[AttrNumberGetAttrOffset(Anum_pg_statistic_stainherit)] = BoolGetDatum(false);
values[AttrNumberGetAttrOffset(Anum_pg_statistic_stanullfrac)] = Float4GetDatum(nullfract);
values[AttrNumberGetAttrOffset(Anum_pg_statistic_stawidth)] = Int32GetDatum(width);
values[AttrNumberGetAttrOffset(Anum_pg_statistic_stadistinct)] = Float4GetDatum(distinct);
i = AttrNumberGetAttrOffset(Anum_pg_statistic_stakind1);
slot_kinds = (int *) ARR_DATA_PTR(kind_array);
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
values[i++] = Int16GetDatum(slot_kinds[k]); /* stakindN */
#if PG12_GE
i = AttrNumberGetAttrOffset(Anum_pg_statistic_stacoll1);
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
values[i++] = ObjectIdGetDatum(((Oid *) ARR_DATA_PTR(collations))[k]); /* stacollN */
#endif
i = AttrNumberGetAttrOffset(Anum_pg_statistic_staop1);
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
values[i++] = ObjectIdGetDatum(slot_ops[k]); /* staopN */
i = AttrNumberGetAttrOffset(Anum_pg_statistic_stanumbers1);
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
if (slot_numbers[k] == NULL)
nulls[i++] = true;
else
values[i++] = PointerGetDatum(slot_numbers[k]); /* stanumbersN */
i = AttrNumberGetAttrOffset(Anum_pg_statistic_stavalues1);
for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
{
Oid value_oid = value_kinds[k];
HeapTuple type_tuple;
Form_pg_type type;
int idx;
int nelems;
Datum *decoded_data;
if (value_oid == InvalidOid)
{
nulls[i++] = true;
continue;
}
type_tuple = SearchSysCache1(TYPEOID, value_oid);
Assert(HeapTupleIsValid(type_tuple));
type = (Form_pg_type) GETSTRUCT(type_tuple);
Assert(slot_values[k] != NULL);
nelems = ARR_DIMS(slot_values[k])[0];
decoded_data = palloc0(nelems * sizeof(Datum));
for (idx = 1; idx <= nelems; ++idx)
{
bool isnull;
Datum d = array_get_element(PointerGetDatum(slot_values[k]),
1,
&idx,
CSTRING_ARY_TYPELEN,
CSTRING_TYPELEN,
CSTRING_TYPEBYVAL,
CSTRING_TYPEALIGN,
&isnull);
Assert(!isnull);
decoded_data[idx - 1] =
OidFunctionCall3(type->typinput, d, type->typelem, type->typtypmod);
}
values[i++] = PointerGetDatum(construct_array(decoded_data,
nelems,
value_oid,
type->typlen,
type->typbyval,
type->typalign));
ReleaseSysCache(type_tuple);
}
oldtup = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(rel->rd_id),
Int16GetDatum(attnum),
BoolGetDatum(false));
if (HeapTupleIsValid(oldtup))
{
stup = heap_modify_tuple(oldtup, RelationGetDescr(sd), values, nulls, replaces);
CatalogTupleUpdate(sd, &oldtup->t_self, stup);
ReleaseSysCache(oldtup);
}
else
{
stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
CatalogTupleInsert(sd, stup);
}
heap_freetuple(stup);
relation_close(sd, RowExclusiveLock);
relation_close(rel, ShareUpdateExclusiveLock);
}
/*
* StatsProcessContext filters out duplicate stats from replica chunks.
*
* When processing chunk stats from data nodes, we might receive the same
* stats from multiple data nodes when native replication is enabled. With the
* StatsProcessContext we can filter out the duplicates, and ensure we only
* add the stats once. Without the filtering, we will get errors (e.g., unique
* violations).
*
* We could elide the filtering if we requested stats only for the chunks that
* are "primary", but that requires the ability to specify the specific remote
* chunks to retrieve stats for rather than specifying "all chunks" for the
* given hypertable.
*/
typedef struct ChunkAttKey
{
Oid chunk_relid;
Index attnum;
} ChunkAttKey;
typedef struct StatsProcessContext
{
HTAB *htab;
} StatsProcessContext;
static void
stats_process_context_init(StatsProcessContext *ctx, long nstats)
{
HASHCTL ctl;
MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(ChunkAttKey);
ctl.entrysize = sizeof(ChunkAttKey);
ctl.hcxt = CurrentMemoryContext;
ctx->htab =
hash_create("StatsProcessContext", nstats, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
}
static bool
stats_process_context_add_chunk_attributed(StatsProcessContext *ctx, Oid relid, Index attnum)
{
ChunkAttKey key = {
.chunk_relid = relid,
.attnum = attnum,
};
ChunkAttKey *entry;
bool found;
entry = hash_search(ctx->htab, &key, HASH_ENTER, &found);
if (!found)
{
entry->chunk_relid = relid;
entry->attnum = attnum;
}
return found;
}
static void
stats_process_context_finish(StatsProcessContext *ctx)
{
hash_destroy(ctx->htab);
}
static void
chunk_process_remote_colstats_row(StatsProcessContext *ctx, TupleFactory *tf, TupleDesc tupdesc,
PGresult *res, int row, const char *node_name)
{
Datum values[_Anum_chunk_colstats_max];
bool nulls[_Anum_chunk_colstats_max] = { false };
HeapTuple tuple;
int32 chunk_id;
ChunkDataNode *cdn;
Chunk *chunk;
int32 col_id;
float nullfract;
int32 width;
float distinct;
ArrayType *kind_array;
ArrayType *collation_array;
Datum op_strings;
Oid op_oids[STATISTIC_NUM_SLOTS];
ArrayType *number_arrays[STATISTIC_NUM_SLOTS];
Datum valtype_strings;
Oid valtype_oids[STATISTIC_NUM_SLOTS];
ArrayType *value_arrays[STATISTIC_NUM_SLOTS];
int *slot_kinds;
int i, os_idx, vt_idx;
tuple = tuplefactory_make_tuple(tf, res, row, PQbinaryTuples(res));
heap_deform_tuple(tuple, tupdesc, values, nulls);
chunk_id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_relstats_chunk_id)]);
cdn = ts_chunk_data_node_scan_by_remote_chunk_id_and_node_name(chunk_id,
node_name,
CurrentMemoryContext);
chunk = ts_chunk_get_by_id(cdn->fd.chunk_id, true);
col_id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_colstats_column_id)]);
nullfract = DatumGetFloat4(values[AttrNumberGetAttrOffset(Anum_chunk_colstats_nullfrac)]);
width = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_colstats_width)]);
distinct = DatumGetFloat4(values[AttrNumberGetAttrOffset(Anum_chunk_colstats_distinct)]);
kind_array =
DatumGetArrayTypeP(values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_kinds)]);
collation_array =
DatumGetArrayTypeP(values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_collations)]);
op_strings = values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_op_strings)];
valtype_strings = values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot_valtype_strings)];
slot_kinds = (int *) ARR_DATA_PTR(kind_array);
os_idx = 1;
vt_idx = 1;
/* Filter out chunk cols we've already added. This happens when there are
* replica chunks */
if (stats_process_context_add_chunk_attributed(ctx, chunk->table_id, col_id))
return;
for (i = 0; i < STATISTIC_NUM_SLOTS; ++i)
{
Datum strings[STRINGS_PER_OP_OID];
Datum d;
int k;
op_oids[i] = InvalidOid;
number_arrays[i] = NULL;
value_arrays[i] = NULL;
valtype_oids[i] = InvalidOid;
if (slot_kinds[i] == InvalidOid)
continue;
for (k = 0; k < STRINGS_PER_OP_OID; ++k)
{
bool isnull;
strings[k] = array_get_element(op_strings,
1,
&os_idx,
CSTRING_ARY_TYPELEN,
CSTRING_TYPELEN,
CSTRING_TYPEBYVAL,
CSTRING_TYPEALIGN,
&isnull);
Assert(!isnull);
++os_idx;
}
op_oids[i] = convert_strings_to_op_id(strings);
d = values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot1_numbers) + i];
if (DatumGetPointer(d) != NULL)
number_arrays[i] = DatumGetArrayTypeP(d);
d = values[AttrNumberGetAttrOffset(Anum_chunk_colstats_slot1_values) + i];
if (DatumGetPointer(d) != NULL)
{
value_arrays[i] = DatumGetArrayTypeP(d);
for (k = 0; k < STRINGS_PER_TYPE_OID; ++k)
{
bool isnull;
strings[k] = array_get_element(valtype_strings,
1,
&vt_idx,
CSTRING_ARY_TYPELEN,
CSTRING_TYPELEN,
CSTRING_TYPEBYVAL,
CSTRING_TYPEALIGN,
&isnull);
Assert(!isnull);
++vt_idx;
}
valtype_oids[i] = convert_strings_to_type_id(strings);
}
}
chunk_update_colstats(chunk,
col_id,
nullfract,
width,
distinct,
kind_array,
collation_array,
op_oids,
number_arrays,
valtype_oids,
value_arrays);
}
/*
* Update the stats in the pg_class catalog entry for a chunk.
*
* Similar to code for vacuum/analyze.
*
* We do not update pg_class.relhasindex because vac_update_relstats() only
* sets that field if it reverts back to false (see internal implementation).
*/
static void
chunk_update_relstats(Chunk *chunk, int32 num_pages, float num_tuples, int32 num_allvisible)
{
Relation rel;
rel = try_relation_open(chunk->table_id, ShareUpdateExclusiveLock);
/* If a vacuum is running we might not be able to grab the lock, so just
* raise an error and let the user try again. */
if (NULL == rel)
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("skipping relstats update of \"%s\" --- lock not available",
NameStr(chunk->fd.table_name))));
vac_update_relstats(rel,
num_pages,
num_tuples,
num_allvisible,
true,
InvalidTransactionId,
InvalidMultiXactId,
false);
relation_close(rel, ShareUpdateExclusiveLock);
}
static void
chunk_process_remote_relstats_row(TupleFactory *tf, TupleDesc tupdesc, PGresult *res, int row,
const char *node_name)
{
Datum values[_Anum_chunk_relstats_max];
bool nulls[_Anum_chunk_relstats_max] = { false };
HeapTuple tuple;
int32 chunk_id;
ChunkDataNode *cdn;
Chunk *chunk;
int32 num_pages;
float num_tuples;
int32 num_allvisible;
tuple = tuplefactory_make_tuple(tf, res, row, PQbinaryTuples(res));
heap_deform_tuple(tuple, tupdesc, values, nulls);
chunk_id = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_relstats_chunk_id)]);
cdn = ts_chunk_data_node_scan_by_remote_chunk_id_and_node_name(chunk_id,
node_name,
CurrentMemoryContext);
chunk = ts_chunk_get_by_id(cdn->fd.chunk_id, true);
num_pages = DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_relstats_num_pages)]);
num_tuples = DatumGetFloat4(values[AttrNumberGetAttrOffset(Anum_chunk_relstats_num_tuples)]);
num_allvisible =
DatumGetInt32(values[AttrNumberGetAttrOffset(Anum_chunk_relstats_num_allvisible)]);
chunk_update_relstats(chunk, num_pages, num_tuples, num_allvisible);
}
/*
* Fetch chunk relation or column stats from remote data nodes.
*
* This will remotely fetch, and locally update, relation stats (relpages,
* reltuples, relallvisible in pg_class) for all chunks in a distributed
* hypertable.
*
* For relation stats, we do not fetch 'relhasindex' because there is no way to set it
* using vac_update_relstats() unless the values reverts back to 'false' (see
* internal implementation of PG's vac_update_relstats).
*
* Note that we currently fetch stats from all chunk replicas, i.e., we might
* fetch stats for a local chunk multiple times (once for each
* replica). Presumably, stats should be the same for all replicas, but they
* might vary if ANALYZE didn't run recently on the data node. We currently
* don't care, however, and the "last" chunk replica will win w.r.t. which
* stats will take precedence. We might consider optimizing this in the
* future.
*/
static void
fetch_remote_chunk_stats(Hypertable *ht, FunctionCallInfo fcinfo, bool col_stats)
{
StatsProcessContext statsctx;
DistCmdResult *cmdres;
TupleDesc tupdesc;
TupleFactory *tf;
Size i;
long num_rows;
long num_stats;
Assert(hypertable_is_distributed(ht));
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")));
cmdres = ts_dist_cmd_invoke_func_call_on_all_data_nodes(fcinfo);
/* Expect TEXT response format since dist command API currently defaults
* to requesting TEXT */
tf = tuplefactory_create_for_tupdesc(tupdesc, true);
num_rows = ts_dist_cmd_total_row_count(cmdres);
/* Estimate the number of non-duplicate stats to use for initial size of
* StatsProcessContext. Use slightly bigger than strictly necessary to
* avoid a resize. */
num_stats = (5 * num_rows) / (ht->fd.replication_factor * 4);
stats_process_context_init(&statsctx, num_stats);
for (i = 0; /* exit when res == NULL below */; i++)
{
PGresult *res;
const char *node_name;
int row;
res = ts_dist_cmd_get_result_by_index(cmdres, i, &node_name);
if (NULL == res)
break;
if (col_stats)
for (row = 0; row < PQntuples(res); row++)
chunk_process_remote_colstats_row(&statsctx, tf, tupdesc, res, row, node_name);
else
for (row = 0; row < PQntuples(res); row++)
chunk_process_remote_relstats_row(tf, tupdesc, res, row, node_name);
/* Early cleanup of PGresult protects against ballooning memory usage
* when there are a lot of rows */
ts_dist_cmd_clear_result_by_index(cmdres, i);
}
stats_process_context_finish(&statsctx);
ts_dist_cmd_close_response(cmdres);
}
/*
* Implementation marked unused in PostgresQL lsyscache.c
*/
static int
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;
}
static void *
chunk_api_generate_relstats_context(List *oids)
{
return list_copy(oids);
}
static HeapTuple
chunk_api_fetch_next_relstats_tuple(FuncCallContext *funcctx)
{
List *chunk_oids = (List *) funcctx->user_fctx;
Oid relid;
Chunk *chunk;
if (chunk_oids == NIL)
return NULL;
relid = linitial_oid(chunk_oids);
chunk = ts_chunk_get_by_relid(relid, true);
return chunk_get_single_stats_tuple(chunk, funcctx->tuple_desc);
}
static void
chunk_api_iterate_relstats_context(FuncCallContext *funcctx)
{
List *chunk_oids = (List *) funcctx->user_fctx;
MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
chunk_oids = list_delete_first(chunk_oids);
funcctx->user_fctx = chunk_oids;
MemoryContextSwitchTo(oldcontext);
}
typedef struct ColStatContext
{
List *chunk_oids;
int col_id;
int nattrs;
} ColStatContext;
static void *
chunk_api_generate_colstats_context(List *oids, Oid ht_relid)
{
ColStatContext *ctx = palloc0(sizeof(ColStatContext));
ctx->chunk_oids = list_copy(oids);
ctx->col_id = 1;
ctx->nattrs = get_relnatts(ht_relid);
return ctx;
}
static HeapTuple
chunk_api_fetch_next_colstats_tuple(FuncCallContext *funcctx)
{
ColStatContext *ctx = funcctx->user_fctx;
HeapTuple tuple = NULL;
MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
while (tuple == NULL && ctx->chunk_oids != NIL)
{
Oid relid = linitial_oid(ctx->chunk_oids);
Chunk *chunk = ts_chunk_get_by_relid(relid, true);
tuple = chunk_get_single_colstats_tuple(chunk, ctx->col_id, funcctx->tuple_desc);
/* This loop looks a bit odd as col_id tracks the postgres column id, which is indexed
* starting at 1 */
while (tuple == NULL && ctx->col_id < ctx->nattrs)
{
++ctx->col_id;
tuple = chunk_get_single_colstats_tuple(chunk, ctx->col_id, funcctx->tuple_desc);
}
if (tuple == NULL)
{
ctx->chunk_oids = list_delete_first(ctx->chunk_oids);
ctx->col_id = 1;
}
}
MemoryContextSwitchTo(oldcontext);
return tuple;
}
static void
chunk_api_iterate_colstats_context(FuncCallContext *funcctx)
{
ColStatContext *ctx = funcctx->user_fctx;
MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
++ctx->col_id;
if (AttrNumberGetAttrOffset(ctx->col_id) >= ctx->nattrs)
{
ctx->chunk_oids = list_delete_first(ctx->chunk_oids);
ctx->col_id = 1;
}
MemoryContextSwitchTo(oldcontext);
}
#define GET_CHUNK_RELSTATS_NAME "get_chunk_relstats"
#define GET_CHUNK_COLSTATS_NAME "get_chunk_colstats"
/*
* Fetch and locally update stats for a distributed hypertable by table id.
*
* This function reconstructs the fcinfo argument of the
* chunk_api_get_chunk_stats() function in order to call
* fetch_remote_chunk_stats() and execute either
* _timescaledb_internal.get_chunk_relstats() or
* _timescaledb_internal.get_chunk_colstats() remotely (depending on the
* passed `col_stats` bool).
*/
static void
chunk_api_update_distributed_hypertable_chunk_stats(Oid table_id, bool col_stats)
{
Cache *hcache;
Hypertable *ht;
LOCAL_FCINFO(fcinfo, 1);
FmgrInfo flinfo;
Oid funcoid;
Oid get_chunk_stats_argtypes[1] = { REGCLASSOID };
hcache = ts_hypertable_cache_pin();
ht = ts_hypertable_cache_get_entry(hcache, table_id, CACHE_FLAG_NONE);
if (!hypertable_is_distributed(ht))
ereport(ERROR,
(errcode(ERRCODE_TS_HYPERTABLE_NOT_DISTRIBUTED),
errmsg("hypertable \"%s\" is not distributed", get_rel_name(table_id))));
/* Prepare fcinfo context for remote execution of _timescaledb_internal.get_chunk_relstats() */
funcoid = ts_get_function_oid(col_stats ? GET_CHUNK_COLSTATS_NAME : GET_CHUNK_RELSTATS_NAME,
INTERNAL_SCHEMA_NAME,
1,
get_chunk_stats_argtypes);
fmgr_info_cxt(funcoid, &flinfo, CurrentMemoryContext);
InitFunctionCallInfoData(*fcinfo, &flinfo, 1, InvalidOid, NULL, NULL);
FC_ARG(fcinfo, 0) = ObjectIdGetDatum(table_id);
FC_NULL(fcinfo, 0) = false;
fetch_remote_chunk_stats(ht, fcinfo, col_stats);
CommandCounterIncrement();
ts_cache_release(hcache);
}
void
chunk_api_update_distributed_hypertable_stats(Oid table_id)
{
/* Update stats for hypertable */
chunk_api_update_distributed_hypertable_chunk_stats(table_id, false);
chunk_api_update_distributed_hypertable_chunk_stats(table_id, true);
}
/*
* Get relation stats or column stats for chunks.
*
* This function takes a hypertable or chunk as input (regclass). In case of a
* hypertable, it will get the relstats for all the chunks in the hypertable,
* otherwise only the given chunk.
*
* If a hypertable is distributed, the function will first refresh the local
* chunk stats by fetching stats from remote data nodes.
*/
static Datum
chunk_api_get_chunk_stats(FunctionCallInfo fcinfo, bool col_stats)
{
FuncCallContext *funcctx;
HeapTuple tuple;
if (SRF_IS_FIRSTCALL())
{
Oid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
MemoryContext oldcontext;
TupleDesc tupdesc;
Cache *hcache;
Hypertable *ht;
List *chunk_oids = NIL;
Oid ht_relid = InvalidOid;
if (!OidIsValid(relid))
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid table")));
ht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);
if (NULL == ht)
{
Chunk *chunk = ts_chunk_get_by_relid(relid, false);
if (NULL == chunk)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("must be a hypertable or chunk")));
chunk_oids = list_make1_oid(chunk->table_id);
/* We'll need the hypertable if fetching column stats */
if (col_stats)
ht = ts_hypertable_get_by_id(chunk->fd.hypertable_id);
}
else
{
if (hypertable_is_distributed(ht))
{
/* If this is a distributed hypertable, we fetch stats from
* remote nodes */
fetch_remote_chunk_stats(ht, fcinfo, col_stats);
/* Make updated stats visible so that we can retreive them locally below */
CommandCounterIncrement();
}
chunk_oids = find_inheritance_children(relid, NoLock);
}
if (ht)
ht_relid = ht->main_table_relid;
ts_cache_release(hcache);
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
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")));
/* Save the chunk oid list on the multi-call memory context so that it
* survives across multiple calls to this function (until SRF is
* done). */
funcctx->user_fctx = col_stats ? chunk_api_generate_colstats_context(chunk_oids, ht_relid) :
chunk_api_generate_relstats_context(chunk_oids);
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
MemoryContextSwitchTo(oldcontext);
}
funcctx = SRF_PERCALL_SETUP();
tuple = col_stats ? chunk_api_fetch_next_colstats_tuple(funcctx) :
chunk_api_fetch_next_relstats_tuple(funcctx);
if (tuple == NULL)
SRF_RETURN_DONE(funcctx);
if (col_stats)
chunk_api_iterate_colstats_context(funcctx);
else
chunk_api_iterate_relstats_context(funcctx);
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
}
Datum
chunk_api_get_chunk_colstats(PG_FUNCTION_ARGS)
{
return chunk_api_get_chunk_stats(fcinfo, true);
}
Datum
chunk_api_get_chunk_relstats(PG_FUNCTION_ARGS)
{
return chunk_api_get_chunk_stats(fcinfo, false);
}