Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * fastpath.c
4 : : * routines to handle function requests from the frontend
5 : : *
6 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/tcop/fastpath.c
12 : : *
13 : : * NOTES
14 : : * This cruft is the server side of PQfn.
15 : : *
16 : : *-------------------------------------------------------------------------
17 : : */
18 : : #include "postgres.h"
19 : :
20 : : #include "access/htup_details.h"
21 : : #include "access/xact.h"
22 : : #include "catalog/objectaccess.h"
23 : : #include "catalog/pg_namespace.h"
24 : : #include "catalog/pg_proc.h"
25 : : #include "libpq/pqformat.h"
26 : : #include "libpq/protocol.h"
27 : : #include "mb/pg_wchar.h"
28 : : #include "miscadmin.h"
29 : : #include "tcop/fastpath.h"
30 : : #include "tcop/tcopprot.h"
31 : : #include "utils/acl.h"
32 : : #include "utils/lsyscache.h"
33 : : #include "utils/snapmgr.h"
34 : : #include "utils/syscache.h"
35 : :
36 : :
37 : : /*
38 : : * Formerly, this code attempted to cache the function and type info
39 : : * looked up by fetch_fp_info, but only for the duration of a single
40 : : * transaction command (since in theory the info could change between
41 : : * commands). This was utterly useless, because postgres.c executes
42 : : * each fastpath call as a separate transaction command, and so the
43 : : * cached data could never actually have been reused. If it had worked
44 : : * as intended, it would have had problems anyway with dangling references
45 : : * in the FmgrInfo struct. So, forget about caching and just repeat the
46 : : * syscache fetches on each usage. They're not *that* expensive.
47 : : */
48 : : struct fp_info
49 : : {
50 : : Oid funcid;
51 : : FmgrInfo flinfo; /* function lookup info for funcid */
52 : : Oid namespace; /* other stuff from pg_proc */
53 : : Oid rettype;
54 : : Oid argtypes[FUNC_MAX_ARGS];
55 : : char fname[NAMEDATALEN]; /* function name for logging */
56 : : };
57 : :
58 : :
59 : : static int16 parse_fcall_arguments(StringInfo msgBuf, struct fp_info *fip,
60 : : FunctionCallInfo fcinfo);
61 : :
62 : : /* ----------------
63 : : * SendFunctionResult
64 : : * ----------------
65 : : */
66 : : static void
8156 tgl@sss.pgh.pa.us 67 :CBC 1084 : SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
68 : : {
69 : : StringInfoData buf;
70 : :
746 nathan@postgresql.or 71 : 1084 : pq_beginmessage(&buf, PqMsg_FunctionCallResponse);
72 : :
8156 tgl@sss.pgh.pa.us 73 [ - + ]: 1084 : if (isnull)
74 : : {
1647 heikki.linnakangas@i 75 :UBC 0 : pq_sendint32(&buf, -1);
76 : : }
77 : : else
78 : : {
8156 tgl@sss.pgh.pa.us 79 [ - + ]:CBC 1084 : if (format == 0)
80 : : {
81 : : Oid typoutput;
82 : : bool typisvarlena;
83 : : char *outputstr;
84 : :
7433 tgl@sss.pgh.pa.us 85 :UBC 0 : getTypeOutputInfo(rettype, &typoutput, &typisvarlena);
7095 86 : 0 : outputstr = OidOutputFunctionCall(typoutput, retval);
551 heikki.linnakangas@i 87 : 0 : pq_sendcountedtext(&buf, outputstr, strlen(outputstr));
8156 tgl@sss.pgh.pa.us 88 : 0 : pfree(outputstr);
89 : : }
8156 tgl@sss.pgh.pa.us 90 [ + - ]:CBC 1084 : else if (format == 1)
91 : : {
92 : : Oid typsend;
93 : : bool typisvarlena;
94 : : bytea *outputbytes;
95 : :
7433 96 : 1084 : getTypeBinaryOutputInfo(rettype, &typsend, &typisvarlena);
7095 97 : 1084 : outputbytes = OidSendFunctionCall(typsend, retval);
2887 andres@anarazel.de 98 : 1084 : pq_sendint32(&buf, VARSIZE(outputbytes) - VARHDRSZ);
8156 tgl@sss.pgh.pa.us 99 : 1084 : pq_sendbytes(&buf, VARDATA(outputbytes),
100 : 1084 : VARSIZE(outputbytes) - VARHDRSZ);
101 : 1084 : pfree(outputbytes);
102 : : }
103 : : else
8082 tgl@sss.pgh.pa.us 104 [ # # ]:UBC 0 : ereport(ERROR,
105 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
106 : : errmsg("unsupported format code: %d", format)));
107 : : }
108 : :
9631 tgl@sss.pgh.pa.us 109 :CBC 1084 : pq_endmessage(&buf);
10651 scrappy@hub.org 110 : 1084 : }
111 : :
112 : : /*
113 : : * fetch_fp_info
114 : : *
115 : : * Performs catalog lookups to load a struct fp_info 'fip' for the
116 : : * function 'func_id'.
117 : : */
118 : : static void
2999 tgl@sss.pgh.pa.us 119 : 1084 : fetch_fp_info(Oid func_id, struct fp_info *fip)
120 : : {
121 : : HeapTuple func_htp;
122 : : Form_pg_proc pp;
123 : :
7913 neilc@samurai.com 124 [ - + ]: 1084 : Assert(fip != NULL);
125 : :
126 : : /*
127 : : * Since the validity of this structure is determined by whether the
128 : : * funcid is OK, we clear the funcid here. It must not be set to the
129 : : * correct value until we are about to return with a good struct fp_info,
130 : : * since we can be interrupted (i.e., with an ereport(ERROR, ...)) at any
131 : : * time. [No longer really an issue since we don't save the struct
132 : : * fp_info across transactions anymore, but keep it anyway.]
133 : : */
7466 tgl@sss.pgh.pa.us 134 [ + - + - : 72628 : MemSet(fip, 0, sizeof(struct fp_info));
+ - + - +
+ ]
10226 bruce@momjian.us 135 : 1084 : fip->funcid = InvalidOid;
136 : :
5683 rhaas@postgresql.org 137 : 1084 : func_htp = SearchSysCache1(PROCOID, ObjectIdGetDatum(func_id));
10226 bruce@momjian.us 138 [ - + ]: 1084 : if (!HeapTupleIsValid(func_htp))
8082 tgl@sss.pgh.pa.us 139 [ # # ]:UBC 0 : ereport(ERROR,
140 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
141 : : errmsg("function with OID %u does not exist", func_id)));
10226 bruce@momjian.us 142 :CBC 1084 : pp = (Form_pg_proc) GETSTRUCT(func_htp);
143 : :
144 : : /* reject pg_proc entries that are unsafe to call via fastpath */
1590 tgl@sss.pgh.pa.us 145 [ + - - + ]: 1084 : if (pp->prokind != PROKIND_FUNCTION || pp->proretset)
1590 tgl@sss.pgh.pa.us 146 [ # # ]:UBC 0 : ereport(ERROR,
147 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
148 : : errmsg("cannot call function \"%s\" via fastpath interface",
149 : : NameStr(pp->proname))));
150 : :
151 : : /* watch out for catalog entries with more than FUNC_MAX_ARGS args */
7466 tgl@sss.pgh.pa.us 152 [ - + ]:CBC 1084 : if (pp->pronargs > FUNC_MAX_ARGS)
7466 tgl@sss.pgh.pa.us 153 [ # # ]:UBC 0 : elog(ERROR, "function %s has more than %d arguments",
154 : : NameStr(pp->proname), FUNC_MAX_ARGS);
155 : :
8156 tgl@sss.pgh.pa.us 156 :CBC 1084 : fip->namespace = pp->pronamespace;
157 : 1084 : fip->rettype = pp->prorettype;
7466 158 : 1084 : memcpy(fip->argtypes, pp->proargtypes.values, pp->pronargs * sizeof(Oid));
6897 159 : 1084 : strlcpy(fip->fname, NameStr(pp->proname), NAMEDATALEN);
160 : :
9060 161 : 1084 : ReleaseSysCache(func_htp);
162 : :
1590 163 : 1084 : fmgr_info(func_id, &fip->flinfo);
164 : :
165 : : /*
166 : : * This must be last!
167 : : */
10226 bruce@momjian.us 168 : 1084 : fip->funcid = func_id;
10651 scrappy@hub.org 169 : 1084 : }
170 : :
171 : :
172 : : /*
173 : : * HandleFunctionRequest
174 : : *
175 : : * Server side of PQfn (fastpath function calls from the frontend).
176 : : * This corresponds to the libpq protocol symbol "F".
177 : : *
178 : : * INPUT:
179 : : * postgres.c has already read the message body and will pass it in
180 : : * msgBuf.
181 : : *
182 : : * Note: palloc()s done here and in the called function do not need to be
183 : : * cleaned up explicitly. We are called from PostgresMain() in the
184 : : * MessageContext memory context, which will be automatically reset when
185 : : * control returns to PostgresMain.
186 : : */
187 : : void
8176 tgl@sss.pgh.pa.us 188 : 1084 : HandleFunctionRequest(StringInfo msgBuf)
189 : : {
2415 andres@anarazel.de 190 : 1084 : LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
191 : : Oid fid;
192 : : AclResult aclresult;
193 : : int16 rformat;
194 : : Datum retval;
195 : : struct fp_info my_fp;
196 : : struct fp_info *fip;
197 : : bool callit;
6938 tgl@sss.pgh.pa.us 198 : 1084 : bool was_logged = false;
199 : : char msec_str[32];
200 : :
201 : : /*
202 : : * We only accept COMMIT/ABORT if we are in an aborted transaction, and
203 : : * COMMIT/ABORT cannot be executed through the fastpath interface.
204 : : */
8176 205 [ - + ]: 1084 : if (IsAbortedTransactionBlockState())
8082 tgl@sss.pgh.pa.us 206 [ # # ]:UBC 0 : ereport(ERROR,
207 : : (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
208 : : errmsg("current transaction is aborted, "
209 : : "commands ignored until end of transaction block")));
210 : :
211 : : /*
212 : : * Now that we know we are in a valid transaction, set snapshot in case
213 : : * needed by function itself or one of the datatype I/O routines.
214 : : */
6326 alvherre@alvh.no-ip. 215 :CBC 1084 : PushActiveSnapshot(GetTransactionSnapshot());
216 : :
217 : : /*
218 : : * Begin parsing the buffer contents.
219 : : */
2999 tgl@sss.pgh.pa.us 220 : 1084 : fid = (Oid) pq_getmsgint(msgBuf, 4); /* function oid */
221 : :
222 : : /*
223 : : * There used to be a lame attempt at caching lookup info here. Now we
224 : : * just do the lookups on every call.
225 : : */
8863 226 : 1084 : fip = &my_fp;
227 : 1084 : fetch_fp_info(fid, fip);
228 : :
229 : : /* Log as soon as we have the function OID and name */
6897 230 [ + + ]: 1084 : if (log_statement == LOGSTMT_ALL)
231 : : {
232 [ + - ]: 544 : ereport(LOG,
233 : : (errmsg("fastpath function call: \"%s\" (OID %u)",
234 : : fip->fname, fid)));
235 : 544 : was_logged = true;
236 : : }
237 : :
238 : : /*
239 : : * Check permission to access and call function. Since we didn't go
240 : : * through a normal name lookup, we need to check schema usage too.
241 : : */
1028 peter@eisentraut.org 242 : 1084 : aclresult = object_aclcheck(NamespaceRelationId, fip->namespace, GetUserId(), ACL_USAGE);
8156 tgl@sss.pgh.pa.us 243 [ - + ]: 1084 : if (aclresult != ACLCHECK_OK)
2835 peter_e@gmx.net 244 :UBC 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
8072 tgl@sss.pgh.pa.us 245 : 0 : get_namespace_name(fip->namespace));
4537 rhaas@postgresql.org 246 [ - + ]:CBC 1084 : InvokeNamespaceSearchHook(fip->namespace, true);
247 : :
1028 peter@eisentraut.org 248 : 1084 : aclresult = object_aclcheck(ProcedureRelationId, fid, GetUserId(), ACL_EXECUTE);
8176 tgl@sss.pgh.pa.us 249 [ - + ]: 1084 : if (aclresult != ACLCHECK_OK)
2835 peter_e@gmx.net 250 :UBC 0 : aclcheck_error(aclresult, OBJECT_FUNCTION,
8072 tgl@sss.pgh.pa.us 251 : 0 : get_func_name(fid));
4530 rhaas@postgresql.org 252 [ - + ]:CBC 1084 : InvokeFunctionExecuteHook(fid);
253 : :
254 : : /*
255 : : * Prepare function call info block and insert arguments.
256 : : *
257 : : * Note: for now we pass collation = InvalidOid, so collation-sensitive
258 : : * functions can't be called this way. Perhaps we should pass
259 : : * DEFAULT_COLLATION_OID, instead?
260 : : */
2415 andres@anarazel.de 261 : 1084 : InitFunctionCallInfoData(*fcinfo, &fip->flinfo, 0, InvalidOid, NULL, NULL);
262 : :
1647 heikki.linnakangas@i 263 : 1084 : rformat = parse_fcall_arguments(msgBuf, fip, fcinfo);
264 : :
265 : : /* Verify we reached the end of the message where expected. */
8157 tgl@sss.pgh.pa.us 266 : 1084 : pq_getmsgend(msgBuf);
267 : :
268 : : /*
269 : : * If func is strict, must not call it for null args.
270 : : */
8156 271 : 1084 : callit = true;
272 [ + - ]: 1084 : if (fip->flinfo.fn_strict)
273 : : {
274 : : int i;
275 : :
2415 andres@anarazel.de 276 [ + + ]: 3137 : for (i = 0; i < fcinfo->nargs; i++)
277 : : {
278 [ - + ]: 2053 : if (fcinfo->args[i].isnull)
279 : : {
8156 tgl@sss.pgh.pa.us 280 :UBC 0 : callit = false;
281 : 0 : break;
282 : : }
283 : : }
284 : : }
285 : :
8156 tgl@sss.pgh.pa.us 286 [ + - ]:CBC 1084 : if (callit)
287 : : {
288 : : /* Okay, do it ... */
2415 andres@anarazel.de 289 : 1084 : retval = FunctionCallInvoke(fcinfo);
290 : : }
291 : : else
292 : : {
2415 andres@anarazel.de 293 :UBC 0 : fcinfo->isnull = true;
8156 tgl@sss.pgh.pa.us 294 : 0 : retval = (Datum) 0;
295 : : }
296 : :
297 : : /* ensure we do at least one CHECK_FOR_INTERRUPTS per function call */
7024 tgl@sss.pgh.pa.us 298 [ - + ]:CBC 1084 : CHECK_FOR_INTERRUPTS();
299 : :
2415 andres@anarazel.de 300 : 1084 : SendFunctionResult(retval, fcinfo->isnull, fip->rettype, rformat);
301 : :
302 : : /* We no longer need the snapshot */
6326 alvherre@alvh.no-ip. 303 : 1084 : PopActiveSnapshot();
304 : :
305 : : /*
306 : : * Emit duration logging if appropriate.
307 : : */
6938 tgl@sss.pgh.pa.us 308 [ - - + ]: 1084 : switch (check_log_duration(msec_str, was_logged))
309 : : {
6938 tgl@sss.pgh.pa.us 310 :UBC 0 : case 1:
311 [ # # ]: 0 : ereport(LOG,
312 : : (errmsg("duration: %s ms", msec_str)));
313 : 0 : break;
314 : 0 : case 2:
315 [ # # ]: 0 : ereport(LOG,
316 : : (errmsg("duration: %s ms fastpath function call: \"%s\" (OID %u)",
317 : : msec_str, fip->fname, fid)));
318 : 0 : break;
319 : : }
8157 tgl@sss.pgh.pa.us 320 :CBC 1084 : }
321 : :
322 : : /*
323 : : * Parse function arguments in a 3.0 protocol message
324 : : *
325 : : * Argument values are loaded into *fcinfo, and the desired result format
326 : : * is returned.
327 : : */
328 : : static int16
2999 329 : 1084 : parse_fcall_arguments(StringInfo msgBuf, struct fp_info *fip,
330 : : FunctionCallInfo fcinfo)
331 : : {
332 : : int nargs;
333 : : int i;
334 : : int numAFormats;
8157 335 : 1084 : int16 *aformats = NULL;
336 : : StringInfoData abuf;
337 : :
338 : : /* Get the argument format codes */
339 : 1084 : numAFormats = pq_getmsgint(msgBuf, 2);
340 [ + - ]: 1084 : if (numAFormats > 0)
341 : : {
342 : 1084 : aformats = (int16 *) palloc(numAFormats * sizeof(int16));
343 [ + + ]: 2168 : for (i = 0; i < numAFormats; i++)
344 : 1084 : aformats[i] = pq_getmsgint(msgBuf, 2);
345 : : }
346 : :
347 : 1084 : nargs = pq_getmsgint(msgBuf, 2); /* # of arguments */
348 : :
9232 349 [ + - - + ]: 1084 : if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
8082 tgl@sss.pgh.pa.us 350 [ # # ]:UBC 0 : ereport(ERROR,
351 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
352 : : errmsg("function call message contains %d arguments but function requires %d",
353 : : nargs, fip->flinfo.fn_nargs)));
354 : :
8157 tgl@sss.pgh.pa.us 355 :CBC 1084 : fcinfo->nargs = nargs;
356 : :
8156 357 [ - + - - ]: 1084 : if (numAFormats > 1 && numAFormats != nargs)
8082 tgl@sss.pgh.pa.us 358 [ # # ]:UBC 0 : ereport(ERROR,
359 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
360 : : errmsg("function call message contains %d argument formats but %d arguments",
361 : : numAFormats, nargs)));
362 : :
8156 tgl@sss.pgh.pa.us 363 :CBC 1084 : initStringInfo(&abuf);
364 : :
365 : : /*
366 : : * Copy supplied arguments into arg vector.
367 : : */
9232 368 [ + + ]: 3137 : for (i = 0; i < nargs; ++i)
369 : : {
370 : : int argsize;
371 : : int16 aformat;
372 : :
8176 373 : 2053 : argsize = pq_getmsgint(msgBuf, 4);
8156 374 [ - + ]: 2053 : if (argsize == -1)
375 : : {
2415 andres@anarazel.de 376 :UBC 0 : fcinfo->args[i].isnull = true;
377 : : }
378 : : else
379 : : {
2415 andres@anarazel.de 380 :CBC 2053 : fcinfo->args[i].isnull = false;
7095 tgl@sss.pgh.pa.us 381 [ - + ]: 2053 : if (argsize < 0)
7095 tgl@sss.pgh.pa.us 382 [ # # ]:UBC 0 : ereport(ERROR,
383 : : (errcode(ERRCODE_PROTOCOL_VIOLATION),
384 : : errmsg("invalid argument size %d in function call message",
385 : : argsize)));
386 : :
387 : : /* Reset abuf to empty, and insert raw data into it */
6762 neilc@samurai.com 388 :CBC 2053 : resetStringInfo(&abuf);
7095 tgl@sss.pgh.pa.us 389 : 2053 : appendBinaryStringInfo(&abuf,
390 : 2053 : pq_getmsgbytes(msgBuf, argsize),
391 : : argsize);
392 : : }
393 : :
8156 394 [ - + ]: 2053 : if (numAFormats > 1)
8156 tgl@sss.pgh.pa.us 395 :UBC 0 : aformat = aformats[i];
8156 tgl@sss.pgh.pa.us 396 [ + - ]:CBC 2053 : else if (numAFormats > 0)
397 : 2053 : aformat = aformats[0];
398 : : else
8156 tgl@sss.pgh.pa.us 399 :UBC 0 : aformat = 0; /* default = text */
400 : :
8156 tgl@sss.pgh.pa.us 401 [ - + ]:CBC 2053 : if (aformat == 0)
402 : : {
403 : : Oid typinput;
404 : : Oid typioparam;
405 : : char *pstring;
406 : :
7762 tgl@sss.pgh.pa.us 407 :UBC 0 : getTypeInputInfo(fip->argtypes[i], &typinput, &typioparam);
408 : :
409 : : /*
410 : : * Since stringinfo.c keeps a trailing null in place even for
411 : : * binary data, the contents of abuf are a valid C string. We
412 : : * have to do encoding conversion before calling the typinput
413 : : * routine, though.
414 : : */
7095 415 [ # # ]: 0 : if (argsize == -1)
416 : 0 : pstring = NULL;
417 : : else
418 : 0 : pstring = pg_client_to_server(abuf.data, argsize);
419 : :
2415 andres@anarazel.de 420 : 0 : fcinfo->args[i].value = OidInputFunctionCall(typinput, pstring,
421 : : typioparam, -1);
422 : : /* Free result of encoding conversion, if any */
7095 tgl@sss.pgh.pa.us 423 [ # # # # ]: 0 : if (pstring && pstring != abuf.data)
8156 424 : 0 : pfree(pstring);
425 : : }
8156 tgl@sss.pgh.pa.us 426 [ + - ]:CBC 2053 : else if (aformat == 1)
427 : : {
428 : : Oid typreceive;
429 : : Oid typioparam;
430 : : StringInfo bufptr;
431 : :
432 : : /* Call the argument type's binary input converter */
7762 433 : 2053 : getTypeBinaryInputInfo(fip->argtypes[i], &typreceive, &typioparam);
434 : :
7095 435 [ - + ]: 2053 : if (argsize == -1)
7095 tgl@sss.pgh.pa.us 436 :UBC 0 : bufptr = NULL;
437 : : else
7095 tgl@sss.pgh.pa.us 438 :CBC 2053 : bufptr = &abuf;
439 : :
2415 andres@anarazel.de 440 : 2053 : fcinfo->args[i].value = OidReceiveFunctionCall(typreceive, bufptr,
441 : : typioparam, -1);
442 : :
443 : : /* Trouble if it didn't eat the whole buffer */
7095 tgl@sss.pgh.pa.us 444 [ + - - + ]: 2053 : if (argsize != -1 && abuf.cursor != abuf.len)
8082 tgl@sss.pgh.pa.us 445 [ # # ]:UBC 0 : ereport(ERROR,
446 : : (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
447 : : errmsg("incorrect binary data format in function argument %d",
448 : : i + 1)));
449 : : }
450 : : else
451 [ # # ]: 0 : ereport(ERROR,
452 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
453 : : errmsg("unsupported format code: %d", aformat)));
454 : : }
455 : :
456 : : /* Return result format code */
8156 tgl@sss.pgh.pa.us 457 :CBC 1084 : return (int16) pq_getmsgint(msgBuf, 2);
458 : : }
|