Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeTableFuncscan.c
4 : : * Support routines for scanning RangeTableFunc (XMLTABLE like functions).
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/executor/nodeTableFuncscan.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : /*
16 : : * INTERFACE ROUTINES
17 : : * ExecTableFuncScan scans a function.
18 : : * ExecFunctionNext retrieve next tuple in sequential order.
19 : : * ExecInitTableFuncScan creates and initializes a TableFuncscan node.
20 : : * ExecEndTableFuncScan releases any storage allocated.
21 : : * ExecReScanTableFuncScan rescans the function
22 : : */
23 : : #include "postgres.h"
24 : :
25 : : #include "executor/executor.h"
26 : : #include "executor/nodeTableFuncscan.h"
27 : : #include "executor/tablefunc.h"
28 : : #include "miscadmin.h"
29 : : #include "nodes/execnodes.h"
30 : : #include "utils/builtins.h"
31 : : #include "utils/jsonpath.h"
32 : : #include "utils/lsyscache.h"
33 : : #include "utils/memutils.h"
34 : : #include "utils/tuplestore.h"
35 : : #include "utils/xml.h"
36 : :
37 : : static TupleTableSlot *TableFuncNext(TableFuncScanState *node);
38 : : static bool TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot);
39 : :
40 : : static void tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext);
41 : : static void tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc);
42 : : static void tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext);
43 : :
44 : : /* ----------------------------------------------------------------
45 : : * Scan Support
46 : : * ----------------------------------------------------------------
47 : : */
48 : : /* ----------------------------------------------------------------
49 : : * TableFuncNext
50 : : *
51 : : * This is a workhorse for ExecTableFuncScan
52 : : * ----------------------------------------------------------------
53 : : */
54 : : static TupleTableSlot *
3345 alvherre@alvh.no-ip. 55 :CBC 16545 : TableFuncNext(TableFuncScanState *node)
56 : : {
57 : : TupleTableSlot *scanslot;
58 : :
59 : 16545 : scanslot = node->ss.ss_ScanTupleSlot;
60 : :
61 : : /*
62 : : * If first time through, read all tuples from function and put them in a
63 : : * tuplestore. Subsequent calls just fetch tuples from tuplestore.
64 : : */
65 [ + + ]: 16545 : if (node->tupstore == NULL)
66 : 526 : tfuncFetchRows(node, node->ss.ps.ps_ExprContext);
67 : :
68 : : /*
69 : : * Get the next tuple from tuplestore.
70 : : */
71 : 16469 : (void) tuplestore_gettupleslot(node->tupstore,
72 : : true,
73 : : false,
74 : : scanslot);
75 : 16469 : return scanslot;
76 : : }
77 : :
78 : : /*
79 : : * TableFuncRecheck -- access method routine to recheck a tuple in EvalPlanQual
80 : : */
81 : : static bool
3345 alvherre@alvh.no-ip. 82 :UBC 0 : TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot)
83 : : {
84 : : /* nothing to check */
85 : 0 : return true;
86 : : }
87 : :
88 : : /* ----------------------------------------------------------------
89 : : * ExecTableFuncScan(node)
90 : : *
91 : : * Scans the function sequentially and returns the next qualifying
92 : : * tuple.
93 : : * We call the ExecScan() routine and pass it the appropriate
94 : : * access method functions.
95 : : * ----------------------------------------------------------------
96 : : */
97 : : static TupleTableSlot *
3214 andres@anarazel.de 98 :CBC 16509 : ExecTableFuncScan(PlanState *pstate)
99 : : {
100 : 16509 : TableFuncScanState *node = castNode(TableFuncScanState, pstate);
101 : :
3345 alvherre@alvh.no-ip. 102 : 16509 : return ExecScan(&node->ss,
103 : : (ExecScanAccessMtd) TableFuncNext,
104 : : (ExecScanRecheckMtd) TableFuncRecheck);
105 : : }
106 : :
107 : : /* ----------------------------------------------------------------
108 : : * ExecInitTableFuncScan
109 : : * ----------------------------------------------------------------
110 : : */
111 : : TableFuncScanState *
112 : 414 : ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
113 : : {
114 : : TableFuncScanState *scanstate;
115 : 414 : TableFunc *tf = node->tablefunc;
116 : : TupleDesc tupdesc;
117 : : int i;
118 : :
119 : : /* check for unsupported flags */
120 [ - + ]: 414 : Assert(!(eflags & EXEC_FLAG_MARK));
121 : :
122 : : /*
123 : : * TableFuncscan should not have any children.
124 : : */
125 [ - + ]: 414 : Assert(outerPlan(node) == NULL);
126 [ - + ]: 414 : Assert(innerPlan(node) == NULL);
127 : :
128 : : /*
129 : : * create new ScanState for node
130 : : */
131 : 414 : scanstate = makeNode(TableFuncScanState);
132 : 414 : scanstate->ss.ps.plan = (Plan *) node;
133 : 414 : scanstate->ss.ps.state = estate;
3214 andres@anarazel.de 134 : 414 : scanstate->ss.ps.ExecProcNode = ExecTableFuncScan;
135 : :
136 : : /*
137 : : * Miscellaneous initialization
138 : : *
139 : : * create expression context for node
140 : : */
3345 alvherre@alvh.no-ip. 141 : 414 : ExecAssignExprContext(estate, &scanstate->ss.ps);
142 : :
143 : : /*
144 : : * initialize source tuple type
145 : : */
146 : 414 : tupdesc = BuildDescFromLists(tf->colnames,
147 : 414 : tf->coltypes,
148 : 414 : tf->coltypmods,
149 : 414 : tf->colcollations);
150 : : /* and the corresponding scan slot */
2728 andres@anarazel.de 151 : 414 : ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc,
152 : : &TTSOpsMinimalTuple, 0);
153 : :
154 : : /*
155 : : * Initialize result type and projection.
156 : : */
2734 157 : 414 : ExecInitResultTypeTL(&scanstate->ss.ps);
3345 alvherre@alvh.no-ip. 158 : 414 : ExecAssignScanProjectionInfo(&scanstate->ss);
159 : :
160 : : /*
161 : : * initialize child expressions
162 : : */
3000 andres@anarazel.de 163 : 414 : scanstate->ss.ps.qual =
164 : 414 : ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
165 : :
166 : : /* Only XMLTABLE and JSON_TABLE are supported currently */
761 amitlan@postgresql.o 167 : 414 : scanstate->routine =
168 [ + + ]: 414 : tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine;
169 : :
2822 rhodiumtoad@postgres 170 : 414 : scanstate->perTableCxt =
3345 alvherre@alvh.no-ip. 171 : 414 : AllocSetContextCreate(CurrentMemoryContext,
172 : : "TableFunc per value context",
173 : : ALLOCSET_DEFAULT_SIZES);
174 : 414 : scanstate->opaque = NULL; /* initialized at runtime */
175 : :
176 : 414 : scanstate->ns_names = tf->ns_names;
177 : :
3339 andres@anarazel.de 178 : 414 : scanstate->ns_uris =
179 : 414 : ExecInitExprList(tf->ns_uris, (PlanState *) scanstate);
3345 alvherre@alvh.no-ip. 180 : 414 : scanstate->docexpr =
181 : 414 : ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate);
182 : 414 : scanstate->rowexpr =
183 : 414 : ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate);
3339 andres@anarazel.de 184 : 414 : scanstate->colexprs =
185 : 414 : ExecInitExprList(tf->colexprs, (PlanState *) scanstate);
186 : 414 : scanstate->coldefexprs =
187 : 414 : ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate);
761 amitlan@postgresql.o 188 : 414 : scanstate->colvalexprs =
189 : 414 : ExecInitExprList(tf->colvalexprs, (PlanState *) scanstate);
190 : 414 : scanstate->passingvalexprs =
191 : 414 : ExecInitExprList(tf->passingvalexprs, (PlanState *) scanstate);
192 : :
3345 alvherre@alvh.no-ip. 193 : 414 : scanstate->notnulls = tf->notnulls;
194 : :
195 : : /* these are allocated now and initialized later */
146 michael@paquier.xyz 196 :GNC 414 : scanstate->in_functions = palloc_array(FmgrInfo, tupdesc->natts);
197 : 414 : scanstate->typioparams = palloc_array(Oid, tupdesc->natts);
198 : :
199 : : /*
200 : : * Fill in the necessary fmgr infos.
201 : : */
3345 alvherre@alvh.no-ip. 202 [ + + ]:CBC 1520 : for (i = 0; i < tupdesc->natts; i++)
203 : : {
204 : : Oid in_funcid;
205 : :
3180 andres@anarazel.de 206 : 1106 : getTypeInputInfo(TupleDescAttr(tupdesc, i)->atttypid,
3345 alvherre@alvh.no-ip. 207 : 1106 : &in_funcid, &scanstate->typioparams[i]);
208 : 1106 : fmgr_info(in_funcid, &scanstate->in_functions[i]);
209 : : }
210 : :
211 : 414 : return scanstate;
212 : : }
213 : :
214 : : /* ----------------------------------------------------------------
215 : : * ExecEndTableFuncScan
216 : : *
217 : : * frees any storage allocated through C routines.
218 : : * ----------------------------------------------------------------
219 : : */
220 : : void
221 : 338 : ExecEndTableFuncScan(TableFuncScanState *node)
222 : : {
223 : : /*
224 : : * Release tuplestore resources
225 : : */
226 [ + + ]: 338 : if (node->tupstore != NULL)
227 : 286 : tuplestore_end(node->tupstore);
228 : 338 : node->tupstore = NULL;
229 : 338 : }
230 : :
231 : : /* ----------------------------------------------------------------
232 : : * ExecReScanTableFuncScan
233 : : *
234 : : * Rescans the relation.
235 : : * ----------------------------------------------------------------
236 : : */
237 : : void
238 : 296 : ExecReScanTableFuncScan(TableFuncScanState *node)
239 : : {
240 : 296 : Bitmapset *chgparam = node->ss.ps.chgParam;
241 : :
2734 andres@anarazel.de 242 [ - + ]: 296 : if (node->ss.ps.ps_ResultTupleSlot)
2734 andres@anarazel.de 243 :UBC 0 : ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
3345 alvherre@alvh.no-ip. 244 :CBC 296 : ExecScanReScan(&node->ss);
245 : :
246 : : /*
247 : : * Recompute when parameters are changed.
248 : : */
249 [ + - ]: 296 : if (chgparam)
250 : : {
251 [ + + ]: 296 : if (node->tupstore != NULL)
252 : : {
253 : 164 : tuplestore_end(node->tupstore);
254 : 164 : node->tupstore = NULL;
255 : : }
256 : : }
257 : :
258 [ - + ]: 296 : if (node->tupstore != NULL)
3345 alvherre@alvh.no-ip. 259 :UBC 0 : tuplestore_rescan(node->tupstore);
3345 alvherre@alvh.no-ip. 260 :CBC 296 : }
261 : :
262 : : /* ----------------------------------------------------------------
263 : : * tfuncFetchRows
264 : : *
265 : : * Read rows from a TableFunc producer
266 : : * ----------------------------------------------------------------
267 : : */
268 : : static void
269 : 526 : tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext)
270 : : {
271 : 526 : const TableFuncRoutine *routine = tstate->routine;
272 : : MemoryContext oldcxt;
273 : : Datum value;
274 : : bool isnull;
275 : :
276 [ - + ]: 526 : Assert(tstate->opaque == NULL);
277 : :
278 : : /* build tuplestore for the result */
279 : 526 : oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
280 : 526 : tstate->tupstore = tuplestore_begin_heap(false, false, work_mem);
281 : :
282 : : /*
283 : : * Each call to fetch a new set of rows - of which there may be very many
284 : : * if XMLTABLE or JSON_TABLE is being used in a lateral join - will
285 : : * allocate a possibly substantial amount of memory, so we cannot use the
286 : : * per-query context here. perTableCxt now serves the same function as
287 : : * "argcontext" does in FunctionScan - a place to store per-one-call (i.e.
288 : : * one result table) lifetime data (as opposed to per-query or
289 : : * per-result-tuple).
290 : : */
2822 rhodiumtoad@postgres 291 : 526 : MemoryContextSwitchTo(tstate->perTableCxt);
292 : :
3345 alvherre@alvh.no-ip. 293 [ + + ]: 526 : PG_TRY();
294 : : {
295 : 526 : routine->InitOpaque(tstate,
3240 tgl@sss.pgh.pa.us 296 : 526 : tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->natts);
297 : :
298 : : /*
299 : : * If evaluating the document expression returns NULL, the table
300 : : * expression is empty and we return immediately.
301 : : */
3345 alvherre@alvh.no-ip. 302 : 526 : value = ExecEvalExpr(tstate->docexpr, econtext, &isnull);
303 : :
304 [ + + ]: 526 : if (!isnull)
305 : : {
306 : : /* otherwise, pass the document value to the table builder */
307 : 522 : tfuncInitialize(tstate, econtext, value);
308 : :
309 : : /* initialize ordinality counter */
310 : 514 : tstate->ordinal = 1;
311 : :
312 : : /* Load all rows into the tuplestore, and we're done */
313 : 514 : tfuncLoadRows(tstate, econtext);
314 : : }
315 : : }
316 : 76 : PG_CATCH();
317 : : {
318 [ + - ]: 76 : if (tstate->opaque != NULL)
319 : 76 : routine->DestroyOpaque(tstate);
320 : 76 : PG_RE_THROW();
321 : : }
322 [ - + ]: 450 : PG_END_TRY();
323 : :
324 : : /* clean up and return to original memory context */
325 : :
326 [ + - ]: 450 : if (tstate->opaque != NULL)
327 : : {
328 : 450 : routine->DestroyOpaque(tstate);
329 : 450 : tstate->opaque = NULL;
330 : : }
331 : :
2822 rhodiumtoad@postgres 332 : 450 : MemoryContextSwitchTo(oldcxt);
333 : 450 : MemoryContextReset(tstate->perTableCxt);
3345 alvherre@alvh.no-ip. 334 : 450 : }
335 : :
336 : : /*
337 : : * Fill in namespace declarations, the row filter, and column filters in a
338 : : * table expression builder context.
339 : : */
340 : : static void
341 : 522 : tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
342 : : {
343 : 522 : const TableFuncRoutine *routine = tstate->routine;
344 : : TupleDesc tupdesc;
345 : : ListCell *lc1,
346 : : *lc2;
347 : : bool isnull;
348 : : int colno;
349 : : Datum value;
350 : 522 : int ordinalitycol =
1082 tgl@sss.pgh.pa.us 351 : 522 : ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol;
352 : :
353 : : /*
354 : : * Install the document as a possibly-toasted Datum into the tablefunc
355 : : * context.
356 : : */
3345 alvherre@alvh.no-ip. 357 : 522 : routine->SetDocument(tstate, doc);
358 : :
359 : : /* Evaluate namespace specifications */
360 [ + + + + : 526 : forboth(lc1, tstate->ns_uris, lc2, tstate->ns_names)
+ + + + +
+ + - +
+ ]
361 : : {
362 : 12 : ExprState *expr = (ExprState *) lfirst(lc1);
1699 peter@eisentraut.org 363 : 12 : String *ns_node = lfirst_node(String, lc2);
364 : : char *ns_uri;
365 : : char *ns_name;
366 : :
154 peter@eisentraut.org 367 :GNC 12 : value = ExecEvalExpr(expr, econtext, &isnull);
3345 alvherre@alvh.no-ip. 368 [ - + ]:CBC 12 : if (isnull)
3345 alvherre@alvh.no-ip. 369 [ # # ]:UBC 0 : ereport(ERROR,
370 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
371 : : errmsg("namespace URI must not be null")));
3345 alvherre@alvh.no-ip. 372 :CBC 12 : ns_uri = TextDatumGetCString(value);
373 : :
374 : : /* DEFAULT is passed down to SetNamespace as NULL */
2787 tgl@sss.pgh.pa.us 375 [ + + ]: 12 : ns_name = ns_node ? strVal(ns_node) : NULL;
376 : :
3345 alvherre@alvh.no-ip. 377 : 12 : routine->SetNamespace(tstate, ns_name, ns_uri);
378 : : }
379 : :
380 : : /*
381 : : * Install the row filter expression, if any, into the table builder
382 : : * context.
383 : : */
761 amitlan@postgresql.o 384 [ + + ]: 514 : if (routine->SetRowFilter)
385 : : {
386 : 172 : value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull);
387 [ - + ]: 172 : if (isnull)
761 amitlan@postgresql.o 388 [ # # ]:UBC 0 : ereport(ERROR,
389 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
390 : : errmsg("row filter expression must not be null")));
391 : :
761 amitlan@postgresql.o 392 :CBC 172 : routine->SetRowFilter(tstate, TextDatumGetCString(value));
393 : : }
394 : :
395 : : /*
396 : : * Install the column filter expressions into the table builder context.
397 : : * If an expression is given, use that; otherwise the column name itself
398 : : * is the column filter.
399 : : */
3345 alvherre@alvh.no-ip. 400 : 514 : colno = 0;
401 : 514 : tupdesc = tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
402 [ + + + + : 1078 : foreach(lc1, tstate->colexprs)
+ + ]
403 : : {
404 : : char *colfilter;
3180 andres@anarazel.de 405 : 564 : Form_pg_attribute att = TupleDescAttr(tupdesc, colno);
406 : :
3345 alvherre@alvh.no-ip. 407 [ + + ]: 564 : if (colno != ordinalitycol)
408 : : {
409 : 516 : ExprState *colexpr = lfirst(lc1);
410 : :
411 [ + + ]: 516 : if (colexpr != NULL)
412 : : {
413 : 404 : value = ExecEvalExpr(colexpr, econtext, &isnull);
414 [ - + ]: 404 : if (isnull)
3345 alvherre@alvh.no-ip. 415 [ # # ]:UBC 0 : ereport(ERROR,
416 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
417 : : errmsg("column filter expression must not be null"),
418 : : errdetail("Filter for column \"%s\" is null.",
419 : : NameStr(att->attname))));
3345 alvherre@alvh.no-ip. 420 :CBC 404 : colfilter = TextDatumGetCString(value);
421 : : }
422 : : else
3180 andres@anarazel.de 423 : 112 : colfilter = NameStr(att->attname);
424 : :
3345 alvherre@alvh.no-ip. 425 : 516 : routine->SetColumnFilter(tstate, colfilter, colno);
426 : : }
427 : :
428 : 564 : colno++;
429 : : }
430 : 514 : }
431 : :
432 : : /*
433 : : * Load all the rows from the TableFunc table builder into a tuplestore.
434 : : */
435 : : static void
436 : 514 : tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext)
437 : : {
438 : 514 : const TableFuncRoutine *routine = tstate->routine;
439 : 514 : TupleTableSlot *slot = tstate->ss.ss_ScanTupleSlot;
440 : 514 : TupleDesc tupdesc = slot->tts_tupleDescriptor;
441 : 514 : Datum *values = slot->tts_values;
442 : 514 : bool *nulls = slot->tts_isnull;
443 : 514 : int natts = tupdesc->natts;
444 : : MemoryContext oldcxt;
445 : : int ordinalitycol;
446 : :
447 : 514 : ordinalitycol =
448 : 514 : ((TableFuncScan *) (tstate->ss.ps.plan))->tablefunc->ordinalitycol;
449 : :
450 : : /*
451 : : * We need a short-lived memory context that we can clean up each time
452 : : * around the loop, to avoid wasting space. Our default per-tuple context
453 : : * is fine for the job, since we won't have used it for anything yet in
454 : : * this tuple cycle.
455 : : */
2822 rhodiumtoad@postgres 456 : 514 : oldcxt = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
457 : :
458 : : /*
459 : : * Keep requesting rows from the table builder until there aren't any.
460 : : */
3345 alvherre@alvh.no-ip. 461 [ + + ]: 16533 : while (routine->FetchRow(tstate))
462 : : {
463 : 16087 : ListCell *cell = list_head(tstate->coldefexprs);
464 : : int colno;
465 : :
3206 andres@anarazel.de 466 [ - + ]: 16087 : CHECK_FOR_INTERRUPTS();
467 : :
3345 alvherre@alvh.no-ip. 468 : 16087 : ExecClearTuple(tstate->ss.ss_ScanTupleSlot);
469 : :
470 : : /*
471 : : * Obtain the value of each column for this row, installing them into
472 : : * the slot; then add the tuple to the tuplestore.
473 : : */
474 [ + + ]: 110643 : for (colno = 0; colno < natts; colno++)
475 : : {
3180 andres@anarazel.de 476 : 94624 : Form_pg_attribute att = TupleDescAttr(tupdesc, colno);
477 : :
3345 alvherre@alvh.no-ip. 478 [ + + ]: 94624 : if (colno == ordinalitycol)
479 : : {
480 : : /* Fast path for ordinality column */
481 : 180 : values[colno] = Int32GetDatum(tstate->ordinal++);
482 : 180 : nulls[colno] = false;
483 : : }
484 : : else
485 : : {
486 : : bool isnull;
487 : :
488 : 94444 : values[colno] = routine->GetValue(tstate,
489 : : colno,
490 : : att->atttypid,
491 : : att->atttypmod,
492 : : &isnull);
493 : :
494 : : /* No value? Evaluate and apply the default, if any */
495 [ + + + + ]: 94380 : if (isnull && cell != NULL)
496 : : {
497 : 15262 : ExprState *coldefexpr = (ExprState *) lfirst(cell);
498 : :
499 [ + + ]: 15262 : if (coldefexpr != NULL)
500 : 148 : values[colno] = ExecEvalExpr(coldefexpr, econtext,
501 : : &isnull);
502 : : }
503 : :
504 : : /* Verify a possible NOT NULL constraint */
505 [ + + + + ]: 94380 : if (isnull && bms_is_member(colno, tstate->notnulls))
506 [ + - ]: 4 : ereport(ERROR,
507 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
508 : : errmsg("null is not allowed in column \"%s\"",
509 : : NameStr(att->attname))));
510 : :
511 : 94376 : nulls[colno] = isnull;
512 : : }
513 : :
514 : : /* advance list of default expressions */
515 [ + + ]: 94556 : if (cell != NULL)
2486 tgl@sss.pgh.pa.us 516 : 91494 : cell = lnext(tstate->coldefexprs, cell);
517 : : }
518 : :
3345 alvherre@alvh.no-ip. 519 : 16019 : tuplestore_putvalues(tstate->tupstore, tupdesc, values, nulls);
520 : :
2822 rhodiumtoad@postgres 521 : 16019 : MemoryContextReset(econtext->ecxt_per_tuple_memory);
522 : : }
523 : :
3345 alvherre@alvh.no-ip. 524 : 446 : MemoryContextSwitchTo(oldcxt);
525 : 446 : }
|