Use custom node to block frozen chunk modifications

This patch changes the code that blocks frozen chunk
modifications to no longer use triggers but to use custom
node instead. Frozen chunks is a timescaledb internal object
and should therefore not be protected by TRIGGER which is
external and creates several hazards. TRIGGERs created to
protect internal state contend with user-created triggers.
The trigger created to protect frozen chunks does not work
well with our restoring GUC which we use when restoring
logical dumps. Thirdly triggers are not functional for any
internal operations but are only working in code paths that
explicitly added trigger support.
This commit is contained in:
Sven Klemm 2022-11-21 17:36:55 +01:00 committed by Sven Klemm
parent ce778faa11
commit 3b94b996f2
15 changed files with 254 additions and 340 deletions

View File

@ -100,12 +100,3 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.attach_osm_table_chunk(
hypertable REGCLASS,
chunk REGCLASS)
RETURNS BOOL AS '@MODULE_PATHNAME@', 'ts_chunk_attach_osm_table_chunk' LANGUAGE C VOLATILE;
-- Trigger that blocks modifications on frozen chunks
CREATE OR REPLACE FUNCTION _timescaledb_internal.frozen_chunk_modify_blocker() RETURNS trigger
LANGUAGE plpgsql STRICT AS
$BODY$
BEGIN
RAISE EXCEPTION 'unable to modify frozen chunk %s', TG_TABLE_NAME;
END;
$BODY$ SET search_path TO pg_catalog, pg_temp;

View File

@ -391,15 +391,6 @@ CREATE FUNCTION @extschema@.alter_data_node(
) RETURNS TABLE(node_name NAME, host TEXT, port INTEGER, database NAME, available BOOLEAN)
AS '@MODULE_PATHNAME@', 'ts_data_node_alter' LANGUAGE C VOLATILE;
-- Trigger that blocks modifications on frozen chunks
CREATE OR REPLACE FUNCTION _timescaledb_internal.frozen_chunk_modify_blocker() RETURNS trigger
LANGUAGE plpgsql STRICT AS
$BODY$
BEGIN
RAISE EXCEPTION 'unable to modify frozen chunk %s', TG_TABLE_NAME;
END;
$BODY$ SET search_path TO pg_catalog, pg_temp;
--
-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg`
--

View File

@ -308,8 +308,6 @@ GRANT SELECT ON _timescaledb_catalog.dimension TO PUBLIC;
DROP INDEX _timescaledb_catalog.chunk_data_node_node_name_idx;
DROP FUNCTION @extschema@.alter_data_node;
DROP FUNCTION _timescaledb_internal.frozen_chunk_modify_blocker;
--
-- Prevent downgrading if there are hierarchical continuous aggregates
--

View File

@ -179,15 +179,6 @@ static Chunk *chunk_resurrect(const Hypertable *ht, int chunk_id);
*/
#define CHUNK_STATUS_COMPRESSED_PARTIAL 8
/* The name of the trigger function that blocks data modifications on frozen chunks */
#define TS_FROZEN_TRIGGER_NAME_FUNCTION "frozen_chunk_modify_blocker"
/* The name of the row freeze trigger */
#define TS_FROZEN_TRIGGER_NAME_ROW "frozen_chunk_modify_blocker_row"
/* The name of the statement freeze trigger */
#define TS_FROZEN_TRIGGER_NAME_STMT "frozen_chunk_modify_blocker_stmt"
static HeapTuple
chunk_formdata_make_tuple(const FormData_chunk *fd, TupleDesc desc)
{
@ -3558,30 +3549,6 @@ ts_chunk_set_partial(Chunk *chunk)
return ts_chunk_add_status(chunk, CHUNK_STATUS_COMPRESSED_PARTIAL);
}
#if PG14_GE
/* Install a new trigger of the given table OID */
static void
install_chunk_trigger(CreateTrigStmt *stmt, Oid table_oid)
{
ObjectAddress objaddr;
objaddr = CreateTrigger(stmt,
NULL,
table_oid,
InvalidOid,
InvalidOid,
InvalidOid,
InvalidOid,
InvalidOid,
NULL,
false,
false);
if (!OidIsValid(objaddr.objectId))
elog(ERROR, "could not create internal blocker trigger on %d", table_oid);
}
#endif
/* No inserts, updates, and deletes are permitted on a frozen chunk.
* Compression policies etc do not run on a frozen chunk.
* Only valid operation is dropping the chunk
@ -3590,59 +3557,6 @@ bool
ts_chunk_set_frozen(Chunk *chunk)
{
#if PG14_GE
char *relname = get_rel_name(chunk->table_id);
Oid schemaid = get_rel_namespace(chunk->table_id);
char *schema = get_namespace_name(schemaid);
/* Create a trigger that reject data modifications on the frozen chunk.
*
* Operations like inserts and compression / decompression check the
* state of chunks directly in the code path. However, operations like
* updates and deletes are handled by PostgreSQL. For these remaining
* operations, the trigger is responsible for rejecting the operation
* on the frozen chunk.
*
* We need to create a row and a statement trigger. To block
* INSERT / UPDATE / DELETE operations, we need a row trigger. Since
* the trigger is installed on the chunk, a statement-based trigger
* will not fire for the individual chunks on these operations.
*
* A trigger that fires on TRUNCATE operations needs to be a statement
* trigger; PostgreSQL does not support row based TRUNCATE triggers.
* These triggers will also fire on the individual chunks.
*/
/* Row based INSERT / UPDATE / DELETE trigger */
CreateTrigStmt stmt_chunk_trigger_row = {
.type = T_CreateTrigStmt,
.row = true,
.timing = TRIGGER_TYPE_BEFORE,
.trigname = TS_FROZEN_TRIGGER_NAME_ROW,
.relation = makeRangeVar(schema, relname, -1),
.funcname = list_make2(makeString(INTERNAL_SCHEMA_NAME),
makeString(TS_FROZEN_TRIGGER_NAME_FUNCTION)),
.args = NIL,
.events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_DELETE | TRIGGER_TYPE_UPDATE,
};
install_chunk_trigger(&stmt_chunk_trigger_row, chunk->table_id);
/* Statement based TRUNCATE trigger */
CreateTrigStmt stmt_chunk_trigger_statement = {
.type = T_CreateTrigStmt,
.row = false,
.timing = TRIGGER_TYPE_BEFORE,
.trigname = TS_FROZEN_TRIGGER_NAME_STMT,
.relation = makeRangeVar(schema, relname, -1),
.funcname = list_make2(makeString(INTERNAL_SCHEMA_NAME),
makeString(TS_FROZEN_TRIGGER_NAME_FUNCTION)),
.args = NIL,
.events = TRIGGER_TYPE_TRUNCATE,
};
install_chunk_trigger(&stmt_chunk_trigger_statement, chunk->table_id);
return ts_chunk_add_status(chunk, CHUNK_STATUS_FROZEN);
#else
elog(ERROR, "freeze chunk supported only for PG14 or greater");
@ -3654,48 +3568,6 @@ bool
ts_chunk_unset_frozen(Chunk *chunk)
{
#if PG14_GE
Relation tgrel;
ScanKeyData skey[1];
SysScanDesc tgscan;
HeapTuple tuple;
int deleted_triggers = 0;
/* Search for the freeze triggers on the chunk and remove them. */
tgrel = table_open(TriggerRelationId, RowExclusiveLock);
ScanKeyInit(&skey[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber,
F_OIDEQ,
ObjectIdGetDatum(chunk->table_id));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true, NULL, 1, skey);
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
{
Form_pg_trigger trig = (Form_pg_trigger) GETSTRUCT(tuple);
if ((namestrcmp(&(trig->tgname), TS_FROZEN_TRIGGER_NAME_STMT) == 0) ||
(namestrcmp(&(trig->tgname), TS_FROZEN_TRIGGER_NAME_ROW) == 0))
{
ObjectAddress objaddr = { .classId = TriggerRelationId, .objectId = trig->oid };
performDeletion(&objaddr, DROP_RESTRICT, 0);
deleted_triggers++;
}
}
systable_endscan(tgscan);
table_close(tgrel, RowExclusiveLock);
if (deleted_triggers != 2)
{
ereport(WARNING,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("found only %d frozen trigger on %s (2 expected)",
deleted_triggers,
get_rel_name(chunk->table_id))));
}
return ts_chunk_clear_status(chunk, CHUNK_STATUS_FROZEN);
#else
elog(ERROR, "freeze chunk supported only for PG14 or greater");

View File

@ -148,6 +148,17 @@ ts_chunk_dispatch_get_chunk_insert_state(ChunkDispatch *dispatch, Point *point,
*/
bool found;
Chunk *new_chunk = ts_hypertable_find_chunk_for_point(dispatch->hypertable, point);
#if PG14_GE
/*
* Frozen chunks require at least PG14.
*/
if (new_chunk && ts_chunk_is_frozen(new_chunk))
elog(ERROR,
"cannot INSERT into frozen chunk \"%s\"",
get_rel_name(new_chunk->table_id));
#endif
if (new_chunk == NULL)
{
new_chunk = ts_hypertable_create_chunk_for_point(dispatch->hypertable, point, &found);

View File

@ -1151,6 +1151,14 @@ process_truncate(ProcessUtilityArgs *args)
chunk->hypertable_relid,
CACHE_FLAG_NONE);
/*
* Block direct TRUNCATE on frozen chunk.
*/
#if PG14_GE
if (ts_chunk_is_frozen(chunk))
elog(ERROR, "cannot TRUNCATE frozen chunk \"%s\"", get_rel_name(relid));
#endif
Assert(ht != NULL);
/* If the hypertable has continuous aggregates, then invalidate

View File

@ -5,5 +5,6 @@ set(SOURCES
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})
add_subdirectory(compress_dml)
add_subdirectory(decompress_chunk)
add_subdirectory(frozen_chunk_dml)
add_subdirectory(gapfill)
add_subdirectory(skip_scan)

View File

@ -0,0 +1,3 @@
# Add all *.c to sources in upperlevel directory
set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/frozen_chunk_dml.c)
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})

View File

@ -0,0 +1,139 @@
/*
* 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 <nodes/extensible.h>
#include <optimizer/pathnode.h>
#include <optimizer/paths.h>
#include "compat/compat.h"
#include "chunk.h"
#include "hypertable.h"
#include "frozen_chunk_dml.h"
#include "utils.h"
/*
* Path, Plan and State node for blocking DML on frozen chunks.
*/
static Path *frozen_chunk_dml_path_create(Path *subpath, Oid chunk_relid);
static Plan *frozen_chunk_dml_plan_create(PlannerInfo *root, RelOptInfo *relopt,
CustomPath *best_path, List *tlist, List *clauses,
List *custom_plans);
static Node *frozen_chunk_dml_state_create(CustomScan *scan);
static void frozen_chunk_dml_begin(CustomScanState *node, EState *estate, int eflags);
static TupleTableSlot *frozen_chunk_dml_exec(CustomScanState *node);
static void frozen_chunk_dml_end(CustomScanState *node);
static void frozen_chunk_dml_rescan(CustomScanState *node);
static CustomPathMethods frozen_chunk_dml_path_methods = {
.CustomName = "FrozenChunkDml",
.PlanCustomPath = frozen_chunk_dml_plan_create,
};
static CustomScanMethods frozen_chunk_dml_plan_methods = {
.CustomName = "FrozenChunkDml",
.CreateCustomScanState = frozen_chunk_dml_state_create,
};
static CustomExecMethods frozen_chunk_dml_state_methods = {
.CustomName = FROZEN_CHUNK_DML_STATE_NAME,
.BeginCustomScan = frozen_chunk_dml_begin,
.EndCustomScan = frozen_chunk_dml_end,
.ExecCustomScan = frozen_chunk_dml_exec,
.ReScanCustomScan = frozen_chunk_dml_rescan,
};
static void
frozen_chunk_dml_begin(CustomScanState *node, EState *estate, int eflags)
{
CustomScan *cscan = castNode(CustomScan, node->ss.ps.plan);
Plan *subplan = linitial(cscan->custom_plans);
node->custom_ps = list_make1(ExecInitNode(subplan, estate, eflags));
}
/*
* nothing to reset for rescan in dml blocker
*/
static void
frozen_chunk_dml_rescan(CustomScanState *node)
{
}
/* we cannot update/delete rows if we have a frozen chunk. so
* throw an error. Note this subplan will return 0 tuples as the chunk is empty
* and all rows are saved in the compressed chunk.
*/
static TupleTableSlot *
frozen_chunk_dml_exec(CustomScanState *node)
{
FrozenChunkDmlState *state = (FrozenChunkDmlState *) node;
Oid chunk_relid = state->chunk_relid;
elog(ERROR,
"cannot update/delete rows from chunk \"%s\" as it is frozen",
get_rel_name(chunk_relid));
return NULL;
}
static void
frozen_chunk_dml_end(CustomScanState *node)
{
PlanState *substate = linitial(node->custom_ps);
ExecEndNode(substate);
}
static Path *
frozen_chunk_dml_path_create(Path *subpath, Oid chunk_relid)
{
FrozenChunkDmlPath *path = (FrozenChunkDmlPath *) palloc0(sizeof(FrozenChunkDmlPath));
memcpy(&path->cpath.path, subpath, sizeof(Path));
path->cpath.path.type = T_CustomPath;
path->cpath.path.pathtype = T_CustomScan;
path->cpath.path.parent = subpath->parent;
path->cpath.path.pathtarget = subpath->pathtarget;
path->cpath.methods = &frozen_chunk_dml_path_methods;
path->cpath.custom_paths = list_make1(subpath);
path->chunk_relid = chunk_relid;
return &path->cpath.path;
}
static Plan *
frozen_chunk_dml_plan_create(PlannerInfo *root, RelOptInfo *relopt, CustomPath *best_path,
List *tlist, List *clauses, List *custom_plans)
{
FrozenChunkDmlPath *cdpath = (FrozenChunkDmlPath *) best_path;
CustomScan *cscan = makeNode(CustomScan);
Assert(list_length(custom_plans) == 1);
cscan->methods = &frozen_chunk_dml_plan_methods;
cscan->custom_plans = custom_plans;
cscan->scan.scanrelid = relopt->relid;
cscan->scan.plan.targetlist = tlist;
cscan->custom_scan_tlist = NIL;
cscan->custom_private = list_make1_oid(cdpath->chunk_relid);
return &cscan->scan.plan;
}
static Node *
frozen_chunk_dml_state_create(CustomScan *scan)
{
FrozenChunkDmlState *state;
state = (FrozenChunkDmlState *) newNode(sizeof(FrozenChunkDmlState), T_CustomScanState);
state->chunk_relid = linitial_oid(scan->custom_private);
state->cscan_state.methods = &frozen_chunk_dml_state_methods;
return (Node *) state;
}
Path *
frozen_chunk_dml_generate_path(Path *subpath, Chunk *chunk)
{
return frozen_chunk_dml_path_create(subpath, chunk->table_id);
}

View File

@ -0,0 +1,29 @@
/*
* 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.
*/
#ifndef TIMESCALEDB_FROZEN_CHUNK_DML_H
#define TIMESCALEDB_FROZEN_CHUNK_DML_H
#include <postgres.h>
#include <nodes/execnodes.h>
#include "hypertable.h"
typedef struct FrozenChunkDmlPath
{
CustomPath cpath;
Oid chunk_relid;
} FrozenChunkDmlPath;
typedef struct FrozenChunkDmlState
{
CustomScanState cscan_state;
Oid chunk_relid;
} FrozenChunkDmlState;
Path *frozen_chunk_dml_generate_path(Path *subpath, Chunk *chunk);
#define FROZEN_CHUNK_DML_STATE_NAME "FrozenChunkDmlState"
#endif

View File

@ -5,6 +5,7 @@
*/
#include <postgres.h>
#include <catalog/pg_trigger.h>
#include <commands/extension.h>
#include <optimizer/paths.h>
#include <parser/parsetree.h>
#include <foreign/fdwapi.h>
@ -24,6 +25,7 @@
#include "ts_catalog/hypertable_compression.h"
#include "hypertable.h"
#include "nodes/compress_dml/compress_dml.h"
#include "nodes/frozen_chunk_dml/frozen_chunk_dml.h"
#include "nodes/decompress_chunk/decompress_chunk.h"
#include "nodes/data_node_dispatch.h"
#include "nodes/data_node_copy.h"
@ -32,6 +34,8 @@
#include <math.h>
#define OSM_EXTENSION_NAME "timescaledb_osm"
static bool
is_dist_hypertable_involved(PlannerInfo *root)
{
@ -49,6 +53,21 @@ is_dist_hypertable_involved(PlannerInfo *root)
return false;
}
#if PG14_GE
static int osm_present = -1;
static bool
is_osm_present()
{
if (osm_present == -1)
{
Oid osm_oid = get_extension_oid(OSM_EXTENSION_NAME, true);
osm_present = OidIsValid(osm_oid);
}
return osm_present;
}
#endif
void
tsl_create_upper_paths_hook(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel,
RelOptInfo *output_rel, TsRelType input_reltype, Hypertable *ht,
@ -114,10 +133,28 @@ tsl_set_rel_pathlist_query(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeT
ts_decompress_chunk_generate_paths(root, rel, ht, chunk);
}
}
void
tsl_set_rel_pathlist_dml(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte,
Hypertable *ht)
{
#if PG14_GE
if (is_osm_present())
{
Chunk *chunk = ts_chunk_get_by_relid(rte->relid, false);
if (chunk && ts_chunk_is_frozen(chunk))
{
ListCell *lc;
foreach (lc, rel->pathlist)
{
Path **pathptr = (Path **) &lfirst(lc);
*pathptr = frozen_chunk_dml_generate_path(*pathptr, chunk);
}
return;
}
}
#endif
if (ht != NULL && TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht))
{
ListCell *lc;

View File

@ -24,10 +24,12 @@ CREATE OR REPLACE VIEW chunk_view AS
AND chcons.dimension_slice_id = dimsl.id;
GRANT SELECT on chunk_view TO PUBLIC;
\c :TEST_DBNAME :ROLE_SUPERUSER
-- fake presence of timescaledb_osm
INSERT INTO pg_extension(oid,extname,extowner,extnamespace,extrelocatable,extversion) SELECT 1,'timescaledb_osm',10,11,false,'1.0';
CREATE SCHEMA test1;
GRANT CREATE ON SCHEMA test1 TO :ROLE_DEFAULT_PERM_USER;
GRANT USAGE ON SCHEMA test1 TO :ROLE_DEFAULT_PERM_USER;
--mock hooks for OSM intercation with timescaledb
-- mock hooks for OSM interaction with timescaledb
CREATE OR REPLACE FUNCTION ts_setup_osm_hook( ) RETURNS VOID
AS :TSL_MODULE_PATHNAME LANGUAGE C VOLATILE;
CREATE OR REPLACE FUNCTION ts_undo_osm_hook( ) RETURNS VOID
@ -62,25 +64,13 @@ FROM timescaledb_information.chunks
WHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'
ORDER BY chunk_name LIMIT 1
\gset
-- Freeze and check trigger
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
--------+--------
(0 rows)
-- Freeze
SELECT _timescaledb_internal.freeze_chunk( :'CHNAME');
freeze_chunk
--------------
t
(1 row)
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
----------------------------------+--------
frozen_chunk_modify_blocker_row | 31
frozen_chunk_modify_blocker_stmt | 34
(2 rows)
SELECT * from test1.hyper1 ORDER BY 1;
time | temp
------+------
@ -101,13 +91,13 @@ SELECT * from test1.hyper1 ORDER BY 1;
UPDATE test1.hyper1 SET temp = 40 WHERE time = 20;
-- Frozen chunk is affected
UPDATE test1.hyper1 SET temp = 40 WHERE temp = 0.5;
ERROR: unable to modify frozen chunk _hyper_1_1_chunks
ERROR: cannot update/delete rows from chunk "_hyper_1_1_chunk" as it is frozen
-- Frozen chunk is affected
UPDATE test1.hyper1 SET temp = 40 WHERE time = 10;
ERROR: unable to modify frozen chunk _hyper_1_1_chunks
ERROR: cannot update/delete rows from chunk "_hyper_1_1_chunk" as it is frozen
-- Frozen chunk is affected
DELETE FROM test1.hyper1 WHERE time = 10;
ERROR: unable to modify frozen chunk _hyper_1_1_chunks
ERROR: cannot update/delete rows from chunk "_hyper_1_1_chunk" as it is frozen
SELECT * from test1.hyper1 ORDER BY 1;
time | temp
------+------
@ -118,7 +108,7 @@ SELECT * from test1.hyper1 ORDER BY 1;
BEGIN;
DELETE FROM test1.hyper1 WHERE time = 20;
DELETE FROM test1.hyper1 WHERE temp = 0.5;
ERROR: unable to modify frozen chunk _hyper_1_1_chunks
ERROR: cannot update/delete rows from chunk "_hyper_1_1_chunk" as it is frozen
ROLLBACK;
-- TEST update on unfrozen chunk should be possible
BEGIN;
@ -147,12 +137,9 @@ SELECT * FROM test1.hyper1 WHERE time = 30;
(1 row)
UPDATE test1.hyper1 SET temp = 40 WHERE time = 30::text::float;
ERROR: cannot update/delete rows from chunk "_hyper_1_1_chunk" as it is frozen
SELECT * FROM test1.hyper1 WHERE time = 30;
time | temp
------+------
30 | 40
(1 row)
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
-- TEST delete on unfrozen chunks should be possible
BEGIN;
@ -180,18 +167,16 @@ SELECT * FROM test1.hyper1 WHERE time = 30;
(1 row)
DELETE FROM test1.hyper1 WHERE time = 30::text::float;
ERROR: cannot update/delete rows from chunk "_hyper_1_1_chunk" as it is frozen
SELECT * FROM test1.hyper1 WHERE time = 30;
time | temp
------+------
(0 rows)
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
-- TEST inserts into a frozen chunk fails
INSERT INTO test1.hyper1 VALUES ( 11, 11);
ERROR: Insert not permitted on frozen chunk "_hyper_1_1_chunk"
ERROR: cannot INSERT into frozen chunk "_hyper_1_1_chunk"
-- Test truncating table should fail
TRUNCATE test1.hyper1;
ERROR: unable to modify frozen chunk _hyper_1_1_chunks
TRUNCATE :CHNAME;
ERROR: cannot TRUNCATE frozen chunk "_hyper_1_1_chunk"
SELECT * from test1.hyper1 ORDER BY 1;
time | temp
------+------
@ -305,13 +290,13 @@ SELECT decompress_chunk( :'CHNAME');
ERROR: decompress_chunk not permitted on frozen chunk "_hyper_2_3_chunk"
--insert into frozen chunk, should fail
INSERT INTO public.table_to_compress VALUES ('2020-01-01 10:00', 12, 77);
ERROR: Insert not permitted on frozen chunk "_hyper_2_3_chunk"
ERROR: cannot INSERT into frozen chunk "_hyper_2_3_chunk"
--touches all chunks
UPDATE public.table_to_compress SET value = 3;
ERROR: cannot update/delete rows from chunk "_hyper_2_3_chunk" as it is compressed
ERROR: cannot update/delete rows from chunk "_hyper_2_3_chunk" as it is frozen
--touches only frozen chunk
DELETE FROM public.table_to_compress WHERE time < '2020-01-02';
ERROR: cannot update/delete rows from chunk "_hyper_2_3_chunk" as it is compressed
ERROR: cannot update/delete rows from chunk "_hyper_2_3_chunk" as it is frozen
\set ON_ERROR_STOP 1
--try to refreeze
SELECT _timescaledb_internal.freeze_chunk( :'CHNAME');
@ -781,7 +766,7 @@ FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
\set ON_ERROR_STOP 0
-- Copy should fail because one of che chunks is frozen
COPY test1.copy_test FROM STDIN DELIMITER ',';
ERROR: Insert not permitted on frozen chunk "_hyper_8_15_chunk"
ERROR: cannot INSERT into frozen chunk "_hyper_8_15_chunk"
\set ON_ERROR_STOP 1
-- Count existing rows
SELECT COUNT(*) FROM test1.copy_test;
@ -790,48 +775,6 @@ SELECT COUNT(*) FROM test1.copy_test;
2
(1 row)
-- Test dump & restore
\c postgres :ROLE_SUPERUSER
\! utils/pg_dump_aux_dump.sh dump/pg_dump.sql
\c :TEST_DBNAME
-- Make sure tables was droped by pg_dump_aux_dump.sh
\set ON_ERROR_STOP 0
SELECT * FROM test1.copy_test;
ERROR: relation "test1.copy_test" does not exist at character 15
\set ON_ERROR_STOP 1
SET client_min_messages = ERROR;
CREATE EXTENSION timescaledb CASCADE;
RESET client_min_messages;
--\! cp dump/pg_dump.sql /tmp/dump.sql
SELECT timescaledb_pre_restore();
timescaledb_pre_restore
-------------------------
t
(1 row)
\! utils/pg_dump_aux_restore.sh dump/pg_dump.sql
SELECT timescaledb_post_restore();
timescaledb_post_restore
--------------------------
t
(1 row)
SELECT _timescaledb_internal.stop_background_workers();
stop_background_workers
-------------------------
t
(1 row)
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
-- Make sure the chunk is still frozen
-- Check trigger
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
----------------------------------+--------
frozen_chunk_modify_blocker_row | 31
frozen_chunk_modify_blocker_stmt | 34
(2 rows)
-- Check state
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
@ -843,7 +786,7 @@ FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
\set ON_ERROR_STOP 0
-- Copy should fail because one of che chunks is frozen
COPY test1.copy_test FROM STDIN DELIMITER ',';
ERROR: Insert not permitted on frozen chunk "_hyper_8_15_chunk"
ERROR: cannot INSERT into frozen chunk "_hyper_8_15_chunk"
\set ON_ERROR_STOP 1
-- Count existing rows
SELECT COUNT(*) FROM test1.copy_test;
@ -859,14 +802,6 @@ SELECT _timescaledb_internal.unfreeze_chunk( :'COPY_CHNAME');
t
(1 row)
-- Check trigger
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
----------------------------------+--------
frozen_chunk_modify_blocker_row | 31
frozen_chunk_modify_blocker_stmt | 34
(2 rows)
-- Check state
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
@ -877,53 +812,6 @@ FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
-- Copy should work now
COPY test1.copy_test FROM STDIN DELIMITER ',';
-- Test that unfreeze works even if somebody has dropped one the block triggers
SELECT _timescaledb_internal.freeze_chunk( :'COPY_CHNAME');
freeze_chunk
--------------
t
(1 row)
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
table_name | status
-------------------+--------
_hyper_8_15_chunk | 4
(1 row)
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'COPY_CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
----------------------------------+--------
frozen_chunk_modify_blocker_row | 31
frozen_chunk_modify_blocker_stmt | 34
(2 rows)
DROP TRIGGER frozen_chunk_modify_blocker_row ON :COPY_CHNAME;
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'COPY_CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
----------------------------------+--------
frozen_chunk_modify_blocker_stmt | 34
(1 row)
SELECT _timescaledb_internal.unfreeze_chunk( :'COPY_CHNAME');
WARNING: found only 1 frozen trigger on _hyper_8_15_chunk (2 expected)
unfreeze_chunk
----------------
t
(1 row)
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'COPY_CHNAME'::regclass ORDER BY tgname, tgtype;
tgname | tgtype
--------+--------
(0 rows)
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
table_name | status
-------------------+--------
_hyper_8_15_chunk | 0
(1 row)
-- clean up databases created
\c :TEST_DBNAME :ROLE_SUPERUSER
DROP DATABASE postgres_fdw_db;

View File

@ -73,7 +73,6 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
_timescaledb_internal.first_combinefunc(internal,internal)
_timescaledb_internal.first_sfunc(internal,anyelement,"any")
_timescaledb_internal.freeze_chunk(regclass)
_timescaledb_internal.frozen_chunk_modify_blocker()
_timescaledb_internal.generate_uuid()
_timescaledb_internal.get_chunk_colstats(regclass)
_timescaledb_internal.get_chunk_relstats(regclass)

View File

@ -136,14 +136,6 @@ set(SOLO_TESTS
reorder
telemetry_stats)
# chunk_utils_internal creates a backup, drop the databases and restores the
# backup. So, it should not run in parallel with other tests.
if((${PG_VERSION_MAJOR} GREATER_EQUAL "14"))
if(CMAKE_BUILD_TYPE MATCHES Debug)
list(APPEND SOLO_TESTS chunk_utils_internal)
endif()
endif()
set(TEST_TEMPLATES
compression_insert.sql.in cagg_union_view.sql.in plan_skip_scan.sql.in
transparent_decompression.sql.in

View File

@ -27,11 +27,14 @@ CREATE OR REPLACE VIEW chunk_view AS
GRANT SELECT on chunk_view TO PUBLIC;
\c :TEST_DBNAME :ROLE_SUPERUSER
-- fake presence of timescaledb_osm
INSERT INTO pg_extension(oid,extname,extowner,extnamespace,extrelocatable,extversion) SELECT 1,'timescaledb_osm',10,11,false,'1.0';
CREATE SCHEMA test1;
GRANT CREATE ON SCHEMA test1 TO :ROLE_DEFAULT_PERM_USER;
GRANT USAGE ON SCHEMA test1 TO :ROLE_DEFAULT_PERM_USER;
--mock hooks for OSM intercation with timescaledb
-- mock hooks for OSM interaction with timescaledb
CREATE OR REPLACE FUNCTION ts_setup_osm_hook( ) RETURNS VOID
AS :TSL_MODULE_PATHNAME LANGUAGE C VOLATILE;
@ -61,10 +64,8 @@ WHERE hypertable_name = 'hyper1' and hypertable_schema = 'test1'
ORDER BY chunk_name LIMIT 1
\gset
-- Freeze and check trigger
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
-- Freeze
SELECT _timescaledb_internal.freeze_chunk( :'CHNAME');
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
SELECT * from test1.hyper1 ORDER BY 1;
@ -121,7 +122,7 @@ ROLLBACK;
INSERT INTO test1.hyper1 VALUES ( 11, 11);
-- Test truncating table should fail
TRUNCATE test1.hyper1;
TRUNCATE :CHNAME;
SELECT * from test1.hyper1 ORDER BY 1;
@ -494,33 +495,6 @@ COPY test1.copy_test FROM STDIN DELIMITER ',';
-- Count existing rows
SELECT COUNT(*) FROM test1.copy_test;
-- Test dump & restore
\c postgres :ROLE_SUPERUSER
\! utils/pg_dump_aux_dump.sh dump/pg_dump.sql
\c :TEST_DBNAME
-- Make sure tables was droped by pg_dump_aux_dump.sh
\set ON_ERROR_STOP 0
SELECT * FROM test1.copy_test;
\set ON_ERROR_STOP 1
SET client_min_messages = ERROR;
CREATE EXTENSION timescaledb CASCADE;
RESET client_min_messages;
--\! cp dump/pg_dump.sql /tmp/dump.sql
SELECT timescaledb_pre_restore();
\! utils/pg_dump_aux_restore.sh dump/pg_dump.sql
SELECT timescaledb_post_restore();
SELECT _timescaledb_internal.stop_background_workers();
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
-- Make sure the chunk is still frozen
-- Check trigger
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
-- Check state
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
@ -539,9 +513,6 @@ SELECT COUNT(*) FROM test1.copy_test;
-- Check unfreeze restored chunk
SELECT _timescaledb_internal.unfreeze_chunk( :'COPY_CHNAME');
-- Check trigger
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'CHNAME'::regclass ORDER BY tgname, tgtype;
-- Check state
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
@ -552,22 +523,6 @@ COPY test1.copy_test FROM STDIN DELIMITER ',';
2021-01-01 01:10:00+01,1
\.
-- Test that unfreeze works even if somebody has dropped one the block triggers
SELECT _timescaledb_internal.freeze_chunk( :'COPY_CHNAME');
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'COPY_CHNAME'::regclass ORDER BY tgname, tgtype;
DROP TRIGGER frozen_chunk_modify_blocker_row ON :COPY_CHNAME;
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'COPY_CHNAME'::regclass ORDER BY tgname, tgtype;
SELECT _timescaledb_internal.unfreeze_chunk( :'COPY_CHNAME');
SELECT tgname, tgtype FROM pg_trigger WHERE tgrelid = :'COPY_CHNAME'::regclass ORDER BY tgname, tgtype;
SELECT table_name, status
FROM _timescaledb_catalog.chunk WHERE table_name = :'COPY_CHUNK_NAME';
-- clean up databases created
\c :TEST_DBNAME :ROLE_SUPERUSER
DROP DATABASE postgres_fdw_db;