1 /*-------------------------------------------------------------------------
4 * Server Programming Interface
6 *-------------------------------------------------------------------------
8 #include "executor/spi.h"
9 #include "catalog/pg_type.h"
10 #include "access/printtup.h"
15 QueryTreeList *qtlist; /* malloced */
16 uint32 processed; /* by Executor */
17 SPITupleTable *tuptable;
18 Portal portal; /* portal per procedure */
19 MemoryContext savedcxt;
23 static Portal _SPI_portal = (Portal) NULL;
24 static _SPI_connection *_SPI_stack = NULL;
25 static _SPI_connection *_SPI_current = NULL;
26 static int _SPI_connected = -1;
27 static int _SPI_curid = -1;
29 uint32 SPI_processed = 0;
30 SPITupleTable *SPI_tuptable;
33 void spi_printtup(HeapTuple tuple, TupleDesc tupdesc);
37 QueryTreeList *qtlist;
43 static int _SPI_execute(char *src, int tcount, _SPI_plan * plan);
44 static int _SPI_pquery(QueryDesc * queryDesc, EState * state, int tcount);
47 static void _SPI_fetch(FetchStmt * stmt);
51 _SPI_execute_plan(_SPI_plan * plan,
52 Datum * Values, char *Nulls, int tcount);
54 #define _SPI_CPLAN_CURCXT 0
55 #define _SPI_CPLAN_PROCXT 1
56 #define _SPI_CPLAN_TOPCXT 2
58 static _SPI_plan *_SPI_copy_plan(_SPI_plan * plan, int location);
60 static int _SPI_begin_call(bool execmem);
61 static int _SPI_end_call(bool procmem);
62 static MemoryContext _SPI_execmem(void);
63 static MemoryContext _SPI_procmem(void);
64 static bool _SPI_checktuples(void);
66 #ifdef SPI_EXECUTOR_STATS
67 extern int ShowExecutorStats;
68 extern void ResetUsage(void);
69 extern void ShowUsage(void);
73 /* =================== interface functions =================== */
79 PortalVariableMemory pvmem;
82 * It's possible on startup and after commit/abort. In future we'll
83 * catch commit/abort in some way...
85 strcpy(pname, "<SPI manager>");
86 _SPI_portal = GetPortalByName(pname);
87 if (!PortalIsValid(_SPI_portal))
89 if (_SPI_stack != NULL) /* there was abort */
91 _SPI_current = _SPI_stack = NULL;
92 _SPI_connected = _SPI_curid = -1;
95 _SPI_portal = CreatePortal(pname);
96 if (!PortalIsValid(_SPI_portal))
97 elog(FATAL, "SPI_connect: global initialization failed");
101 * When procedure called by Executor _SPI_curid expected to be equal
104 if (_SPI_curid != _SPI_connected)
105 return (SPI_ERROR_CONNECT);
107 if (_SPI_stack == NULL)
109 if (_SPI_connected != -1)
110 elog(FATAL, "SPI_connect: no connection(s) expected");
111 _SPI_stack = (_SPI_connection *) malloc(sizeof(_SPI_connection));
115 if (_SPI_connected <= -1)
116 elog(FATAL, "SPI_connect: some connection(s) expected");
117 _SPI_stack = (_SPI_connection *) realloc(_SPI_stack,
118 (_SPI_connected + 2) * sizeof(_SPI_connection));
122 * We' returning to procedure where _SPI_curid == _SPI_connected - 1
126 _SPI_current = &(_SPI_stack[_SPI_connected]);
127 _SPI_current->qtlist = NULL;
128 _SPI_current->processed = 0;
129 _SPI_current->tuptable = NULL;
131 /* Create Portal for this procedure ... */
132 sprintf(pname, "<SPI %d>", _SPI_connected);
133 _SPI_current->portal = CreatePortal(pname);
134 if (!PortalIsValid(_SPI_current->portal))
135 elog(FATAL, "SPI_connect: initialization failed");
137 /* ... and switch to Portal' Variable memory - procedure' context */
138 pvmem = PortalGetVariableMemory(_SPI_current->portal);
139 _SPI_current->savedcxt = MemoryContextSwitchTo((MemoryContext) pvmem);
141 _SPI_current->savedId = GetScanCommandId();
142 SetScanCommandId(GetCurrentCommandId());
144 return (SPI_OK_CONNECT);
153 res = _SPI_begin_call(false); /* live in procedure memory */
157 /* Restore memory context as it was before procedure call */
158 MemoryContextSwitchTo(_SPI_current->savedcxt);
159 PortalDestroy(&(_SPI_current->portal));
161 SetScanCommandId(_SPI_current->savedId);
164 * After _SPI_begin_call _SPI_connected == _SPI_curid. Now we are
165 * closing connection to SPI and returning to upper Executor and so
166 * _SPI_connected must be equal to _SPI_curid.
170 if (_SPI_connected == -1)
177 _SPI_stack = (_SPI_connection *) realloc(_SPI_stack,
178 (_SPI_connected + 1) * sizeof(_SPI_connection));
179 _SPI_current = &(_SPI_stack[_SPI_connected]);
182 return (SPI_OK_FINISH);
187 SPI_exec(char *src, int tcount)
191 if (src == NULL || tcount < 0)
192 return (SPI_ERROR_ARGUMENT);
194 res = _SPI_begin_call(true);
198 res = _SPI_execute(src, tcount, NULL);
205 SPI_execp(void *plan, Datum * Values, char *Nulls, int tcount)
209 if (plan == NULL || tcount < 0)
210 return (SPI_ERROR_ARGUMENT);
212 if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
213 return (SPI_ERROR_PARAM);
215 res = _SPI_begin_call(true);
219 /* copy plan to current (executor) context */
220 plan = (void *) _SPI_copy_plan(plan, _SPI_CPLAN_CURCXT);
222 res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, tcount);
229 SPI_prepare(char *src, int nargs, Oid * argtypes)
233 if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL))
235 SPI_result = SPI_ERROR_ARGUMENT;
239 SPI_result = _SPI_begin_call(true);
243 plan = (_SPI_plan *) palloc(sizeof(_SPI_plan)); /* Executor context */
244 plan->argtypes = argtypes;
247 SPI_result = _SPI_execute(src, 0, plan);
249 if (SPI_result >= 0) /* copy plan to procedure context */
250 plan = _SPI_copy_plan(plan, _SPI_CPLAN_PROCXT);
256 return ((void *) plan);
261 SPI_saveplan(void *plan)
267 SPI_result = SPI_ERROR_ARGUMENT;
271 SPI_result = _SPI_begin_call(false); /* don't change context */
275 newplan = _SPI_copy_plan((_SPI_plan *) plan, _SPI_CPLAN_TOPCXT);
280 return ((void *) newplan);
285 SPI_copytuple(HeapTuple tuple)
287 MemoryContext oldcxt = NULL;
292 SPI_result = SPI_ERROR_ARGUMENT;
296 if (_SPI_curid + 1 == _SPI_connected) /* connected */
298 if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
299 elog(FATAL, "SPI: stack corrupted");
300 oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
303 ctuple = heap_copytuple(tuple);
306 MemoryContextSwitchTo(oldcxt);
312 SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
313 Datum * Values, char *Nulls)
315 MemoryContext oldcxt = NULL;
317 int numberOfAttributes;
324 if (rel == NULL || tuple == NULL || natts <= 0 || attnum == NULL || Values == NULL)
326 SPI_result = SPI_ERROR_ARGUMENT;
330 if (_SPI_curid + 1 == _SPI_connected) /* connected */
332 if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
333 elog(FATAL, "SPI: stack corrupted");
334 oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
337 numberOfAttributes = rel->rd_att->natts;
338 v = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
339 n = (char *) palloc(numberOfAttributes * sizeof(char));
341 /* fetch old values and nulls */
342 for (i = 0; i < numberOfAttributes; i++)
344 v[i] = heap_getattr(tuple, i + 1, rel->rd_att, &isnull);
345 n[i] = (isnull) ? 'n' : ' ';
348 /* replace values and nulls */
349 for (i = 0; i < natts; i++)
351 if (attnum[i] <= 0 || attnum[i] > numberOfAttributes)
353 v[attnum[i] - 1] = Values[i];
354 n[attnum[i] - 1] = (Nulls && Nulls[i] == 'n') ? 'n' : ' ';
357 if (i == natts) /* no errors in attnum[] */
359 mtuple = heap_formtuple(rel->rd_att, v, n);
360 infomask = mtuple->t_infomask;
361 memmove(&(mtuple->t_ctid), &(tuple->t_ctid),
362 ((char *) &(tuple->t_hoff) - (char *) &(tuple->t_ctid)));
363 mtuple->t_infomask = infomask;
364 mtuple->t_natts = numberOfAttributes;
369 SPI_result = SPI_ERROR_NOATTRIBUTE;
376 MemoryContextSwitchTo(oldcxt);
382 SPI_fnumber(TupleDesc tupdesc, char *fname)
386 for (res = 0; res < tupdesc->natts; res++)
388 if (strcasecmp(tupdesc->attrs[res]->attname.data, fname) == 0)
392 return (SPI_ERROR_NOATTRIBUTE);
396 SPI_fname(TupleDesc tupdesc, int fnumber)
400 if (tupdesc->natts < fnumber || fnumber <= 0)
402 SPI_result = SPI_ERROR_NOATTRIBUTE;
406 return (nameout(&(tupdesc->attrs[fnumber - 1]->attname)));
410 SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
417 if (tuple->t_natts < fnumber || fnumber <= 0)
419 SPI_result = SPI_ERROR_NOATTRIBUTE;
423 val = heap_getattr(tuple, fnumber, tupdesc, &isnull);
426 foutoid = typtoout((Oid) tupdesc->attrs[fnumber - 1]->atttypid);
427 if (!OidIsValid(foutoid))
429 SPI_result = SPI_ERROR_NOOUTFUNC;
433 return (fmgr(foutoid, val,
434 gettypelem(tupdesc->attrs[fnumber - 1]->atttypid),
435 tupdesc->attrs[fnumber - 1]->atttypmod));
439 SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool * isnull)
445 if (tuple->t_natts < fnumber || fnumber <= 0)
447 SPI_result = SPI_ERROR_NOATTRIBUTE;
448 return ((Datum) NULL);
451 val = heap_getattr(tuple, fnumber, tupdesc, isnull);
457 SPI_gettype(TupleDesc tupdesc, int fnumber)
462 if (tupdesc->natts < fnumber || fnumber <= 0)
464 SPI_result = SPI_ERROR_NOATTRIBUTE;
468 typeTuple = SearchSysCacheTuple(TYPOID,
469 ObjectIdGetDatum(tupdesc->attrs[fnumber - 1]->atttypid),
472 if (!HeapTupleIsValid(typeTuple))
474 SPI_result = SPI_ERROR_TYPUNKNOWN;
478 return (pstrdup(((TypeTupleForm) GETSTRUCT(typeTuple))->typname.data));
482 SPI_gettypeid(TupleDesc tupdesc, int fnumber)
486 if (tupdesc->natts < fnumber || fnumber <= 0)
488 SPI_result = SPI_ERROR_NOATTRIBUTE;
492 return (tupdesc->attrs[fnumber - 1]->atttypid);
496 SPI_getrelname(Relation rel)
498 return (pstrdup(rel->rd_rel->relname.data));
502 SPI_palloc (Size size)
504 MemoryContext oldcxt = NULL;
507 if (_SPI_curid + 1 == _SPI_connected) /* connected */
509 if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
510 elog(FATAL, "SPI: stack corrupted");
511 oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
514 pointer = palloc (size);
517 MemoryContextSwitchTo(oldcxt);
523 SPI_repalloc (void *pointer, Size size)
525 MemoryContext oldcxt = NULL;
527 if (_SPI_curid + 1 == _SPI_connected) /* connected */
529 if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
530 elog(FATAL, "SPI: stack corrupted");
531 oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
534 pointer = repalloc (pointer, size);
537 MemoryContextSwitchTo(oldcxt);
543 SPI_pfree (void *pointer)
545 MemoryContext oldcxt = NULL;
547 if (_SPI_curid + 1 == _SPI_connected) /* connected */
549 if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
550 elog(FATAL, "SPI: stack corrupted");
551 oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
557 MemoryContextSwitchTo(oldcxt);
562 /* =================== private functions =================== */
566 * store tuple retrieved by Executor into SPITupleTable
567 * of current SPI procedure
571 spi_printtup(HeapTuple tuple, TupleDesc tupdesc)
573 SPITupleTable *tuptable;
574 MemoryContext oldcxt;
577 * When called by Executor _SPI_curid expected to be equal to
580 if (_SPI_curid != _SPI_connected || _SPI_connected < 0)
581 elog(FATAL, "SPI: improper call to spi_printtup");
582 if (_SPI_current != &(_SPI_stack[_SPI_curid]))
583 elog(FATAL, "SPI: stack corrupted in spi_printtup");
585 oldcxt = _SPI_procmem(); /* switch to procedure memory context */
587 tuptable = _SPI_current->tuptable;
588 if (tuptable == NULL)
590 _SPI_current->tuptable = tuptable = (SPITupleTable *)
591 palloc(sizeof(SPITupleTable));
592 tuptable->alloced = tuptable->free = 128;
593 tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
594 tuptable->tupdesc = CreateTupleDescCopy(tupdesc);
596 else if (tuptable->free == 0)
598 tuptable->free = 256;
599 tuptable->alloced += tuptable->free;
600 tuptable->vals = (HeapTuple *) repalloc(tuptable->vals,
601 tuptable->alloced * sizeof(HeapTuple));
604 tuptable->vals[tuptable->alloced - tuptable->free] = heap_copytuple(tuple);
607 MemoryContextSwitchTo(oldcxt);
616 _SPI_execute(char *src, int tcount, _SPI_plan * plan)
618 QueryTreeList *queryTree_list;
627 Oid *argtypes = NULL;
631 /* Increment CommandCounter to see changes made by now */
632 CommandCounterIncrement();
636 _SPI_current->tuptable = NULL;
637 _SPI_current->qtlist = NULL;
642 argtypes = plan->argtypes;
644 ptlist = planTree_list = (List *)
645 pg_parse_and_plan(src, argtypes, nargs, &queryTree_list, None);
647 _SPI_current->qtlist = queryTree_list;
649 qlen = queryTree_list->len;
652 queryTree = (Query *) (queryTree_list->qtrees[i]);
653 planTree = lfirst(planTree_list);
655 planTree_list = lnext(planTree_list);
657 if (queryTree->commandType == CMD_UTILITY)
659 if (nodeTag(queryTree->utilityStmt) == T_CopyStmt)
661 CopyStmt *stmt = (CopyStmt *) (queryTree->utilityStmt);
663 if (stmt->filename == NULL)
664 return (SPI_ERROR_COPY);
666 else if (nodeTag(queryTree->utilityStmt) == T_ClosePortalStmt ||
667 nodeTag(queryTree->utilityStmt) == T_FetchStmt)
668 return (SPI_ERROR_CURSOR);
669 else if (nodeTag(queryTree->utilityStmt) == T_TransactionStmt)
670 return (SPI_ERROR_TRANSACTION);
671 res = SPI_OK_UTILITY;
674 ProcessUtility(queryTree->utilityStmt, None);
676 CommandCounterIncrement();
680 else if (i >= qlen - 1)
683 else if (plan == NULL)
685 qdesc = CreateQueryDesc(queryTree, planTree,
686 (i < qlen - 1) ? None : SPI);
687 state = CreateExecutorState();
688 res = _SPI_pquery(qdesc, state, (i < qlen - 1) ? 0 : tcount);
689 if (res < 0 || i >= qlen - 1)
691 CommandCounterIncrement();
695 qdesc = CreateQueryDesc(queryTree, planTree,
696 (i < qlen - 1) ? None : SPI);
697 res = _SPI_pquery(qdesc, NULL, (i < qlen - 1) ? 0 : tcount);
705 plan->qtlist = queryTree_list;
706 plan->ptlist = ptlist;
713 _SPI_execute_plan(_SPI_plan * plan, Datum * Values, char *Nulls, int tcount)
715 QueryTreeList *queryTree_list = plan->qtlist;
716 List *planTree_list = plan->ptlist;
721 int nargs = plan->nargs;
722 int qlen = queryTree_list->len;
727 /* Increment CommandCounter to see changes made by now */
728 CommandCounterIncrement();
732 _SPI_current->tuptable = NULL;
733 _SPI_current->qtlist = NULL;
737 queryTree = (Query *) (queryTree_list->qtrees[i]);
738 planTree = lfirst(planTree_list);
740 planTree_list = lnext(planTree_list);
742 if (queryTree->commandType == CMD_UTILITY)
744 ProcessUtility(queryTree->utilityStmt, None);
746 CommandCounterIncrement();
748 return (SPI_OK_UTILITY);
752 qdesc = CreateQueryDesc(queryTree, planTree,
753 (i < qlen - 1) ? None : SPI);
754 state = CreateExecutorState();
757 ParamListInfo paramLI = (ParamListInfo) palloc((nargs + 1) *
758 sizeof(ParamListInfoData));
760 state->es_param_list_info = paramLI;
761 for (k = 0; k < plan->nargs; paramLI++, k++)
763 paramLI->kind = PARAM_NUM;
765 paramLI->isnull = (Nulls && Nulls[k] == 'n');
766 paramLI->value = Values[k];
768 paramLI->kind = PARAM_INVALID;
771 state->es_param_list_info = NULL;
772 res = _SPI_pquery(qdesc, state, (i < qlen - 1) ? 0 : tcount);
773 if (res < 0 || i >= qlen - 1)
775 CommandCounterIncrement();
784 _SPI_pquery(QueryDesc * queryDesc, EState * state, int tcount)
786 Query *parseTree = queryDesc->parsetree;
787 Plan *plan = queryDesc->plantree;
788 int operation = queryDesc->operation;
789 CommandDest dest = queryDesc->dest;
791 bool isRetrieveIntoPortal = false;
792 bool isRetrieveIntoRelation = false;
793 char *intoName = NULL;
800 if (parseTree->isPortal)
802 isRetrieveIntoPortal = true;
803 intoName = parseTree->into;
804 parseTree->isBinary = false; /* */
806 return (SPI_ERROR_CURSOR);
809 else if (parseTree->into != NULL) /* select into table */
811 res = SPI_OK_SELINTO;
812 isRetrieveIntoRelation = true;
813 queryDesc->dest = None; /* */
826 return (SPI_ERROR_OPUNKNOWN);
829 if (state == NULL) /* plan preparation */
831 #ifdef SPI_EXECUTOR_STATS
832 if (ShowExecutorStats)
835 tupdesc = ExecutorStart(queryDesc, state);
837 /* Don't work currently */
838 if (isRetrieveIntoPortal)
840 ProcessPortal(intoName,
846 return (SPI_OK_CURSOR);
849 ExecutorRun(queryDesc, state, EXEC_FOR, tcount);
851 _SPI_current->processed = state->es_processed;
852 if (operation == CMD_SELECT && queryDesc->dest == SPI)
854 if (_SPI_checktuples())
855 elog(FATAL, "SPI_select: # of processed tuples check failed");
858 ExecutorEnd(queryDesc, state);
860 #ifdef SPI_EXECUTOR_STATS
861 if (ShowExecutorStats)
863 fprintf(stderr, "! Executor Stats:\n");
870 SPI_processed = _SPI_current->processed;
871 SPI_tuptable = _SPI_current->tuptable;
873 queryDesc->dest = dest;
881 _SPI_fetch(FetchStmt * stmt)
883 char *name = stmt->portalname;
884 int feature = (stmt->direction == FORWARD) ? EXEC_FOR : EXEC_BACK;
885 int count = stmt->howMany;
887 QueryDesc *queryDesc;
889 MemoryContext context;
892 elog(FATAL, "SPI_fetch from blank portal unsupported");
894 portal = GetPortalByName(name);
895 if (!PortalIsValid(portal))
896 elog(FATAL, "SPI_fetch: portal \"%s\" not found", name);
898 context = MemoryContextSwitchTo((MemoryContext) PortalGetHeapMemory(portal));
900 queryDesc = PortalGetQueryDesc(portal);
901 state = PortalGetState(portal);
903 ExecutorRun(queryDesc, state, feature, count);
905 MemoryContextSwitchTo(context); /* switch to the normal Executor
908 _SPI_current->processed = state->es_processed;
909 if (_SPI_checktuples())
910 elog(FATAL, "SPI_fetch: # of processed tuples check failed");
912 SPI_processed = _SPI_current->processed;
913 SPI_tuptable = _SPI_current->tuptable;
922 MemoryContext oldcxt;
923 PortalHeapMemory phmem;
925 phmem = PortalGetHeapMemory(_SPI_current->portal);
926 oldcxt = MemoryContextSwitchTo((MemoryContext) phmem);
935 MemoryContext oldcxt;
936 PortalVariableMemory pvmem;
938 pvmem = PortalGetVariableMemory(_SPI_current->portal);
939 oldcxt = MemoryContextSwitchTo((MemoryContext) pvmem);
950 _SPI_begin_call(bool execmem)
952 if (_SPI_curid + 1 != _SPI_connected)
953 return (SPI_ERROR_UNCONNECTED);
955 if (_SPI_current != &(_SPI_stack[_SPI_curid]))
956 elog(FATAL, "SPI: stack corrupted");
958 if (execmem) /* switch to the Executor memory context */
961 StartPortalAllocMode(DefaultAllocMode, 0);
968 _SPI_end_call(bool procmem)
972 * We' returning to procedure where _SPI_curid == _SPI_connected - 1
976 if (_SPI_current->qtlist) /* free _SPI_plan allocations */
978 free(_SPI_current->qtlist->qtrees);
979 free(_SPI_current->qtlist);
980 _SPI_current->qtlist = NULL;
983 if (procmem) /* switch to the procedure memory context */
984 { /* but free Executor memory before */
985 EndPortalAllocMode();
995 uint32 processed = _SPI_current->processed;
996 SPITupleTable *tuptable = _SPI_current->tuptable;
1001 if (tuptable != NULL)
1004 else /* some tuples were processed */
1006 if (tuptable == NULL) /* spi_printtup was not called */
1008 else if (processed != (tuptable->alloced - tuptable->free))
1016 _SPI_copy_plan(_SPI_plan * plan, int location)
1019 MemoryContext oldcxt = NULL;
1022 if (location == _SPI_CPLAN_PROCXT)
1023 oldcxt = MemoryContextSwitchTo((MemoryContext)
1024 PortalGetVariableMemory(_SPI_current->portal));
1025 else if (location == _SPI_CPLAN_TOPCXT)
1026 oldcxt = MemoryContextSwitchTo(TopMemoryContext);
1028 newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
1029 newplan->qtlist = (QueryTreeList *) palloc(sizeof(QueryTreeList));
1030 newplan->qtlist->len = plan->qtlist->len;
1031 newplan->qtlist->qtrees = (Query **) palloc(plan->qtlist->len *
1033 for (i = 0; i < plan->qtlist->len; i++)
1034 newplan->qtlist->qtrees[i] = (Query *)
1035 copyObject(plan->qtlist->qtrees[i]);
1037 newplan->ptlist = (List *) copyObject(plan->ptlist);
1038 newplan->nargs = plan->nargs;
1039 if (plan->nargs > 0)
1041 newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
1042 memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
1045 newplan->argtypes = NULL;
1047 if (location != _SPI_CPLAN_CURCXT)
1048 MemoryContextSwitchTo(oldcxt);