mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-16 02:23:49 +08:00
Remove AsyncAppend custom node
This commit is contained in:
parent
dde86ac116
commit
b1159eba68
@ -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)
|
||||||
|
@ -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));
|
|
||||||
}
|
|
@ -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);
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
|
|
@ -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
|
||||||
|
@ -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();
|
|
Loading…
x
Reference in New Issue
Block a user