mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-15 10:11:29 +08:00
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:
parent
ce778faa11
commit
3b94b996f2
@ -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;
|
||||
|
@ -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`
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
|
128
src/chunk.c
128
src/chunk.c
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
3
tsl/src/nodes/frozen_chunk_dml/CMakeLists.txt
Normal file
3
tsl/src/nodes/frozen_chunk_dml/CMakeLists.txt
Normal 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})
|
139
tsl/src/nodes/frozen_chunk_dml/frozen_chunk_dml.c
Normal file
139
tsl/src/nodes/frozen_chunk_dml/frozen_chunk_dml.c
Normal 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);
|
||||
}
|
29
tsl/src/nodes/frozen_chunk_dml/frozen_chunk_dml.h
Normal file
29
tsl/src/nodes/frozen_chunk_dml/frozen_chunk_dml.h
Normal 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
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user