8
8
*
9
9
*
10
10
* IDENTIFICATION
11
- * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.136 2009/11/04 22:26:05 tgl Exp $
11
+ * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.137 2009/12/14 02:15:51 tgl Exp $
12
12
*
13
13
*-------------------------------------------------------------------------
14
14
*/
@@ -338,7 +338,7 @@ init_sql_fcache(FmgrInfo *finfo, bool lazyEvalOK)
338
338
fcache -> returnsTuple = check_sql_fn_retval (foid ,
339
339
rettype ,
340
340
queryTree_list ,
341
- false ,
341
+ NULL ,
342
342
& fcache -> junkFilter );
343
343
344
344
if (fcache -> returnsTuple )
@@ -1003,14 +1003,27 @@ ShutdownSQLFunction(Datum arg)
1003
1003
* function definition of a polymorphic function.)
1004
1004
*
1005
1005
* This function returns true if the sql function returns the entire tuple
1006
- * result of its final statement, and false otherwise. Note that because we
1007
- * allow "SELECT rowtype_expression", this may be false even when the declared
1008
- * function return type is a rowtype .
1006
+ * result of its final statement, or false if it returns just the first column
1007
+ * result of that statement. It throws an error if the final statement doesn't
1008
+ * return the right type at all .
1009
1009
*
1010
- * If insertRelabels is true, then binary-compatible cases are dealt with
1011
- * by actually inserting RelabelType nodes into the output targetlist;
1012
- * obviously the caller must pass a parsetree that it's okay to modify in this
1013
- * case.
1010
+ * Note that because we allow "SELECT rowtype_expression", the result can be
1011
+ * false even when the declared function return type is a rowtype.
1012
+ *
1013
+ * If modifyTargetList isn't NULL, the function will modify the final
1014
+ * statement's targetlist in two cases:
1015
+ * (1) if the tlist returns values that are binary-coercible to the expected
1016
+ * type rather than being exactly the expected type. RelabelType nodes will
1017
+ * be inserted to make the result types match exactly.
1018
+ * (2) if there are dropped columns in the declared result rowtype. NULL
1019
+ * output columns will be inserted in the tlist to match them.
1020
+ * (Obviously the caller must pass a parsetree that is okay to modify when
1021
+ * using this flag.) Note that this flag does not affect whether the tlist is
1022
+ * considered to be a legal match to the result type, only how we react to
1023
+ * allowed not-exact-match cases. *modifyTargetList will be set true iff
1024
+ * we had to make any "dangerous" changes that could modify the semantics of
1025
+ * the statement. If it is set true, the caller should not use the modified
1026
+ * statement, but for simplicity we apply the changes anyway.
1014
1027
*
1015
1028
* If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined
1016
1029
* to convert the function's tuple result to the correct output tuple type.
@@ -1019,10 +1032,11 @@ ShutdownSQLFunction(Datum arg)
1019
1032
*/
1020
1033
bool
1021
1034
check_sql_fn_retval (Oid func_id , Oid rettype , List * queryTreeList ,
1022
- bool insertRelabels ,
1035
+ bool * modifyTargetList ,
1023
1036
JunkFilter * * junkFilter )
1024
1037
{
1025
1038
Query * parse ;
1039
+ List * * tlist_ptr ;
1026
1040
List * tlist ;
1027
1041
int tlistlen ;
1028
1042
char fn_typtype ;
@@ -1031,6 +1045,8 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1031
1045
1032
1046
AssertArg (!IsPolymorphicType (rettype ));
1033
1047
1048
+ if (modifyTargetList )
1049
+ * modifyTargetList = false; /* initialize for no change */
1034
1050
if (junkFilter )
1035
1051
* junkFilter = NULL ; /* initialize in case of VOID result */
1036
1052
@@ -1064,6 +1080,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1064
1080
parse -> utilityStmt == NULL &&
1065
1081
parse -> intoClause == NULL )
1066
1082
{
1083
+ tlist_ptr = & parse -> targetList ;
1067
1084
tlist = parse -> targetList ;
1068
1085
}
1069
1086
else if (parse &&
@@ -1072,6 +1089,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1072
1089
parse -> commandType == CMD_DELETE ) &&
1073
1090
parse -> returningList )
1074
1091
{
1092
+ tlist_ptr = & parse -> returningList ;
1075
1093
tlist = parse -> returningList ;
1076
1094
}
1077
1095
else
@@ -1132,11 +1150,16 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1132
1150
format_type_be (rettype )),
1133
1151
errdetail ("Actual return type is %s." ,
1134
1152
format_type_be (restype ))));
1135
- if (insertRelabels && restype != rettype )
1153
+ if (modifyTargetList && restype != rettype )
1154
+ {
1136
1155
tle -> expr = (Expr * ) makeRelabelType (tle -> expr ,
1137
1156
rettype ,
1138
1157
-1 ,
1139
1158
COERCE_DONTCARE );
1159
+ /* Relabel is dangerous if TLE is a sort/group or setop column */
1160
+ if (tle -> ressortgroupref != 0 || parse -> setOperations )
1161
+ * modifyTargetList = true;
1162
+ }
1140
1163
1141
1164
/* Set up junk filter if needed */
1142
1165
if (junkFilter )
@@ -1149,6 +1172,8 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1149
1172
int tupnatts ; /* physical number of columns in tuple */
1150
1173
int tuplogcols ; /* # of nondeleted columns in tuple */
1151
1174
int colindex ; /* physical column index */
1175
+ List * newtlist ; /* new non-junk tlist entries */
1176
+ List * junkattrs ; /* new junk tlist entries */
1152
1177
1153
1178
/*
1154
1179
* If the target list is of length 1, and the type of the varnode in
@@ -1165,11 +1190,16 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1165
1190
restype = exprType ((Node * ) tle -> expr );
1166
1191
if (IsBinaryCoercible (restype , rettype ))
1167
1192
{
1168
- if (insertRelabels && restype != rettype )
1193
+ if (modifyTargetList && restype != rettype )
1194
+ {
1169
1195
tle -> expr = (Expr * ) makeRelabelType (tle -> expr ,
1170
1196
rettype ,
1171
1197
-1 ,
1172
1198
COERCE_DONTCARE );
1199
+ /* Relabel is dangerous if sort/group or setop column */
1200
+ if (tle -> ressortgroupref != 0 || parse -> setOperations )
1201
+ * modifyTargetList = true;
1202
+ }
1173
1203
/* Set up junk filter if needed */
1174
1204
if (junkFilter )
1175
1205
* junkFilter = ExecInitJunkFilter (tlist , false, NULL );
@@ -1193,11 +1223,14 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1193
1223
/*
1194
1224
* Verify that the targetlist matches the return tuple type. We scan
1195
1225
* the non-deleted attributes to ensure that they match the datatypes
1196
- * of the non-resjunk columns.
1226
+ * of the non-resjunk columns. For deleted attributes, insert NULL
1227
+ * result columns if the caller asked for that.
1197
1228
*/
1198
1229
tupnatts = tupdesc -> natts ;
1199
1230
tuplogcols = 0 ; /* we'll count nondeleted cols as we go */
1200
1231
colindex = 0 ;
1232
+ newtlist = NIL ; /* these are only used if modifyTargetList */
1233
+ junkattrs = NIL ;
1201
1234
1202
1235
foreach (lc , tlist )
1203
1236
{
@@ -1207,7 +1240,11 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1207
1240
Oid atttype ;
1208
1241
1209
1242
if (tle -> resjunk )
1243
+ {
1244
+ if (modifyTargetList )
1245
+ junkattrs = lappend (junkattrs , tle );
1210
1246
continue ;
1247
+ }
1211
1248
1212
1249
do
1213
1250
{
@@ -1219,6 +1256,26 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1219
1256
format_type_be (rettype )),
1220
1257
errdetail ("Final statement returns too many columns." )));
1221
1258
attr = tupdesc -> attrs [colindex - 1 ];
1259
+ if (attr -> attisdropped && modifyTargetList )
1260
+ {
1261
+ Expr * null_expr ;
1262
+
1263
+ /* The type of the null we insert isn't important */
1264
+ null_expr = (Expr * ) makeConst (INT4OID ,
1265
+ -1 ,
1266
+ sizeof (int32 ),
1267
+ (Datum ) 0 ,
1268
+ true, /* isnull */
1269
+ true /* byval */ );
1270
+ newtlist = lappend (newtlist ,
1271
+ makeTargetEntry (null_expr ,
1272
+ colindex ,
1273
+ NULL ,
1274
+ false));
1275
+ /* NULL insertion is dangerous in a setop */
1276
+ if (parse -> setOperations )
1277
+ * modifyTargetList = true;
1278
+ }
1222
1279
} while (attr -> attisdropped );
1223
1280
tuplogcols ++ ;
1224
1281
@@ -1233,28 +1290,66 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
1233
1290
format_type_be (tletype ),
1234
1291
format_type_be (atttype ),
1235
1292
tuplogcols )));
1236
- if (insertRelabels && tletype != atttype )
1237
- tle -> expr = (Expr * ) makeRelabelType (tle -> expr ,
1238
- atttype ,
1239
- -1 ,
1240
- COERCE_DONTCARE );
1293
+ if (modifyTargetList )
1294
+ {
1295
+ if (tletype != atttype )
1296
+ {
1297
+ tle -> expr = (Expr * ) makeRelabelType (tle -> expr ,
1298
+ atttype ,
1299
+ -1 ,
1300
+ COERCE_DONTCARE );
1301
+ /* Relabel is dangerous if sort/group or setop column */
1302
+ if (tle -> ressortgroupref != 0 || parse -> setOperations )
1303
+ * modifyTargetList = true;
1304
+ }
1305
+ tle -> resno = colindex ;
1306
+ newtlist = lappend (newtlist , tle );
1307
+ }
1241
1308
}
1242
1309
1243
- for (;;)
1310
+ /* remaining columns in tupdesc had better all be dropped */
1311
+ for (colindex ++ ; colindex <= tupnatts ; colindex ++ )
1244
1312
{
1245
- colindex ++ ;
1246
- if (colindex > tupnatts )
1247
- break ;
1248
1313
if (!tupdesc -> attrs [colindex - 1 ]-> attisdropped )
1249
- tuplogcols ++ ;
1314
+ ereport (ERROR ,
1315
+ (errcode (ERRCODE_INVALID_FUNCTION_DEFINITION ),
1316
+ errmsg ("return type mismatch in function declared to return %s" ,
1317
+ format_type_be (rettype )),
1318
+ errdetail ("Final statement returns too few columns." )));
1319
+ if (modifyTargetList )
1320
+ {
1321
+ Expr * null_expr ;
1322
+
1323
+ /* The type of the null we insert isn't important */
1324
+ null_expr = (Expr * ) makeConst (INT4OID ,
1325
+ -1 ,
1326
+ sizeof (int32 ),
1327
+ (Datum ) 0 ,
1328
+ true, /* isnull */
1329
+ true /* byval */ );
1330
+ newtlist = lappend (newtlist ,
1331
+ makeTargetEntry (null_expr ,
1332
+ colindex ,
1333
+ NULL ,
1334
+ false));
1335
+ /* NULL insertion is dangerous in a setop */
1336
+ if (parse -> setOperations )
1337
+ * modifyTargetList = true;
1338
+ }
1250
1339
}
1251
1340
1252
- if (tlistlen != tuplogcols )
1253
- ereport (ERROR ,
1254
- (errcode (ERRCODE_INVALID_FUNCTION_DEFINITION ),
1255
- errmsg ("return type mismatch in function declared to return %s" ,
1256
- format_type_be (rettype )),
1257
- errdetail ("Final statement returns too few columns." )));
1341
+ if (modifyTargetList )
1342
+ {
1343
+ /* ensure resjunk columns are numbered correctly */
1344
+ foreach (lc , junkattrs )
1345
+ {
1346
+ TargetEntry * tle = (TargetEntry * ) lfirst (lc );
1347
+
1348
+ tle -> resno = colindex ++ ;
1349
+ }
1350
+ /* replace the tlist with the modified one */
1351
+ * tlist_ptr = list_concat (newtlist , junkattrs );
1352
+ }
1258
1353
1259
1354
/* Set up junk filter if needed */
1260
1355
if (junkFilter )
0 commit comments