Skip to content

Commit 5c26328

Browse files
committed
Fix INSERT on hypertables using sub-selects with aggregates
When inserting into a hypertable using a sub-select clause that involves an aggregate, the insert fails with the error "Aggref found in non-Agg plan node". This happens because the target list from the aggregate sub-select expects an aggregate parent node and we simply reuse the target list when modifying the insert plan with new CustomScan plan nodes. This change creates a modified target list to use with the CustomScan node that avoids this issue.
1 parent b57e2bf commit 5c26328

File tree

5 files changed

+519
-10
lines changed

5 files changed

+519
-10
lines changed

src/chunk_dispatch_plan.c

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <nodes/readfuncs.h>
66
#include <utils/rel.h>
77
#include <catalog/pg_type.h>
8+
#include <rewrite/rewriteManip.h>
89

910
#include "chunk_dispatch_plan.h"
1011
#include "chunk_dispatch_state.h"
@@ -27,6 +28,84 @@ static CustomScanMethods chunk_dispatch_plan_methods = {
2728
.CreateCustomScanState = create_chunk_dispatch_state,
2829
};
2930

31+
/*
32+
* Build a target list for a CustomScan node.
33+
*
34+
* The CustomScan node needs to provide a modified target list in case of
35+
* subplan nodes that have target lists with expressions that involve, e.g.,
36+
* aggregates. Such target lists need a matching parent node type (e.g., a
37+
* target list with an AggDef needs a Agg node parent). If we simply use the
38+
* subplan's target list on a CustomScan parent node, there might be a mismatch.
39+
*
40+
* The code here is similar to ExecCheckPlanOutput() in nodeModifyTable.c in the
41+
* PostgreSQL source code, but here we construct a modified target list for the
42+
* CustomScan node instead of simply just checking validity.
43+
*
44+
*/
45+
static List *
46+
build_customscan_targetlist(Relation rel, List *targetlist)
47+
{
48+
TupleDesc tupdesc = RelationGetDescr(rel);
49+
List *new_targetlist = NIL;
50+
ListCell *lc;
51+
int attno = 0;
52+
53+
foreach(lc, targetlist)
54+
{
55+
TargetEntry *te = lfirst(lc);
56+
Form_pg_attribute attr;
57+
Expr *expr;
58+
59+
/* Ignore junk items */
60+
if (te->resjunk)
61+
continue;
62+
63+
if (attno >= tupdesc->natts)
64+
ereport(ERROR,
65+
(errcode(ERRCODE_DATATYPE_MISMATCH),
66+
errmsg("table row type and query-specified row type do not match"),
67+
errdetail("Query has too many columns.")));
68+
69+
attr = tupdesc->attrs[attno++];
70+
71+
if (attr->attisdropped)
72+
expr = &makeConst(INT4OID,
73+
-1,
74+
InvalidOid,
75+
sizeof(int32),
76+
(Datum) 0,
77+
true,
78+
true)->xpr;
79+
else
80+
81+
/*
82+
* According to nodes/primnodes.h, the INDEX_VAR Var type is
83+
* abused in CustomScan nodes to reference custom scan tuple
84+
* types.
85+
*/
86+
expr = &makeVar(INDEX_VAR,
87+
attno,
88+
exprType((Node *) te->expr),
89+
exprTypmod((Node *) te->expr),
90+
exprCollation((Node *) te->expr),
91+
0)->xpr;
92+
93+
new_targetlist = lappend(new_targetlist,
94+
makeTargetEntry(expr,
95+
attno,
96+
NULL,
97+
te->resjunk));
98+
}
99+
100+
if (attno != tupdesc->natts)
101+
ereport(ERROR,
102+
(errcode(ERRCODE_DATATYPE_MISMATCH),
103+
errmsg("table row type and query-specified row type do not match"),
104+
errdetail("Query has too few columns.")));
105+
106+
return new_targetlist;
107+
}
108+
30109
/* Create a chunk dispatch plan node in the form of a CustomScan node. The
31110
* purpose of this plan node is to dispatch (route) tuples to the correct chunk
32111
* in a hypertable.
@@ -42,10 +121,11 @@ static CustomScanMethods chunk_dispatch_plan_methods = {
42121
* node.
43122
*/
44123
CustomScan *
45-
chunk_dispatch_plan_create(Plan *subplan, Oid hypertable_relid, Query *parse)
124+
chunk_dispatch_plan_create(Plan *subplan, Index hypertable_rti, Oid hypertable_relid, Query *parse)
46125
{
47126
CustomScan *cscan = makeNode(CustomScan);
48127
ChunkDispatchInfo *info = chunk_dispatch_info_create(hypertable_relid, parse);
128+
Relation rel;
49129

50130
cscan->custom_private = list_make1(info);
51131
cscan->methods = &chunk_dispatch_plan_methods;
@@ -60,11 +140,20 @@ chunk_dispatch_plan_create(Plan *subplan, Oid hypertable_relid, Query *parse)
60140
cscan->scan.plan.plan_width = subplan->plan_width;
61141

62142
/*
63-
* Copy target list from parent table. This should work since hypertables
64-
* mandate that chunks have identical column definitions
143+
* Create a modified target list for the CustomScan based on the subplan's
144+
* original target list
145+
*/
146+
rel = relation_open(hypertable_relid, AccessShareLock);
147+
cscan->scan.plan.targetlist = build_customscan_targetlist(rel, subplan->targetlist);
148+
RelationClose(rel);
149+
150+
/*
151+
* We need to set a custom_scan_tlist for EXPLAIN (verbose). But this
152+
* target list needs to reference the proper rangetable entry so we must
153+
* replace all INDEX_VAR attnos with the hypertable's rangetable index.
65154
*/
66-
cscan->scan.plan.targetlist = subplan->targetlist;
67-
cscan->custom_scan_tlist = NIL;
155+
cscan->custom_scan_tlist = copyObject(cscan->scan.plan.targetlist);
156+
ChangeVarNodes((Node *) cscan->custom_scan_tlist, INDEX_VAR, hypertable_rti, 0);
68157

69158
return cscan;
70159
}

src/chunk_dispatch_plan.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
#include <nodes/parsenodes.h>
77
#include <nodes/extensible.h>
88

9-
extern CustomScan *chunk_dispatch_plan_create(Plan *subplan, Oid hypertable_relid, Query *parse);
9+
extern CustomScan *chunk_dispatch_plan_create(Plan *subplan, Index hypertable_rti, Oid hypertable_relid, Query *parse);
1010

1111
#endif /* TIMESCALEDB_CHUNK_DISPATCH_PLAN_H */

src/planner.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ modifytable_plan_walker(Plan **planptr, void *pctx)
382382
*/
383383
forboth(lc_plan, mt->plans, lc_rel, mt->resultRelations)
384384
{
385-
RangeTblEntry *rte = rt_fetch(lfirst_int(lc_rel), ctx->rtable);
385+
Index rti = lfirst_int(lc_rel);
386+
RangeTblEntry *rte = rt_fetch(rti, ctx->rtable);
386387
Hypertable *ht = hypertable_cache_get_entry(ctx->hcache, rte->relid);
387388

388389
if (ht != NULL)
@@ -394,7 +395,7 @@ modifytable_plan_walker(Plan **planptr, void *pctx)
394395
* We replace the plan with our custom chunk dispatch
395396
* plan.
396397
*/
397-
*subplan_ptr = chunk_dispatch_plan_create(subplan, rte->relid, ctx->parse);
398+
*subplan_ptr = chunk_dispatch_plan_create(subplan, rti, rte->relid, ctx->parse);
398399
hypertable_found = true;
399400
}
400401
}

0 commit comments

Comments
 (0)