Skip to content

Commit bb85d09

Browse files
committed
Handle default NULL insertion a little better.
If a column is omitted in an INSERT, and there's no column default, the code in preptlist.c generates a NULL Const to be inserted. Furthermore, if the column is of a domain type, we wrap the Const in CoerceToDomain, so as to throw a run-time error if the domain has a NOT NULL constraint. That's fine as far as it goes, but there are two problems: 1. We're being sloppy about the type/typmod that the Const is labeled with. It really should have the domain's base type/typmod, since it's the input to CoerceToDomain not the output. This can result in coerce_to_domain inserting a useless length-coercion function (useless because it's being applied to a null). The coercion would typically get const-folded away later, but it'd be better not to create it in the first place. 2. We're not applying expression preprocessing (specifically, eval_const_expressions) to the resulting expression tree. The planner's primary expression-preprocessing pass already happened, so that means the length coercion step and CoerceToDomain node miss preprocessing altogether. This is at the least inefficient, since it means the length coercion and CoerceToDomain will actually be executed for each inserted row, though they could be const-folded away in most cases. Worse, it seems possible that missing preprocessing for the length coercion could result in an invalid plan (for example, due to failing to perform default-function-argument insertion). I'm not aware of any live bug of that sort with core datatypes, and it might be unreachable for extension types as well because of restrictions of CREATE CAST, but I'm not entirely convinced that it's unreachable. Hence, it seems worth back-patching the fix (although I only went back to v14, as the patch doesn't apply cleanly at all in v13). There are several places in the rewriter that are building null domain constants the same way as preptlist.c. While those are before the planner and hence don't have any reachable bug, they're still applying a length coercion that will be const-folded away later, uselessly wasting cycles. Hence, make a utility routine that all of these places can call to do it right. Making this code more careful about the typmod assigned to the generated NULL constant has visible but cosmetic effects on some of the plans shown in contrib/postgres_fdw's regression tests. Discussion: https://postgr.es/m/1865579.1738113656@sss.pgh.pa.us Backpatch-through: 14
1 parent b17e397 commit bb85d09

File tree

6 files changed

+92
-78
lines changed

6 files changed

+92
-78
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4223,13 +4223,13 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
42234223

42244224
PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo');
42254225
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
4226-
QUERY PLAN
4227-
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4226+
QUERY PLAN
4227+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
42284228
Insert on public.ft1
42294229
Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
42304230
Batch Size: 1
42314231
-> Result
4232-
Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum
4232+
Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft1 '::character(10), NULL::user_enum
42334233
(5 rows)
42344234

42354235
ALTER TABLE "S 1"."T 1" RENAME TO "T 0";
@@ -4257,13 +4257,13 @@ EXECUTE st6;
42574257
(9 rows)
42584258

42594259
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
4260-
QUERY PLAN
4261-
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4260+
QUERY PLAN
4261+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
42624262
Insert on public.ft1
42634263
Remote SQL: INSERT INTO "S 1"."T 0"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
42644264
Batch Size: 1
42654265
-> Result
4266-
Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum
4266+
Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft1 '::character(10), NULL::user_enum
42674267
(5 rows)
42684268

42694269
ALTER TABLE "S 1"."T 0" RENAME TO "T 1";
@@ -4634,13 +4634,13 @@ explain (verbose, costs off) select * from ft3 f, loct3 l
46344634
-- ===================================================================
46354635
EXPLAIN (verbose, costs off)
46364636
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
4637-
QUERY PLAN
4638-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4637+
QUERY PLAN
4638+
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
46394639
Insert on public.ft2
46404640
Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
46414641
Batch Size: 1
46424642
-> Subquery Scan on "*SELECT*"
4643-
Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum
4643+
Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum
46444644
-> Foreign Scan on public.ft2 ft2_1
46454645
Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
46464646
Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint
@@ -5750,14 +5750,14 @@ SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
57505750

57515751
EXPLAIN (verbose, costs off)
57525752
INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;
5753-
QUERY PLAN
5754-
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
5753+
QUERY PLAN
5754+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
57555755
Insert on public.ft2
57565756
Output: (ft2.tableoid)::regclass
57575757
Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
57585758
Batch Size: 1
57595759
-> Result
5760-
Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum
5760+
Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum
57615761
(6 rows)
57625762

57635763
INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;

src/backend/optimizer/prep/preptlist.c

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
#include "parser/parsetree.h"
4747
#include "utils/rel.h"
4848

49-
static List *expand_insert_targetlist(List *tlist, Relation rel);
49+
static List *expand_insert_targetlist(PlannerInfo *root, List *tlist,
50+
Relation rel);
5051

5152

5253
/*
@@ -102,7 +103,7 @@ preprocess_targetlist(PlannerInfo *root)
102103
*/
103104
tlist = parse->targetList;
104105
if (command_type == CMD_INSERT)
105-
tlist = expand_insert_targetlist(tlist, target_relation);
106+
tlist = expand_insert_targetlist(root, tlist, target_relation);
106107
else if (command_type == CMD_UPDATE)
107108
root->update_colnos = extract_update_targetlist_colnos(tlist);
108109

@@ -148,7 +149,8 @@ preprocess_targetlist(PlannerInfo *root)
148149
ListCell *l2;
149150

150151
if (action->commandType == CMD_INSERT)
151-
action->targetList = expand_insert_targetlist(action->targetList,
152+
action->targetList = expand_insert_targetlist(root,
153+
action->targetList,
152154
target_relation);
153155
else if (action->commandType == CMD_UPDATE)
154156
action->updateColnos =
@@ -352,7 +354,7 @@ extract_update_targetlist_colnos(List *tlist)
352354
* but now this code is only applied to INSERT targetlists.
353355
*/
354356
static List *
355-
expand_insert_targetlist(List *tlist, Relation rel)
357+
expand_insert_targetlist(PlannerInfo *root, List *tlist, Relation rel)
356358
{
357359
List *new_tlist = NIL;
358360
ListCell *tlist_item;
@@ -406,26 +408,18 @@ expand_insert_targetlist(List *tlist, Relation rel)
406408
* confuse code comparing the finished plan to the target
407409
* relation, however.
408410
*/
409-
Oid atttype = att_tup->atttypid;
410-
Oid attcollation = att_tup->attcollation;
411411
Node *new_expr;
412412

413413
if (!att_tup->attisdropped)
414414
{
415-
new_expr = (Node *) makeConst(atttype,
416-
-1,
417-
attcollation,
418-
att_tup->attlen,
419-
(Datum) 0,
420-
true, /* isnull */
421-
att_tup->attbyval);
422-
new_expr = coerce_to_domain(new_expr,
423-
InvalidOid, -1,
424-
atttype,
425-
COERCION_IMPLICIT,
426-
COERCE_IMPLICIT_CAST,
427-
-1,
428-
false);
415+
new_expr = coerce_null_to_domain(att_tup->atttypid,
416+
att_tup->atttypmod,
417+
att_tup->attcollation,
418+
att_tup->attlen,
419+
att_tup->attbyval);
420+
/* Must run expression preprocessing on any non-const nodes */
421+
if (!IsA(new_expr, Const))
422+
new_expr = eval_const_expressions(root, new_expr);
429423
}
430424
else
431425
{

src/backend/parser/parse_coerce.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,43 @@ coerce_to_specific_type(ParseState *pstate, Node *node,
12631263
constructName);
12641264
}
12651265

1266+
/*
1267+
* coerce_null_to_domain()
1268+
* Build a NULL constant, then wrap it in CoerceToDomain
1269+
* if the desired type is a domain type. This allows any
1270+
* NOT NULL domain constraint to be enforced at runtime.
1271+
*/
1272+
Node *
1273+
coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
1274+
int typlen, bool typbyval)
1275+
{
1276+
Node *result;
1277+
Oid baseTypeId;
1278+
int32 baseTypeMod = typmod;
1279+
1280+
/*
1281+
* The constant must appear to have the domain's base type/typmod, else
1282+
* coerce_to_domain() will apply a length coercion which is useless.
1283+
*/
1284+
baseTypeId = getBaseTypeAndTypmod(typid, &baseTypeMod);
1285+
result = (Node *) makeConst(baseTypeId,
1286+
baseTypeMod,
1287+
collation,
1288+
typlen,
1289+
(Datum) 0,
1290+
true, /* isnull */
1291+
typbyval);
1292+
if (typid != baseTypeId)
1293+
result = coerce_to_domain(result,
1294+
baseTypeId, baseTypeMod,
1295+
typid,
1296+
COERCION_IMPLICIT,
1297+
COERCE_IMPLICIT_CAST,
1298+
-1,
1299+
false);
1300+
return result;
1301+
}
1302+
12661303
/*
12671304
* parser_coercion_errposition - report coercion error location, if possible
12681305
*

src/backend/rewrite/rewriteHandler.c

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -997,23 +997,11 @@ rewriteTargetListIU(List *targetList,
997997
if (commandType == CMD_INSERT)
998998
new_tle = NULL;
999999
else
1000-
{
1001-
new_expr = (Node *) makeConst(att_tup->atttypid,
1002-
-1,
1003-
att_tup->attcollation,
1004-
att_tup->attlen,
1005-
(Datum) 0,
1006-
true, /* isnull */
1007-
att_tup->attbyval);
1008-
/* this is to catch a NOT NULL domain constraint */
1009-
new_expr = coerce_to_domain(new_expr,
1010-
InvalidOid, -1,
1011-
att_tup->atttypid,
1012-
COERCION_IMPLICIT,
1013-
COERCE_IMPLICIT_CAST,
1014-
-1,
1015-
false);
1016-
}
1000+
new_expr = coerce_null_to_domain(att_tup->atttypid,
1001+
att_tup->atttypmod,
1002+
att_tup->attcollation,
1003+
att_tup->attlen,
1004+
att_tup->attbyval);
10171005
}
10181006

10191007
if (new_expr)
@@ -1575,21 +1563,11 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
15751563
continue;
15761564
}
15771565

1578-
new_expr = (Node *) makeConst(att_tup->atttypid,
1579-
-1,
1580-
att_tup->attcollation,
1581-
att_tup->attlen,
1582-
(Datum) 0,
1583-
true, /* isnull */
1584-
att_tup->attbyval);
1585-
/* this is to catch a NOT NULL domain constraint */
1586-
new_expr = coerce_to_domain(new_expr,
1587-
InvalidOid, -1,
1588-
att_tup->atttypid,
1589-
COERCION_IMPLICIT,
1590-
COERCE_IMPLICIT_CAST,
1591-
-1,
1592-
false);
1566+
new_expr = coerce_null_to_domain(att_tup->atttypid,
1567+
att_tup->atttypmod,
1568+
att_tup->attcollation,
1569+
att_tup->attlen,
1570+
att_tup->attbyval);
15931571
}
15941572
newList = lappend(newList, new_expr);
15951573
}

src/backend/rewrite/rewriteManip.c

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "parser/parse_relation.h"
2323
#include "parser/parsetree.h"
2424
#include "rewrite/rewriteManip.h"
25+
#include "utils/lsyscache.h"
2526

2627

2728
typedef struct
@@ -1466,20 +1467,21 @@ ReplaceVarsFromTargetList_callback(Var *var,
14661467
return (Node *) var;
14671468

14681469
case REPLACEVARS_SUBSTITUTE_NULL:
1469-
1470-
/*
1471-
* If Var is of domain type, we should add a CoerceToDomain
1472-
* node, in case there is a NOT NULL domain constraint.
1473-
*/
1474-
return coerce_to_domain((Node *) makeNullConst(var->vartype,
1475-
var->vartypmod,
1476-
var->varcollid),
1477-
InvalidOid, -1,
1478-
var->vartype,
1479-
COERCION_IMPLICIT,
1480-
COERCE_IMPLICIT_CAST,
1481-
-1,
1482-
false);
1470+
{
1471+
/*
1472+
* If Var is of domain type, we must add a CoerceToDomain
1473+
* node, in case there is a NOT NULL domain constraint.
1474+
*/
1475+
int16 vartyplen;
1476+
bool vartypbyval;
1477+
1478+
get_typlenbyval(var->vartype, &vartyplen, &vartypbyval);
1479+
return coerce_null_to_domain(var->vartype,
1480+
var->vartypmod,
1481+
var->varcollid,
1482+
vartyplen,
1483+
vartypbyval);
1484+
}
14831485
}
14841486
elog(ERROR, "could not find replacement targetlist entry for attno %d",
14851487
var->varattno);

src/include/parser/parse_coerce.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ extern Node *coerce_to_specific_type_typmod(ParseState *pstate, Node *node,
6161
Oid targetTypeId, int32 targetTypmod,
6262
const char *constructName);
6363

64+
extern Node *coerce_null_to_domain(Oid typid, int32 typmod, Oid collation,
65+
int typlen, bool typbyval);
66+
6467
extern int parser_coercion_errposition(ParseState *pstate,
6568
int coerce_location,
6669
Node *input_expr);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy