Remove AsyncAppend custom node

This commit is contained in:
Sven Klemm 2023-12-13 17:22:20 +01:00 committed by Sven Klemm
parent dde86ac116
commit b1159eba68
7 changed files with 1 additions and 1026 deletions

View File

@ -1,4 +1,4 @@
set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/async_append.c) set(SOURCES)
target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES}) target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES})
add_subdirectory(compress_dml) add_subdirectory(compress_dml)
add_subdirectory(decompress_chunk) add_subdirectory(decompress_chunk)

View File

@ -1,480 +0,0 @@
/*
* 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 <parser/parsetree.h>
#include <nodes/plannodes.h>
#include <nodes/extensible.h>
#include <nodes/nodeFuncs.h>
#include <optimizer/paths.h>
#include <optimizer/pathnode.h>
#include <optimizer/prep.h>
#include <foreign/fdwapi.h>
#include <access/sysattr.h>
#include <miscadmin.h>
#include "async_append.h"
#include "planner.h"
#include "cache.h"
#include "hypertable.h"
#include "hypertable_cache.h"
#include "utils.h"
/*
* AsyncAppend provides an asynchronous API during query execution that
* decouples the sending of query requests from the reading of the result.
*
* Normally, an Append executes serially, i.e., it first executes the first
* child node, then the second, and so forth. In the case of a distributed
* query, that means the query on the second data node will not start
* executing until the first node has finished. Thus, if there are three data
* nodes, the remote communication will proceed as follows:
*
* 1. Send query to data node 1.
* 2. Get data from data node 1.
* 3. Send query to data node 2.
* 4. Get data from data node 2.
* 5. Send query to data node 3.
* 6. Get data from data node 4.
*
* Since a data node will always need some time to process a query before it
* is ready to send back results, this won't be very efficient.
* In contrast, AsyncAppend makes sure that all data node requests are sent
* before any data is read:
*
* 1. Send query to data node 1.
* 2. Send query to data node 2.
* 3. Send query to data node 3.
* 4. Get data from data node 1.
* 5. Get data from data node 2.
* 6. Get data from data node 4.
*
* With asynchronous approach, data node 2 and 3 will start processing their
* queries while the data from data node 1 is still being read.
*
* There's a caveat with this asynchronous approach, however. Since there's
* only one connection to each data node (to make sure that each data node is
* tied to a single transaction and snapshot), it is not possible to start
* executing a new query on the same data node until the first query is
* complete (to ensure the connection in idle state). This is important if a
* query consists of several sub-queries that are sent as separate queries to
* the same node. In that case, the entire result of the first sub-query must
* be fetched before proceeding with the next sub-query, which may cause
* memory blow up.
*
* The sub-query issue can be solved by using a CURSOR to break up a query in
* batches (multiple FETCH statements that fetch a fixed amount of rows each
* time). FETCH statements for multiple CURSORs (for different sub-queries)
* can be interleaved as long as they always read the full batch before
* returning. The downside of a CURSOR, however, is that it doesn't support
* parallel execution of the query on the data nodes.
*
* AsyncAppend is only used for plans that involve distributed hypertables (a
* plan that involves scanning of data nodes). The node is injected as a
* parent of Append or MergeAppend nodes. Here is how the modified plan looks
* like.
*
* .......
* |
* -------------
* | AsyncAppend |
* -------------
* | -----------------------
* -----------| Append or MergeAppend |
* -----------------------
* | --------------
* ---------------| DataNodeScan |
* | --------------
* | --------------
* ---------------| DataNodeScan |
* | --------------
*
* .....
*
*
* Since the PostgreSQL planner treats partitioned relations in a special way
* (throwing away existing and generating new paths), we needed to adjust plan
* paths at a later stage, thus using upper path hooks to do that.
*
* There are ways asynchronous appends can be further improved. For instance,
* after sending the initial queries to all nodes, the append node should pick
* the child to read based on which data node returns data first instead of
* just picking the first child.
*
*/
/* Plan state node for AsyncAppend plan */
typedef struct AsyncAppendState
{
CustomScanState css;
PlanState *subplan_state; /* AppendState or MergeAppendState */
List *data_node_scans; /* DataNodeScan states */
bool first_run;
} AsyncAppendState;
static TupleTableSlot *async_append_exec(CustomScanState *node);
static void async_append_begin(CustomScanState *node, EState *estate, int eflags);
static void async_append_end(CustomScanState *node);
static void async_append_rescan(CustomScanState *node);
static CustomExecMethods async_append_state_methods = {
.CustomName = "AsyncAppendState",
.BeginCustomScan = async_append_begin,
.EndCustomScan = async_append_end,
.ExecCustomScan = async_append_exec,
.ReScanCustomScan = async_append_rescan,
};
static Node *
async_append_state_create(CustomScan *cscan)
{
AsyncAppendState *state =
(AsyncAppendState *) newNode(sizeof(AsyncAppendState), T_CustomScanState);
state->subplan_state = NULL;
state->css.methods = &async_append_state_methods;
state->first_run = true;
return (Node *) state;
}
static CustomScanMethods async_append_plan_methods = {
.CustomName = "AsyncAppend",
.CreateCustomScanState = async_append_state_create,
};
static PlanState *
find_data_node_scan_state_child(PlanState *state)
{
if (state)
{
switch (nodeTag(state))
{
case T_CustomScanState:
return state;
case T_AggState:
case T_ResultState:
case T_SortState:
/* Data scan state can be buried under AggState or SortState */
return find_data_node_scan_state_child(state->lefttree);
default:
elog(ERROR,
"unexpected child node of Append or MergeAppend: %s",
ts_get_node_name((Node *) state->plan));
}
}
elog(ERROR, "could not find a DataNodeScan in plan state for AsyncAppend");
pg_unreachable();
}
static List *
get_data_node_async_scan_states(AsyncAppendState *state)
{
PlanState **child_plans;
int num_child_plans;
List *dn_plans = NIL;
int i;
if (IsA(state->subplan_state, AppendState))
{
AppendState *astate = castNode(AppendState, state->subplan_state);
child_plans = astate->appendplans;
num_child_plans = astate->as_nplans;
}
else if (IsA(state->subplan_state, MergeAppendState))
{
MergeAppendState *mstate = castNode(MergeAppendState, state->subplan_state);
child_plans = mstate->mergeplans;
num_child_plans = mstate->ms_nplans;
}
else
elog(ERROR,
"unexpected child node of AsyncAppend: %s",
ts_get_node_name((Node *) state->subplan_state->plan));
for (i = 0; i < num_child_plans; i++)
dn_plans = lappend(dn_plans, find_data_node_scan_state_child(child_plans[i]));
return dn_plans;
}
static void
async_append_begin(CustomScanState *node, EState *estate, int eflags)
{
AsyncAppendState *state = (AsyncAppendState *) node;
CustomScan *cscan = castNode(CustomScan, node->ss.ps.plan);
Plan *subplan;
PlanState *subplan_state;
Assert(cscan->custom_plans != NULL);
Assert(list_length(cscan->custom_plans) == 1);
subplan = linitial(cscan->custom_plans);
subplan_state = ExecInitNode(subplan, estate, eflags);
state->subplan_state = subplan_state;
state->css.custom_ps = list_make1(state->subplan_state);
state->data_node_scans = get_data_node_async_scan_states(state);
}
static void
iterate_data_nodes_and_exec(AsyncAppendState *aas, void (*dn_exec)(AsyncScanState *ass))
{
ListCell *lc;
foreach (lc, aas->data_node_scans)
{
AsyncScanState *dnss = lfirst(lc);
dn_exec(dnss);
}
}
static void
init(AsyncScanState *ass)
{
ass->init(ass);
}
static void
send_fetch_request(AsyncScanState *ass)
{
ass->send_fetch_request(ass);
}
static void
fetch_data(AsyncScanState *ass)
{
ass->fetch_data(ass);
}
static TupleTableSlot *
async_append_exec(CustomScanState *node)
{
TupleTableSlot *slot;
AsyncAppendState *state = (AsyncAppendState *) node;
ExprContext *econtext = node->ss.ps.ps_ExprContext;
Assert(state->subplan_state != NULL);
Assert(state->data_node_scans != NIL);
if (state->first_run)
{
state->first_run = false;
iterate_data_nodes_and_exec(state, init);
iterate_data_nodes_and_exec(state, send_fetch_request);
/* Fetch a new data batch into all sub-nodes. This will clear the
* connection for new requests (important when there are, e.g.,
* subqueries that share the connection). */
iterate_data_nodes_and_exec(state, fetch_data);
}
ResetExprContext(econtext);
slot = ExecProcNode(state->subplan_state);
econtext->ecxt_scantuple = slot;
if (!TupIsNull(slot))
{
if (!node->ss.ps.ps_ProjInfo)
return slot;
return ExecProject(node->ss.ps.ps_ProjInfo);
}
return ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
}
static void
async_append_end(CustomScanState *node)
{
AsyncAppendState *state = (AsyncAppendState *) node;
ExecEndNode(state->subplan_state);
}
static void
async_append_rescan(CustomScanState *node)
{
AsyncAppendState *state = (AsyncAppendState *) node;
if (node->ss.ps.chgParam != NULL)
UpdateChangedParamSet(state->subplan_state, node->ss.ps.chgParam);
ExecReScan(state->subplan_state);
}
static Plan *
async_append_plan_create(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist,
List *clauses, List *custom_plans)
{
CustomScan *cscan = makeNode(CustomScan);
Plan *subplan;
cscan->methods = &async_append_plan_methods;
/* output target list */
cscan->scan.plan.targetlist = tlist;
/* we don't scan a real relation here */
cscan->scan.scanrelid = 0;
cscan->flags = best_path->flags;
/* remove Result node since AsyncAppend node will project */
if (IsA(linitial(custom_plans), Result) &&
castNode(Result, linitial(custom_plans))->resconstantqual == NULL)
{
Result *result = castNode(Result, linitial(custom_plans));
if (result->plan.righttree != NULL)
elog(ERROR, "unexpected right tree below result node in async append");
custom_plans = list_make1(result->plan.lefttree);
}
cscan->custom_plans = custom_plans;
subplan = linitial(custom_plans);
if (!(IsA(subplan, MergeAppend) || IsA(subplan, Append)))
elog(ERROR, "unexpected child node of AsyncAppend: %s", ts_get_node_name((Node *) subplan));
/* input target list */
cscan->custom_scan_tlist = subplan->targetlist;
return &cscan->scan.plan;
}
static CustomPathMethods async_append_path_methods = {
.CustomName = "AsyncAppendPath",
.PlanCustomPath = async_append_plan_create,
};
static bool
is_data_node_scan_path(Path *path)
{
return false;
}
static void
path_process(PlannerInfo *root, Path **path)
{
Path **subpath = path;
Path *subp = *subpath;
List *children = NIL;
Path *child;
AsyncAppendPath *aa_path;
switch (nodeTag(*subpath))
{
case T_AppendPath:
children = castNode(AppendPath, subp)->subpaths;
break;
case T_MergeAppendPath:
children = castNode(MergeAppendPath, subp)->subpaths;
break;
case T_AggPath:
path_process(root, &castNode(AggPath, subp)->subpath);
return;
case T_GroupPath:
path_process(root, &castNode(GroupPath, subp)->subpath);
return;
case T_SortPath:
path_process(root, &castNode(SortPath, subp)->subpath);
return;
case T_IncrementalSortPath:
path_process(root, &castNode(IncrementalSortPath, subp)->spath.subpath);
return;
case T_UpperUniquePath:
path_process(root, &castNode(UpperUniquePath, subp)->subpath);
return;
case T_ProjectionPath:
path_process(root, &castNode(ProjectionPath, subp)->subpath);
return;
case T_ProjectSetPath:
path_process(root, &castNode(ProjectSetPath, subp)->subpath);
return;
case T_LimitPath:
path_process(root, &castNode(LimitPath, subp)->subpath);
return;
case T_UniquePath:
path_process(root, &castNode(UniquePath, subp)->subpath);
return;
case T_GatherPath:
path_process(root, &castNode(GatherPath, subp)->subpath);
return;
case T_GatherMergePath:
path_process(root, &castNode(GatherMergePath, subp)->subpath);
return;
case T_MaterialPath:
path_process(root, &castNode(MaterialPath, subp)->subpath);
return;
case T_NestPath:
case T_MergePath:
case T_HashPath:
path_process(root, &((JoinPath *) subp)->outerjoinpath);
path_process(root, &((JoinPath *) subp)->innerjoinpath);
return;
case T_MinMaxAggPath:
{
MinMaxAggPath *mm_path = castNode(MinMaxAggPath, subp);
ListCell *mm_lc;
foreach (mm_lc, mm_path->mmaggregates)
{
MinMaxAggInfo *mm_info = lfirst_node(MinMaxAggInfo, mm_lc);
path_process(root, &mm_info->path);
}
return;
}
case T_WindowAggPath:
path_process(root, &castNode(WindowAggPath, subp)->subpath);
return;
default:
return;
}
if (children == NIL)
return;
/* AsyncAppend only makes sense when there are multiple children that we'd
* like to asynchronously scan. Also note that postgres will remove append
* nodes when there's a single child and this will confuse AsyncAppend. */
if (list_length(children) <= 1)
return;
child = linitial(children);
/* sometimes data node scan is buried under ProjectionPath or AggPath */
if (IsA(child, ProjectionPath))
child = castNode(ProjectionPath, child)->subpath;
else if (IsA(child, AggPath))
child = castNode(AggPath, child)->subpath;
if (!is_data_node_scan_path(child))
return;
aa_path = (AsyncAppendPath *) newNode(sizeof(AsyncAppendPath), T_CustomPath);
aa_path->cpath.path.pathtype = T_CustomScan;
aa_path->cpath.path.parent = subp->parent;
aa_path->cpath.path.pathtarget = subp->pathtarget;
aa_path->cpath.flags = 0;
aa_path->cpath.methods = &async_append_path_methods;
aa_path->cpath.path.parallel_aware = false;
aa_path->cpath.path.param_info = subp->param_info;
aa_path->cpath.path.parallel_safe = false;
aa_path->cpath.path.parallel_workers = subp->parallel_workers;
aa_path->cpath.path.pathkeys = subp->pathkeys;
aa_path->cpath.custom_paths = list_make1(subp);
/* reuse subpath estimated rows and costs */
aa_path->cpath.path.rows = subp->rows;
aa_path->cpath.path.startup_cost = subp->startup_cost;
aa_path->cpath.path.total_cost = subp->total_cost;
*subpath = &aa_path->cpath.path;
}
void
async_append_add_paths(PlannerInfo *root, RelOptInfo *final_rel)
{
ListCell *lc;
foreach (lc, final_rel->pathlist)
path_process(root, (Path **) &lfirst(lc));
}

View File

@ -1,34 +0,0 @@
/*
* 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.
*/
#pragma once
#include <postgres.h>
#include <nodes/execnodes.h>
#include <nodes/pathnodes.h>
typedef struct AsyncAppendPath
{
CustomPath cpath;
} AsyncAppendPath;
/*
* A wrapper node for any descendant node that AsyncAppend plan needs to interact with.
* This node provides an async interface to underlying node.
*
* This node should not be confused with AsyncAppend plan state node
*/
typedef struct AsyncScanState
{
CustomScanState css;
/* Initialize the scan state */
void (*init)(struct AsyncScanState *state);
/* Send a request for new data */
void (*send_fetch_request)(struct AsyncScanState *state);
/* Fetch the actual data */
void (*fetch_data)(struct AsyncScanState *state);
} AsyncScanState;
extern void async_append_add_paths(PlannerInfo *root, RelOptInfo *final_rel);

View File

@ -11,7 +11,6 @@
#include <foreign/fdwapi.h> #include <foreign/fdwapi.h>
#include <nodes/nodeFuncs.h> #include <nodes/nodeFuncs.h>
#include "nodes/async_append.h"
#include "nodes/skip_scan/skip_scan.h" #include "nodes/skip_scan/skip_scan.h"
#include "chunk.h" #include "chunk.h"
#include "compat/compat.h" #include "compat/compat.h"
@ -30,23 +29,6 @@
#define OSM_EXTENSION_NAME "timescaledb_osm" #define OSM_EXTENSION_NAME "timescaledb_osm"
static bool
is_dist_hypertable_involved(PlannerInfo *root)
{
int rti;
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RangeTblEntry *rte = root->simple_rte_array[rti];
bool distributed = false;
if (ts_rte_is_hypertable(rte, &distributed) && distributed)
return true;
}
return false;
}
#if PG14_GE #if PG14_GE
static int osm_present = -1; static int osm_present = -1;
@ -80,11 +62,6 @@ tsl_create_upper_paths_hook(PlannerInfo *root, UpperRelationKind stage, RelOptIn
case UPPERREL_DISTINCT: case UPPERREL_DISTINCT:
tsl_skip_scan_paths_add(root, input_rel, output_rel); tsl_skip_scan_paths_add(root, input_rel, output_rel);
break; break;
case UPPERREL_FINAL:
if (ts_guc_enable_async_append && root->parse->resultRelation == 0 &&
is_dist_hypertable_involved(root))
async_append_add_paths(root, output_rel);
break;
default: default:
break; break;
} }

View File

@ -1,355 +0,0 @@
-- 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.
\c :TEST_DBNAME :ROLE_SUPERUSER
CREATE OR REPLACE FUNCTION ts_test_ddl_command_hook_reg() RETURNS VOID
AS :TSL_MODULE_PATHNAME, 'ts_test_ddl_command_hook_reg'
LANGUAGE C VOLATILE STRICT;
CREATE OR REPLACE FUNCTION ts_test_ddl_command_hook_unreg() RETURNS VOID
AS :TSL_MODULE_PATHNAME, 'ts_test_ddl_command_hook_unreg'
LANGUAGE C VOLATILE STRICT;
SET client_min_messages TO ERROR;
DROP SCHEMA IF EXISTS htable_schema CASCADE;
DROP TABLE IF EXISTS htable;
DROP TABLE IF EXISTS non_htable;
SET client_min_messages TO NOTICE;
CREATE SCHEMA htable_schema;
-- Install test hooks
SELECT ts_test_ddl_command_hook_reg();
ts_test_ddl_command_hook_reg
------------------------------
(1 row)
CREATE TABLE htable(time timestamptz, device int, color int CONSTRAINT color_check CHECK (color > 0), temp float);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE htable(time timestamptz, device int, color int CONSTRAINT color_check CHECK (color > 0), temp float);
NOTICE: test_ddl_command_end: CREATE TABLE
CREATE UNIQUE INDEX htable_pk ON htable(time);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE UNIQUE INDEX htable_pk ON htable(time);
NOTICE: test_ddl_command_end: CREATE INDEX
-- CREATE TABLE
SELECT * FROM create_hypertable('htable', 'time');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
1 | public | htable | t
(1 row)
SELECT * FROM test.show_columns('htable');
Column | Type | NotNull
--------+--------------------------+---------
time | timestamp with time zone | t
device | integer | f
color | integer | f
temp | double precision | f
(4 rows)
SELECT * FROM test.show_constraints('htable');
Constraint | Type | Columns | Index | Expr | Deferrable | Deferred | Validated
-------------+------+---------+-------+-------------+------------+----------+-----------
color_check | c | {color} | - | (color > 0) | f | f | t
(1 row)
SELECT * FROM test.show_indexes('htable');
Index | Columns | Expr | Unique | Primary | Exclusion | Tablespace
-----------+---------+------+--------+---------+-----------+------------
htable_pk | {time} | | t | f | f |
(1 row)
-- ADD CONSTRAINT
ALTER TABLE htable ADD CONSTRAINT device_check CHECK (device > 0);
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable ADD CONSTRAINT device_check CHECK (device > 0);
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_ddl_command_end: ALTER TABLE
SELECT * FROM test.show_constraints('htable');
Constraint | Type | Columns | Index | Expr | Deferrable | Deferred | Validated
--------------+------+----------+-------+--------------+------------+----------+-----------
color_check | c | {color} | - | (color > 0) | f | f | t
device_check | c | {device} | - | (device > 0) | f | f | t
(2 rows)
-- DROP CONSTRAINT
ALTER TABLE htable DROP CONSTRAINT device_check;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable DROP CONSTRAINT device_check;
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_sql_drop: constraint: public.htable.device_check
NOTICE: test_ddl_command_end: ALTER TABLE
SELECT * FROM test.show_constraints('htable');
Constraint | Type | Columns | Index | Expr | Deferrable | Deferred | Validated
-------------+------+---------+-------+-------------+------------+----------+-----------
color_check | c | {color} | - | (color > 0) | f | f | t
(1 row)
-- DROP COLUMN
ALTER TABLE htable DROP COLUMN color;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable DROP COLUMN color;
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_sql_drop: constraint: public.htable.color_check
NOTICE: test_ddl_command_end: ALTER TABLE
SELECT * FROM test.show_columns('htable');
Column | Type | NotNull
--------+--------------------------+---------
time | timestamp with time zone | t
device | integer | f
temp | double precision | f
(3 rows)
-- ADD COLUMN
ALTER TABLE htable ADD COLUMN description text;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable ADD COLUMN description text;
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_ddl_command_end: ALTER TABLE
SELECT * FROM test.show_columns('htable');
Column | Type | NotNull
-------------+--------------------------+---------
time | timestamp with time zone | t
device | integer | f
temp | double precision | f
description | text | f
(4 rows)
-- CREATE INDEX
CREATE INDEX htable_description_idx ON htable (description);
NOTICE: test_ddl_command_start: 1 hypertables, query: CREATE INDEX htable_description_idx ON htable (description);
NOTICE: test_ddl_command_start: public.htable
SELECT * FROM test.show_indexes('htable');
Index | Columns | Expr | Unique | Primary | Exclusion | Tablespace
------------------------+---------------+------+--------+---------+-----------+------------
htable_description_idx | {description} | | f | f | f |
htable_pk | {time} | | t | f | f |
(2 rows)
-- CREATE/DROP TRIGGER
CREATE OR REPLACE FUNCTION test_trigger()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN OLD;
END
$BODY$;
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE OR REPLACE FUNCTION test_trigger()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN OLD;
END
$BODY$;
CREATE TRIGGER htable_trigger_test
BEFORE INSERT ON htable
FOR EACH ROW EXECUTE FUNCTION test_trigger();
NOTICE: test_ddl_command_start: 1 hypertables, query: CREATE TRIGGER htable_trigger_test
BEFORE INSERT ON htable
FOR EACH ROW EXECUTE FUNCTION test_trigger();
NOTICE: test_ddl_command_start: public.htable
DROP TRIGGER htable_trigger_test on htable;
NOTICE: test_ddl_command_start: 1 hypertables, query: DROP TRIGGER htable_trigger_test on htable;
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_sql_drop: trigger
DROP FUNCTION test_trigger();
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP FUNCTION test_trigger();
-- CLUSTER, TRUNCATE, REINDEX, VACUUM (should not call event hooks)
CREATE TABLE non_htable (id int);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE non_htable (id int);
NOTICE: test_ddl_command_end: CREATE TABLE
CLUSTER htable USING htable_description_idx;
NOTICE: test_ddl_command_start: 1 hypertables, query: CLUSTER htable USING htable_description_idx;
NOTICE: test_ddl_command_start: public.htable
TRUNCATE non_htable, htable;
NOTICE: test_ddl_command_start: 1 hypertables, query: TRUNCATE non_htable, htable;
NOTICE: test_ddl_command_start: public.htable
REINDEX TABLE htable;
NOTICE: test_ddl_command_start: 1 hypertables, query: REINDEX TABLE htable;
NOTICE: test_ddl_command_start: public.htable
VACUUM htable;
NOTICE: test_ddl_command_start: 1 hypertables, query: VACUUM htable;
NOTICE: test_ddl_command_start: public.htable
-- ALTER TABLE
ALTER TABLE htable ADD CONSTRAINT temp_check CHECK (temp > 0.0);
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable ADD CONSTRAINT temp_check CHECK (temp > 0.0);
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_ddl_command_end: ALTER TABLE
SELECT * FROM test.show_constraints('htable');
Constraint | Type | Columns | Index | Expr | Deferrable | Deferred | Validated
------------+------+---------+-------+----------------------------------+------------+----------+-----------
temp_check | c | {temp} | - | (temp > (0.0)::double precision) | f | f | t
(1 row)
ALTER TABLE htable RENAME CONSTRAINT temp_check TO temp_chk;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable RENAME CONSTRAINT temp_check TO temp_chk;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER TABLE
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: public.htable
ALTER TABLE htable RENAME COLUMN description TO descr;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable RENAME COLUMN description TO descr;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER TABLE
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: public.htable
ALTER INDEX htable_description_idx RENAME to htable_descr_idx;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER INDEX htable_description_idx RENAME to htable_descr_idx;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER INDEX
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: public.htable
ALTER TABLE htable SET SCHEMA htable_schema;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable SET SCHEMA htable_schema;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER TABLE
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: htable_schema.htable
ALTER TABLE htable_schema.htable SET SCHEMA public;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable_schema.htable SET SCHEMA public;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER TABLE
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: public.htable
ALTER TABLE public.htable RENAME TO htable2;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE public.htable RENAME TO htable2;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER TABLE
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: public.htable2
ALTER TABLE htable2 RENAME TO htable;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable2 RENAME TO htable;
NOTICE: test_ddl_command_start: wait for ddl_command_end
NOTICE: test_ddl_command_end: ALTER TABLE
NOTICE: test_ddl_command_end: 1 hypertables scheduled
NOTICE: test_ddl_command_end: public.htable
-- DROP INDEX, TABLE
\set ON_ERROR_STOP 0
DROP INDEX htable_description_idx, htable_pk;
ERROR: cannot drop a hypertable index along with other objects
DROP TABLE htable, non_htable;
ERROR: cannot drop a hypertable along with other objects
\set ON_ERROR_STOP 1
DROP INDEX htable_descr_idx;
NOTICE: test_ddl_command_start: 1 hypertables, query: DROP INDEX htable_descr_idx;
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_sql_drop: index
NOTICE: test_ddl_command_end: DROP INDEX
DROP TABLE htable;
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP TABLE htable;
NOTICE: test_sql_drop: constraint: public.htable.temp_chk
NOTICE: test_sql_drop: index
NOTICE: test_sql_drop: index
NOTICE: test_sql_drop: table: public.htable
NOTICE: test_sql_drop: trigger
NOTICE: test_ddl_command_end: DROP TABLE
DROP TABLE non_htable;
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP TABLE non_htable;
NOTICE: test_sql_drop: table: public.non_htable
NOTICE: test_ddl_command_end: DROP TABLE
-- DROP TABLE within procedure
CREATE TABLE test (time timestamp, v int);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE test (time timestamp, v int);
NOTICE: test_ddl_command_end: CREATE TABLE
SELECT create_hypertable('test','time');
WARNING: column type "timestamp without time zone" used for "time" does not follow best practices
NOTICE: adding not-null constraint to column "time"
create_hypertable
-------------------
(2,public,test,t)
(1 row)
CREATE PROCEDURE test_drop() LANGUAGE PLPGSQL AS $$
BEGIN
DROP TABLE test;
END
$$;
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE PROCEDURE test_drop() LANGUAGE PLPGSQL AS $$
BEGIN
DROP TABLE test;
END
$$;
CALL test_drop();
NOTICE: test_ddl_command_start: 0 hypertables, query: CALL test_drop();
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP TABLE test
NOTICE: test_sql_drop: index
NOTICE: test_sql_drop: table: public.test
NOTICE: test_sql_drop: trigger
NOTICE: test_ddl_command_end: DROP TABLE
-- DROP CASCADE cases
-- DROP schema
CREATE TABLE htable_schema.non_htable (id int);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE htable_schema.non_htable (id int);
NOTICE: test_ddl_command_end: CREATE TABLE
CREATE TABLE htable_schema.htable(time timestamptz, device int, color int, temp float);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE htable_schema.htable(time timestamptz, device int, color int, temp float);
NOTICE: test_ddl_command_end: CREATE TABLE
SELECT * FROM create_hypertable('htable_schema.htable', 'time');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+---------------+------------+---------
3 | htable_schema | htable | t
(1 row)
DROP SCHEMA htable_schema CASCADE;
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP SCHEMA htable_schema CASCADE;
NOTICE: drop cascades to 2 other objects
NOTICE: test_sql_drop: index
NOTICE: test_sql_drop: table: htable_schema.non_htable
NOTICE: test_sql_drop: table: htable_schema.htable
NOTICE: test_sql_drop: schema: htable_schema
NOTICE: test_sql_drop: trigger
NOTICE: test_ddl_command_end: DROP SCHEMA
-- DROP column cascades to index drop
CREATE TABLE htable(time timestamptz, device int, color int, temp float);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE htable(time timestamptz, device int, color int, temp float);
NOTICE: test_ddl_command_end: CREATE TABLE
SELECT * FROM create_hypertable('htable', 'time');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
4 | public | htable | t
(1 row)
CREATE INDEX htable_device_idx ON htable (device);
NOTICE: test_ddl_command_start: 1 hypertables, query: CREATE INDEX htable_device_idx ON htable (device);
NOTICE: test_ddl_command_start: public.htable
ALTER TABLE htable DROP COLUMN device;
NOTICE: test_ddl_command_start: 1 hypertables, query: ALTER TABLE htable DROP COLUMN device;
NOTICE: test_ddl_command_start: public.htable
NOTICE: test_sql_drop: index
NOTICE: test_ddl_command_end: ALTER TABLE
DROP TABLE htable;
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP TABLE htable;
NOTICE: test_sql_drop: index
NOTICE: test_sql_drop: table: public.htable
NOTICE: test_sql_drop: trigger
NOTICE: test_ddl_command_end: DROP TABLE
-- DROP foreign key
CREATE TABLE non_htable (id int PRIMARY KEY);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE non_htable (id int PRIMARY KEY);
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE non_htable (id int PRIMARY KEY);
NOTICE: test_ddl_command_end: CREATE TABLE
CREATE TABLE htable(time timestamptz, device int REFERENCES non_htable(id));
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE htable(time timestamptz, device int REFERENCES non_htable(id));
NOTICE: test_ddl_command_start: 0 hypertables, query: CREATE TABLE htable(time timestamptz, device int REFERENCES non_htable(id));
NOTICE: test_ddl_command_end: CREATE TABLE
SELECT * FROM create_hypertable('htable', 'time');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
5 | public | htable | t
(1 row)
DROP TABLE non_htable CASCADE;
NOTICE: test_ddl_command_start: 0 hypertables, query: DROP TABLE non_htable CASCADE;
NOTICE: drop cascades to constraint htable_device_fkey on table htable
NOTICE: test_sql_drop: constraint: public.non_htable.non_htable_pkey
NOTICE: test_sql_drop: constraint: public.htable.htable_device_fkey
NOTICE: test_sql_drop: index
NOTICE: test_sql_drop: table: public.non_htable
NOTICE: test_sql_drop: trigger
NOTICE: test_sql_drop: trigger
NOTICE: test_sql_drop: trigger
NOTICE: test_sql_drop: trigger
NOTICE: test_ddl_command_end: DROP TABLE
-- cleanup
SELECT ts_test_ddl_command_hook_unreg();
ts_test_ddl_command_hook_unreg
--------------------------------
(1 row)

View File

@ -70,7 +70,6 @@ if(CMAKE_BUILD_TYPE MATCHES Debug)
cagg_on_cagg_joins.sql cagg_on_cagg_joins.sql
cagg_tableam.sql cagg_tableam.sql
cagg_policy_run.sql cagg_policy_run.sql
ddl_hook.sql
decompress_memory.sql decompress_memory.sql
decompress_vector_qual.sql decompress_vector_qual.sql
hypertable_generalization.sql hypertable_generalization.sql

View File

@ -1,132 +0,0 @@
-- 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.
\c :TEST_DBNAME :ROLE_SUPERUSER
CREATE OR REPLACE FUNCTION ts_test_ddl_command_hook_reg() RETURNS VOID
AS :TSL_MODULE_PATHNAME, 'ts_test_ddl_command_hook_reg'
LANGUAGE C VOLATILE STRICT;
CREATE OR REPLACE FUNCTION ts_test_ddl_command_hook_unreg() RETURNS VOID
AS :TSL_MODULE_PATHNAME, 'ts_test_ddl_command_hook_unreg'
LANGUAGE C VOLATILE STRICT;
SET client_min_messages TO ERROR;
DROP SCHEMA IF EXISTS htable_schema CASCADE;
DROP TABLE IF EXISTS htable;
DROP TABLE IF EXISTS non_htable;
SET client_min_messages TO NOTICE;
CREATE SCHEMA htable_schema;
-- Install test hooks
SELECT ts_test_ddl_command_hook_reg();
CREATE TABLE htable(time timestamptz, device int, color int CONSTRAINT color_check CHECK (color > 0), temp float);
CREATE UNIQUE INDEX htable_pk ON htable(time);
-- CREATE TABLE
SELECT * FROM create_hypertable('htable', 'time');
SELECT * FROM test.show_columns('htable');
SELECT * FROM test.show_constraints('htable');
SELECT * FROM test.show_indexes('htable');
-- ADD CONSTRAINT
ALTER TABLE htable ADD CONSTRAINT device_check CHECK (device > 0);
SELECT * FROM test.show_constraints('htable');
-- DROP CONSTRAINT
ALTER TABLE htable DROP CONSTRAINT device_check;
SELECT * FROM test.show_constraints('htable');
-- DROP COLUMN
ALTER TABLE htable DROP COLUMN color;
SELECT * FROM test.show_columns('htable');
-- ADD COLUMN
ALTER TABLE htable ADD COLUMN description text;
SELECT * FROM test.show_columns('htable');
-- CREATE INDEX
CREATE INDEX htable_description_idx ON htable (description);
SELECT * FROM test.show_indexes('htable');
-- CREATE/DROP TRIGGER
CREATE OR REPLACE FUNCTION test_trigger()
RETURNS TRIGGER LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN OLD;
END
$BODY$;
CREATE TRIGGER htable_trigger_test
BEFORE INSERT ON htable
FOR EACH ROW EXECUTE FUNCTION test_trigger();
DROP TRIGGER htable_trigger_test on htable;
DROP FUNCTION test_trigger();
-- CLUSTER, TRUNCATE, REINDEX, VACUUM (should not call event hooks)
CREATE TABLE non_htable (id int);
CLUSTER htable USING htable_description_idx;
TRUNCATE non_htable, htable;
REINDEX TABLE htable;
VACUUM htable;
-- ALTER TABLE
ALTER TABLE htable ADD CONSTRAINT temp_check CHECK (temp > 0.0);
SELECT * FROM test.show_constraints('htable');
ALTER TABLE htable RENAME CONSTRAINT temp_check TO temp_chk;
ALTER TABLE htable RENAME COLUMN description TO descr;
ALTER INDEX htable_description_idx RENAME to htable_descr_idx;
ALTER TABLE htable SET SCHEMA htable_schema;
ALTER TABLE htable_schema.htable SET SCHEMA public;
ALTER TABLE public.htable RENAME TO htable2;
ALTER TABLE htable2 RENAME TO htable;
-- DROP INDEX, TABLE
\set ON_ERROR_STOP 0
DROP INDEX htable_description_idx, htable_pk;
DROP TABLE htable, non_htable;
\set ON_ERROR_STOP 1
DROP INDEX htable_descr_idx;
DROP TABLE htable;
DROP TABLE non_htable;
-- DROP TABLE within procedure
CREATE TABLE test (time timestamp, v int);
SELECT create_hypertable('test','time');
CREATE PROCEDURE test_drop() LANGUAGE PLPGSQL AS $$
BEGIN
DROP TABLE test;
END
$$;
CALL test_drop();
-- DROP CASCADE cases
-- DROP schema
CREATE TABLE htable_schema.non_htable (id int);
CREATE TABLE htable_schema.htable(time timestamptz, device int, color int, temp float);
SELECT * FROM create_hypertable('htable_schema.htable', 'time');
DROP SCHEMA htable_schema CASCADE;
-- DROP column cascades to index drop
CREATE TABLE htable(time timestamptz, device int, color int, temp float);
SELECT * FROM create_hypertable('htable', 'time');
CREATE INDEX htable_device_idx ON htable (device);
ALTER TABLE htable DROP COLUMN device;
DROP TABLE htable;
-- DROP foreign key
CREATE TABLE non_htable (id int PRIMARY KEY);
CREATE TABLE htable(time timestamptz, device int REFERENCES non_htable(id));
SELECT * FROM create_hypertable('htable', 'time');
DROP TABLE non_htable CASCADE;
-- cleanup
SELECT ts_test_ddl_command_hook_unreg();