Implement drop_chunks in C

Remove the existing PLPGSQL function that implements drop_chunks, replacing it with a direct call to the C function, which also implements the old PLPGSQL checks in C. Refactor out much of the code shared between the C implementations of show_chunks and drop_chunks.
This commit is contained in:
Amy Tai 2018-11-28 15:10:40 -05:00 committed by amytai
parent 62571e2a56
commit 83014ee2b0
7 changed files with 187 additions and 146 deletions

View File

@ -64,27 +64,17 @@ CREATE OR REPLACE FUNCTION set_number_partitions(
dimension_name NAME = NULL
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_num_slices' LANGUAGE C VOLATILE;
-- Drop chunks that are older than a timestamp or newer than a timestamp.
-- Drop chunks older than the given timestamp. If a hypertable name is given,
-- drop only chunks associated with this table. Any of the first three arguments
-- can be NULL meaning "all values".
CREATE OR REPLACE FUNCTION drop_chunks(
older_than ANYELEMENT = NULL,
older_than "any" = NULL,
table_name NAME = NULL,
schema_name NAME = NULL,
cascade BOOLEAN = FALSE,
newer_than ANYELEMENT = NULL
)
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
older_than_internal BIGINT;
newer_than_internal BIGINT;
BEGIN
IF older_than IS NULL AND newer_than IS NULL THEN
RAISE 'older_than and newer_than timestamps provided to drop_chunks cannot both be NULL';
END IF;
PERFORM _timescaledb_internal.drop_chunks_impl(older_than, table_name, schema_name, cascade,
newer_than_time => newer_than);
END
$BODY$;
newer_than "any" = NULL
) RETURNS SETOF REGCLASS AS '@MODULE_PATHNAME@', 'ts_chunk_drop_chunks'
LANGUAGE C STABLE PARALLEL SAFE;
-- show chunks older than or newer than a specific time.
-- `hypertable` argument can be a valid hypertable or NULL.

View File

@ -3,72 +3,14 @@
-- This file is licensed under the Apache License, see LICENSE-APACHE
-- at the top level directory of the TimescaleDB distribution.
-- show_chunks for internal use only. The only difference
-- from user-facing API is that this one takes a 4th argument
-- specifying the caller name. This makes it easier to taylor
-- error messages to the caller function context.
CREATE OR REPLACE FUNCTION _timescaledb_internal.show_chunks_impl(
CREATE OR REPLACE FUNCTION _timescaledb_internal.drop_chunks_impl(
hypertable REGCLASS = NULL,
older_than "any" = NULL,
newer_than "any" = NULL,
caller_name NAME = NULL
) RETURNS SETOF REGCLASS AS '@MODULE_PATHNAME@', 'ts_chunk_show_chunks'
cascade BOOLEAN = FALSE
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_chunk_drop_chunks'
LANGUAGE C STABLE PARALLEL SAFE;
-- Drop chunks older than the given timestamp. If a hypertable name is given,
-- drop only chunks associated with this table. Any of the first three arguments
-- can be NULL meaning "all values".
CREATE OR REPLACE FUNCTION _timescaledb_internal.drop_chunks_impl(
older_than_time ANYELEMENT = NULL,
table_name NAME = NULL,
schema_name NAME = NULL,
cascade BOOLEAN = FALSE,
newer_than_time ANYELEMENT = NULL
)
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
chunk_row REGCLASS;
cascade_mod TEXT = '';
exist_count INT = 0;
BEGIN
IF cascade THEN
cascade_mod = 'CASCADE';
END IF;
IF table_name IS NOT NULL THEN
SELECT COUNT(*)
FROM _timescaledb_catalog.hypertable h
WHERE (drop_chunks_impl.schema_name IS NULL OR h.schema_name = drop_chunks_impl.schema_name)
AND drop_chunks_impl.table_name = h.table_name
INTO STRICT exist_count;
IF exist_count = 0 THEN
RAISE 'hypertable "%" does not exist', drop_chunks_impl.table_name
USING ERRCODE = 'TS001';
END IF;
END IF;
FOR schema_name, table_name IN
SELECT hyper.schema_name, hyper.table_name
FROM _timescaledb_catalog.hypertable hyper
WHERE
(drop_chunks_impl.schema_name IS NULL OR hyper.schema_name = drop_chunks_impl.schema_name) AND
(drop_chunks_impl.table_name IS NULL OR hyper.table_name = drop_chunks_impl.table_name)
LOOP
FOR chunk_row IN SELECT _timescaledb_internal.show_chunks_impl(schema_name || '.' || table_name, older_than_time, newer_than_time, 'drop_chunks')
LOOP
EXECUTE format(
$$
DROP TABLE %s %s
$$, chunk_row, cascade_mod
);
END LOOP;
END LOOP;
END
$BODY$;
--documentation of these function located in chunk_index.h
CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_index_clone(chunk_index_oid OID) RETURNS OID
AS '@MODULE_PATHNAME@', 'ts_chunk_index_clone' LANGUAGE C VOLATILE STRICT;

View File

@ -39,6 +39,7 @@
#include "dimension.h"
#include "dimension_slice.h"
#include "dimension_vector.h"
#include "errors.h"
#include "partitioning.h"
#include "hypertable.h"
#include "hypercube.h"
@ -51,6 +52,7 @@
#include "cache.h"
TS_FUNCTION_INFO_V1(ts_chunk_show_chunks);
TS_FUNCTION_INFO_V1(ts_chunk_drop_chunks);
/* Used when processing scanned chunks */
typedef enum ChunkResult
@ -65,7 +67,7 @@ static void chunk_scan_ctx_init(ChunkScanCtx *ctx, Hyperspace *hs, Point *p);
static void chunk_scan_ctx_destroy(ChunkScanCtx *ctx);
static void chunk_collision_scan(ChunkScanCtx *scanctx, Hypercube *cube);
static int chunk_scan_ctx_foreach_chunk(ChunkScanCtx *ctx, on_chunk_func on_chunk, uint16 limit);
static void chunk_show_chunks_first_call(FunctionCallInfo fcinfo);
static Chunk **chunk_get_chunks_in_time_range(Oid table_relid, Datum older_than_datum, Datum newer_than_datum, Oid older_than_type, Oid newer_than_type, char *caller_name, MemoryContext mctx, uint64 *num_chunks_returned);
static Datum chunks_return_srf(FunctionCallInfo fcinfo);
static int chunk_cmp(const void *ch1, const void *ch2);
@ -1041,7 +1043,7 @@ chunks_find_all_in_range_limit(Hyperspace *hs,
StrategyNumber end_strategy,
int64 end_value,
int limit,
long *num_found)
uint64 *num_found)
{
ChunkScanCtx *ctx = palloc(sizeof(ChunkScanCtx));
DimensionVec *slices;
@ -1081,7 +1083,7 @@ chunks_typecheck_and_find_all_in_range_limit(Hyperspace *hs,
int limit,
MemoryContext multi_call_memory_ctx,
char *caller_name,
long *num_found)
uint64 *num_found)
{
ChunkScanCtx *chunk_ctx = NULL;
int64 older_than = -1;
@ -1197,20 +1199,36 @@ ts_chunk_show_chunks(PG_FUNCTION_ARGS)
* after doing some computation first
*/
if (SRF_IS_FIRSTCALL())
chunk_show_chunks_first_call(fcinfo);
{
FuncCallContext *funcctx;
Oid table_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
Datum older_than_datum = PG_GETARG_DATUM(1);
Datum newer_than_datum = PG_GETARG_DATUM(2);
/*
* get_fn_expr_argtype defaults to UNKNOWNOID if argument is NULL but
* making it InvalidOid makes the logic simpler later
*/
Oid older_than_type = PG_ARGISNULL(1) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 1);
Oid newer_than_type = PG_ARGISNULL(2) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 2);
funcctx = SRF_FIRSTCALL_INIT();
funcctx->user_fctx = chunk_get_chunks_in_time_range(table_relid, older_than_datum, newer_than_datum, older_than_type, newer_than_type, "show_chunks", funcctx->multi_call_memory_ctx, &funcctx->max_calls);
}
return chunks_return_srf(fcinfo);
}
/* Implementation of show_chunks logic that is carried out on the first call of
* show_chunks set returning function
* */
static void
chunk_show_chunks_first_call(FunctionCallInfo fcinfo)
static Chunk **
chunk_get_chunks_in_time_range(Oid table_relid, Datum older_than_datum, Datum newer_than_datum, Oid older_than_type, Oid newer_than_type, char *caller_name, MemoryContext mctx, uint64 *num_chunks_returned)
{
FuncCallContext *funcctx;
char *caller_name;
ListCell *lc;
MemoryContext oldcontext;
ChunkScanCtx **chunk_scan_ctxs;
Chunk **chunks;
Chunk **current;
Cache *hypertable_cache;
Hypertable *ht;
Dimension *time_dim;
@ -1218,45 +1236,12 @@ chunk_show_chunks_first_call(FunctionCallInfo fcinfo)
/*
* contains the list of hypertables which need to be considred. this is a
* list containing a single hypertable if PG_ARGISNULL(0) is false.
* Otherwise, it will have the list of all hypertables in the system
* list containing a single hypertable if we are passed an invalid table
* OID. Otherwise, it will have the list of all hypertables in the system
*/
List *hypertables = NIL;
ListCell *lc;
int ht_index = 0;
long num_chunks = 0;
MemoryContext oldcontext;
ChunkScanCtx **chunk_scan_ctxs;
Chunk **chunks;
Chunk **current;
Oid table_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
Datum older_than_datum = PG_GETARG_DATUM(1);
Datum newer_than_datum = PG_GETARG_DATUM(2);
/*
* get_fn_expr_argtype defaults to UNKNOWNOID if argument is NULL but
* making it InvalidOid makes the logic simpler later
*/
Oid older_than_type = PG_ARGISNULL(1) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 1);
Oid newer_than_type = PG_ARGISNULL(2) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 2);
funcctx = SRF_FIRSTCALL_INIT();
/*
* The caller name is passed as argument so error messages refer to the
* function that the user actually called. This is necessary because
* show_chunk_impl is used by drop_chunks and export_chunks
*/
caller_name = PG_NARGS() <= 3 ? "show_chunks" : NameStr(*PG_GETARG_NAME(3));
if (PG_NARGS() > 3 && PG_ARGISNULL(3))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("When calling the internal function for show_chunks "
"caller_name cannot be null")
));
uint64 num_chunks = 0;
if (older_than_type != InvalidOid &&
newer_than_type != InvalidOid &&
@ -1271,7 +1256,7 @@ chunk_show_chunks_first_call(FunctionCallInfo fcinfo)
* returned will still be valid in foreach block below
*/
hypertable_cache = ts_hypertable_cache_pin();
if (PG_ARGISNULL(0))
if (!OidIsValid(table_relid))
{
hypertables = ts_hypertable_get_all();
}
@ -1287,7 +1272,7 @@ chunk_show_chunks_first_call(FunctionCallInfo fcinfo)
hypertables = list_make1(ht);
}
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
oldcontext = MemoryContextSwitchTo(mctx);
chunk_scan_ctxs = palloc(sizeof(ChunkScanCtx *) * list_length(hypertables));
MemoryContextSwitchTo(oldcontext);
foreach(lc, hypertables)
@ -1326,12 +1311,12 @@ chunk_show_chunks_first_call(FunctionCallInfo fcinfo)
newer_than_datum,
newer_than_type,
-1,
funcctx->multi_call_memory_ctx,
mctx,
caller_name,
&num_chunks);
}
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
oldcontext = MemoryContextSwitchTo(mctx);
/*
* num_chunks can safely be 0 as palloc protects against unportable
@ -1357,9 +1342,10 @@ chunk_show_chunks_first_call(FunctionCallInfo fcinfo)
}
qsort(chunks, num_chunks, sizeof(Chunk *), chunk_cmp);
funcctx->user_fctx = chunks;
funcctx->max_calls = num_chunks;
*num_chunks_returned = num_chunks;
ts_cache_release(hypertable_cache);
return chunks;
}
Chunk *
@ -1860,3 +1846,67 @@ chunks_return_srf(FunctionCallInfo fcinfo)
else /* do when there is no more left */
SRF_RETURN_DONE(funcctx);
}
static void
do_drop_chunks(Oid table_relid, Datum older_than_datum, Datum newer_than_datum, Oid older_than_type, Oid newer_than_type, bool cascade)
{
int i = 0;
uint64 num_chunks = 0;
Chunk **chunks = chunk_get_chunks_in_time_range(table_relid, older_than_datum, newer_than_datum, older_than_type, newer_than_type, "drop_chunks", CurrentMemoryContext, &num_chunks);
for (; i < num_chunks; i++)
{
ObjectAddress objaddr = {
.classId = RelationRelationId,
.objectId = chunks[i]->table_id,
};
/* Remove the chunk from the hypertable table */
ts_chunk_delete_by_relid(chunks[i]->table_id);
/* Drop the table */
performDeletion(&objaddr, cascade, 0);
}
}
Datum
ts_chunk_drop_chunks(PG_FUNCTION_ARGS)
{
ListCell *lc;
List *ht_oids;
Name table_name = PG_ARGISNULL(1) ? NULL : PG_GETARG_NAME(1);
Name schema_name = PG_ARGISNULL(2) ? NULL : PG_GETARG_NAME(2);
Datum older_than_datum = PG_GETARG_DATUM(0);
Datum newer_than_datum = PG_GETARG_DATUM(4);
/* Making types InvalidOid makes the logic simpler later */
Oid older_than_type = PG_ARGISNULL(0) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 0);
Oid newer_than_type = PG_ARGISNULL(4) ? InvalidOid : get_fn_expr_argtype(fcinfo->flinfo, 4);
bool cascade = PG_GETARG_BOOL(3);
if (PG_ARGISNULL(0) && PG_ARGISNULL(4))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("older_than and newer_than timestamps provided to drop_chunks cannot both be NULL")
));
ht_oids = ts_hypertable_get_all_by_name(schema_name, table_name, CurrentMemoryContext);
if (table_name != NULL)
{
if (ht_oids == NIL)
ereport(ERROR,
(errcode(ERRCODE_TS_HYPERTABLE_NOT_EXIST),
errmsg("hypertable \"%s\" does not exist", NameStr(*table_name))
));
}
foreach(lc, ht_oids)
{
Oid table_relid = lfirst_oid(lc);
do_drop_chunks(table_relid, older_than_datum, newer_than_datum, older_than_type, newer_than_type, cascade);
}
PG_RETURN_NULL();
}

View File

@ -1595,3 +1595,62 @@ ts_hypertables_rename_schema_name(const char *old_name, const char *new_name)
ts_scanner_scan(&scanctx);
}
typedef struct AccumHypertable
{
List *ht_oids;
Name schema_name;
Name table_name;
} AccumHypertable;
static ScanTupleResult
hypertable_tuple_match_name(TupleInfo *ti, void *data)
{
Oid relid;
FormData_hypertable *form = (FormData_hypertable *) GETSTRUCT(ti->tuple);
AccumHypertable *accum = data;
Oid schema_oid = get_namespace_oid(NameStr(form->schema_name), true);
if (!OidIsValid(schema_oid))
return SCAN_CONTINUE;
relid = get_relname_relid(NameStr(form->table_name), schema_oid);
if (!OidIsValid(relid))
return SCAN_CONTINUE;
if ((accum->schema_name == NULL ||
DatumGetBool(DirectFunctionCall2(nameeq, NameGetDatum(accum->schema_name), NameGetDatum(&form->schema_name)))) &&
(accum->table_name == NULL ||
DatumGetBool(DirectFunctionCall2(nameeq, NameGetDatum(accum->table_name), NameGetDatum(&form->table_name)))))
accum->ht_oids = lappend_oid(accum->ht_oids, relid);
return SCAN_CONTINUE;
}
/*
* Used for drop_chunks. Either name can be NULL, which indicates matching on
* all possible names.
*/
List *
ts_hypertable_get_all_by_name(Name schema_name, Name table_name, MemoryContext mctx)
{
Catalog *catalog = ts_catalog_get();
AccumHypertable data = {
.ht_oids = NIL,
.schema_name = schema_name,
.table_name = table_name,
};
hypertable_scan_limit_internal(NULL,
0,
catalog_get_index(catalog, HYPERTABLE, INVALID_INDEXID),
hypertable_tuple_match_name,
&data,
-1,
AccessShareLock,
false,
mctx);
return data.ht_oids;
}

View File

@ -72,6 +72,7 @@ extern char *ts_hypertable_select_tablespace_name(Hypertable *ht, Chunk *chunk);
extern Tablespace *ts_hypertable_get_tablespace_at_offset_from(Hypertable *ht, Oid tablespace_oid, int16 offset);
extern bool ts_hypertable_has_tuples(Oid table_relid, LOCKMODE lockmode);
extern void ts_hypertables_rename_schema_name(const char *old_name, const char *new_name);
extern List *ts_hypertable_get_all_by_name(Name schema_name, Name table_name, MemoryContext mctx);
#define hypertable_scan(schema, table, tuple_found, data, lockmode, tuplock) \
ts_hypertable_scan_with_memory_context(schema, table, tuple_found, data, lockmode, tuplock, CurrentMemoryContext)

View File

@ -12,6 +12,13 @@ $BODY$
WHERE d.hypertable_id = dimension_get_time.hypertable_id AND
d.interval_length IS NOT NULL
$BODY$;
-- Make sure drop_chunks when there are no tables succeeds
SELECT drop_chunks(INTERVAL '1 hour');
drop_chunks
-------------
(1 row)
CREATE TABLE PUBLIC.drop_chunk_test1(time bigint, temp float8, device_id text);
CREATE TABLE PUBLIC.drop_chunk_test2(time bigint, temp float8, device_id text);
CREATE TABLE PUBLIC.drop_chunk_test3(time bigint, temp float8, device_id text);
@ -187,6 +194,8 @@ SELECT * FROM show_chunks('drop_chunk_test2');
CREATE VIEW dependent_view AS SELECT * FROM _timescaledb_internal._hyper_1_1_chunk;
\set ON_ERROR_STOP 0
SELECT drop_chunks();
ERROR: older_than and newer_than timestamps provided to drop_chunks cannot both be NULL
SELECT drop_chunks(2);
ERROR: cannot drop table _timescaledb_internal._hyper_1_1_chunk because other objects depend on it
SELECT drop_chunks(NULL::interval);
@ -656,12 +665,6 @@ WHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table
(9 rows)
\set ON_ERROR_STOP 0
-- should fail because too dangerous to assume anything
-- Note that currently this failure is triggered by SQL typecheker when it tries and fails to resolve
-- ANYELEMENT args to a concrete type. Explicit error checking may be necessary if drop_chunks implementation
-- is fully ported to C.
-- commented out because there is discrepancy between postgres 9.6 and postgres 10 error messages for this case
-- SELECT drop_chunks();
-- should error because no hypertable
SELECT drop_chunks(5, 'drop_chunk_test4');
ERROR: hypertable "drop_chunk_test4" does not exist

View File

@ -14,6 +14,9 @@ $BODY$
d.interval_length IS NOT NULL
$BODY$;
-- Make sure drop_chunks when there are no tables succeeds
SELECT drop_chunks(INTERVAL '1 hour');
CREATE TABLE PUBLIC.drop_chunk_test1(time bigint, temp float8, device_id text);
CREATE TABLE PUBLIC.drop_chunk_test2(time bigint, temp float8, device_id text);
CREATE TABLE PUBLIC.drop_chunk_test3(time bigint, temp float8, device_id text);
@ -84,6 +87,7 @@ SELECT * FROM show_chunks('drop_chunk_test2');
CREATE VIEW dependent_view AS SELECT * FROM _timescaledb_internal._hyper_1_1_chunk;
\set ON_ERROR_STOP 0
SELECT drop_chunks();
SELECT drop_chunks(2);
SELECT drop_chunks(NULL::interval);
SELECT drop_chunks(NULL::int);
@ -189,14 +193,6 @@ WHERE h.schema_name = 'public' AND (h.table_name = 'drop_chunk_test1' OR h.table
\dt "_timescaledb_internal"._hyper*
\set ON_ERROR_STOP 0
-- should fail because too dangerous to assume anything
-- Note that currently this failure is triggered by SQL typecheker when it tries and fails to resolve
-- ANYELEMENT args to a concrete type. Explicit error checking may be necessary if drop_chunks implementation
-- is fully ported to C.
-- commented out because there is discrepancy between postgres 9.6 and postgres 10 error messages for this case
-- SELECT drop_chunks();
-- should error because no hypertable
SELECT drop_chunks(5, 'drop_chunk_test4');
SELECT show_chunks('drop_chunk_test4');