4 * Sample to demonstrate C functions which return setof scalar
6 * Joe Conway <mail@joeconway.com>
8 * Copyright 2002 by PostgreSQL Global Development Group
10 * Permission to use, copy, modify, and distribute this software and its
11 * documentation for any purpose, without fee, and without a written agreement
12 * is hereby granted, provided that the above copyright notice and this
13 * paragraph and the following two paragraphs appear in all copies.
15 * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
16 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
17 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
18 * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
19 * POSSIBILITY OF SUCH DAMAGE.
21 * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
22 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
23 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
24 * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
25 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
34 #include "executor/spi.h"
35 #include "miscadmin.h"
36 #include "utils/builtins.h"
37 #include "utils/guc.h"
38 #include "utils/lsyscache.h"
40 #include "tablefunc.h"
42 static void validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch);
43 static bool compatCrosstabTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
44 static bool compatConnectbyTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
45 static void get_normal_pair(float8 *x1, float8 *x2);
46 static TupleDesc make_crosstab_tupledesc(TupleDesc spi_tupdesc,
48 static Tuplestorestate *connectby(char *relname,
55 MemoryContext per_query_ctx,
56 AttInMetadata *attinmeta);
57 static Tuplestorestate *build_tuplestore_recursively(char *key_fld,
66 MemoryContext per_query_ctx,
67 AttInMetadata *attinmeta,
68 Tuplestorestate *tupstore);
69 static char *quote_ident_cstr(char *rawstr);
73 float8 mean; /* mean of the distribution */
74 float8 stddev; /* stddev of the distribution */
75 float8 carry_val; /* hold second generated value */
76 bool use_carry; /* use second generated value */
81 SPITupleTable *spi_tuptable; /* sql results from user query */
82 char *lastrowid; /* rowid of the last tuple sent */
85 #define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
86 #define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
87 #define xpfree(var_) \
96 /* sign, 10 digits, '\0' */
97 #define INT32_STRLEN 12
100 * normal_rand - return requested number of random values
101 * with a Gaussian (Normal) distribution.
103 * inputs are int numvals, float8 lower_bound, and float8 upper_bound
106 PG_FUNCTION_INFO_V1(normal_rand);
108 normal_rand(PG_FUNCTION_ARGS)
110 FuncCallContext *funcctx;
113 normal_rand_fctx *fctx;
118 MemoryContext oldcontext;
120 /* stuff done only on the first call of the function */
121 if (SRF_IS_FIRSTCALL())
123 /* create a function context for cross-call persistence */
124 funcctx = SRF_FIRSTCALL_INIT();
127 * switch to memory context appropriate for multiple function
130 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
132 /* total number of tuples to be returned */
133 funcctx->max_calls = PG_GETARG_UINT32(0);
135 /* allocate memory for user context */
136 fctx = (normal_rand_fctx *) palloc(sizeof(normal_rand_fctx));
139 * Use fctx to keep track of upper and lower bounds from call to
140 * call. It will also be used to carry over the spare value we get
141 * from the Box-Muller algorithm so that we only actually
142 * calculate a new value every other call.
144 fctx->mean = PG_GETARG_FLOAT8(1);
145 fctx->stddev = PG_GETARG_FLOAT8(2);
147 fctx->use_carry = false;
149 funcctx->user_fctx = fctx;
152 * we might actually get passed a negative number, but for this
153 * purpose it doesn't matter, just cast it as an unsigned value
155 srandom(PG_GETARG_UINT32(3));
157 MemoryContextSwitchTo(oldcontext);
160 /* stuff done on every call of the function */
161 funcctx = SRF_PERCALL_SETUP();
163 call_cntr = funcctx->call_cntr;
164 max_calls = funcctx->max_calls;
165 fctx = funcctx->user_fctx;
167 stddev = fctx->stddev;
168 carry_val = fctx->carry_val;
169 use_carry = fctx->use_carry;
171 if (call_cntr < max_calls) /* do when there is more left to send */
178 * reset use_carry and use second value obtained on last pass
180 fctx->use_carry = false;
188 /* Get the next two normal values */
189 get_normal_pair(&normval_1, &normval_2);
192 result = mean + (stddev * normval_1);
194 /* and save the second */
195 fctx->carry_val = mean + (stddev * normval_2);
196 fctx->use_carry = true;
199 /* send the result */
200 SRF_RETURN_NEXT(funcctx, Float8GetDatum(result));
203 /* do when there is no more left */
204 SRF_RETURN_DONE(funcctx);
209 * Assigns normally distributed (Gaussian) values to a pair of provided
210 * parameters, with mean 0, standard deviation 1.
212 * This routine implements Algorithm P (Polar method for normal deviates)
213 * from Knuth's _The_Art_of_Computer_Programming_, Volume 2, 3rd ed., pages
214 * 122-126. Knuth cites his source as "The polar method", G. E. P. Box, M. E.
215 * Muller, and G. Marsaglia, _Annals_Math,_Stat._ 29 (1958), 610-611.
219 get_normal_pair(float8 *x1, float8 *x2)
229 u1 = (float8) random() / (float8) RAND_MAX;
230 u2 = (float8) random() / (float8) RAND_MAX;
232 v1 = (2.0 * u1) - 1.0;
233 v2 = (2.0 * u2) - 1.0;
235 s = pow(v1, 2) + pow(v2, 2);
247 *x1 = v1 * sqrt((-2.0 * log(s)) / s);
248 *x2 = v2 * sqrt((-2.0 * log(s)) / s);
256 * crosstab - create a crosstab of rowids and values columns from a
257 * SQL statement returning one rowid column, one category column,
258 * and one value column.
260 * e.g. given sql which produces:
263 * ------+-------+-------
274 * <===== values columns =====>
275 * rowid cat1 cat2 cat3 cat4
276 * ------+-------+-------+-------+-------
277 * row1 val1 val2 val3 val4
278 * row2 val5 val6 val7 val8
281 * 1. SQL result must be ordered by 1,2.
282 * 2. The number of values columns depends on the tuple description
283 * of the function's declared return type.
284 * 2. Missing values (i.e. not enough adjacent rows of same rowid to
285 * fill the number of result values columns) are filled in with nulls.
286 * 3. Extra values (i.e. too many adjacent rows of same rowid to fill
287 * the number of result values columns) are skipped.
288 * 4. Rows with all nulls in the values columns are skipped.
290 PG_FUNCTION_INFO_V1(crosstab);
292 crosstab(PG_FUNCTION_ARGS)
294 FuncCallContext *funcctx;
295 TupleDesc ret_tupdesc;
298 TupleTableSlot *slot;
299 AttInMetadata *attinmeta;
300 SPITupleTable *spi_tuptable = NULL;
301 TupleDesc spi_tupdesc;
302 char *lastrowid = NULL;
306 MemoryContext oldcontext;
308 /* stuff done only on the first call of the function */
309 if (SRF_IS_FIRSTCALL())
311 char *sql = GET_STR(PG_GETARG_TEXT_P(0));
312 Oid funcid = fcinfo->flinfo->fn_oid;
315 TupleDesc tupdesc = NULL;
319 /* create a function context for cross-call persistence */
320 funcctx = SRF_FIRSTCALL_INIT();
323 * switch to memory context appropriate for multiple function
326 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
328 /* Connect to SPI manager */
329 if ((ret = SPI_connect()) < 0)
330 elog(ERROR, "crosstab: SPI_connect returned %d", ret);
332 /* Retrieve the desired rows */
333 ret = SPI_exec(sql, 0);
334 proc = SPI_processed;
336 /* Check for qualifying tuples */
337 if ((ret == SPI_OK_SELECT) && (proc > 0))
339 spi_tuptable = SPI_tuptable;
340 spi_tupdesc = spi_tuptable->tupdesc;
343 * The provided SQL query must always return three columns.
345 * 1. rowname the label or identifier for each row in the final
346 * result 2. category the label or identifier for each column
347 * in the final result 3. values the value for each column
348 * in the final result
350 if (spi_tupdesc->natts != 3)
351 elog(ERROR, "crosstab: provided SQL must return 3 columns;"
352 " a rowid, a category, and a values column");
356 /* no qualifying tuples */
358 SRF_RETURN_DONE(funcctx);
361 /* SPI switches context on us, so reset it */
362 MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
364 /* get the typeid that represents our return type */
365 functypeid = get_func_rettype(funcid);
367 /* check typtype to see if we have a predetermined return type */
368 functyptype = get_typtype(functypeid);
370 if (functyptype == 'c')
372 /* Build a tuple description for a functypeid tuple */
373 tupdesc = TypeGetTupleDesc(functypeid, NIL);
375 else if (functyptype == 'p' && functypeid == RECORDOID)
377 if (fcinfo->nargs != 2)
378 elog(ERROR, "Wrong number of arguments specified for function");
381 int num_catagories = PG_GETARG_INT32(1);
383 tupdesc = make_crosstab_tupledesc(spi_tupdesc, num_catagories);
386 else if (functyptype == 'b')
387 elog(ERROR, "Invalid kind of return type specified for function");
389 elog(ERROR, "Unknown kind of return type specified for function");
392 * Check that return tupdesc is compatible with the one we got
393 * from ret_relname, at least based on number and type of
396 if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc))
397 elog(ERROR, "crosstab: return and sql tuple descriptions are"
400 /* allocate a slot for a tuple with this tupdesc */
401 slot = TupleDescGetSlot(tupdesc);
403 /* assign slot to function context */
404 funcctx->slot = slot;
407 * Generate attribute metadata needed later to produce tuples from
410 attinmeta = TupleDescGetAttInMetadata(tupdesc);
411 funcctx->attinmeta = attinmeta;
413 /* allocate memory for user context */
414 fctx = (crosstab_fctx *) palloc(sizeof(crosstab_fctx));
417 * Save spi data for use across calls
419 fctx->spi_tuptable = spi_tuptable;
420 fctx->lastrowid = NULL;
421 funcctx->user_fctx = fctx;
423 /* total number of tuples to be returned */
424 funcctx->max_calls = proc;
426 MemoryContextSwitchTo(oldcontext);
429 /* stuff done on every call of the function */
430 funcctx = SRF_PERCALL_SETUP();
433 * initialize per-call variables
435 call_cntr = funcctx->call_cntr;
436 max_calls = funcctx->max_calls;
438 /* return slot for our tuple */
439 slot = funcctx->slot;
441 /* user context info */
442 fctx = (crosstab_fctx *) funcctx->user_fctx;
443 lastrowid = fctx->lastrowid;
444 spi_tuptable = fctx->spi_tuptable;
447 spi_tupdesc = spi_tuptable->tupdesc;
449 /* attribute return type and return tuple description */
450 attinmeta = funcctx->attinmeta;
451 ret_tupdesc = attinmeta->tupdesc;
453 /* the return tuple always must have 1 rowid + num_categories columns */
454 num_categories = ret_tupdesc->natts - 1;
456 if (call_cntr < max_calls) /* do when there is more left to send */
461 bool allnulls = true;
466 values = (char **) palloc((1 + num_categories) * sizeof(char *));
468 /* and make sure it's clear */
469 memset(values, '\0', (1 + num_categories) * sizeof(char *));
472 * now loop through the sql results and assign each value in
473 * sequence to the next category
475 for (i = 0; i < num_categories; i++)
480 /* see if we've gone too far already */
481 if (call_cntr >= max_calls)
484 /* get the next sql result tuple */
485 spi_tuple = spi_tuptable->vals[call_cntr];
487 /* get the rowid from the current sql result tuple */
488 rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
491 * If this is the first pass through the values for this
492 * rowid set it, otherwise make sure it hasn't changed on
493 * us. Also check to see if the rowid is the same as that
494 * of the last tuple sent -- if so, skip this tuple
498 values[0] = pstrdup(rowid);
500 if ((rowid != NULL) && (strcmp(rowid, values[0]) == 0))
502 if ((lastrowid != NULL) && (strcmp(rowid, lastrowid) == 0))
504 else if (allnulls == true)
508 * Get the next category item value, which is alway
509 * attribute number three.
511 * Be careful to sssign the value to the array index
512 * based on which category we are presently
515 values[1 + i] = SPI_getvalue(spi_tuple, spi_tupdesc, 3);
518 * increment the counter since we consume a row for
519 * each category, but not for last pass because the
520 * API will do that for us
522 if (i < (num_categories - 1))
523 call_cntr = ++funcctx->call_cntr;
528 * We'll fill in NULLs for the missing values, but we
529 * need to decrement the counter since this sql result
530 * row doesn't belong to the current output tuple.
532 call_cntr = --funcctx->call_cntr;
540 xpfree(fctx->lastrowid);
542 if (values[0] != NULL)
545 * switch to memory context appropriate for multiple
548 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
550 lastrowid = fctx->lastrowid = pstrdup(values[0]);
551 MemoryContextSwitchTo(oldcontext);
556 /* build the tuple */
557 tuple = BuildTupleFromCStrings(attinmeta, values);
559 /* make the tuple into a datum */
560 result = TupleGetDatum(slot, tuple);
563 for (i = 0; i < num_categories + 1; i++)
564 if (values[i] != NULL)
568 SRF_RETURN_NEXT(funcctx, result);
573 * Skipping this tuple entirely, but we need to advance
574 * the counter like the API would if we had returned one.
576 call_cntr = ++funcctx->call_cntr;
578 /* we'll start over at the top */
581 /* see if we've gone too far already */
582 if (call_cntr >= max_calls)
584 /* release SPI related resources */
586 SRF_RETURN_DONE(funcctx);
592 /* do when there is no more left */
594 /* release SPI related resources */
596 SRF_RETURN_DONE(funcctx);
601 * connectby_text - produce a result set from a hierarchical (parent/child)
604 * e.g. given table foo:
607 * ------+--------------
619 * connectby(text relname, text keyid_fld, text parent_keyid_fld,
620 * text start_with, int max_depth [, text branch_delim])
621 * connectby('foo', 'keyid', 'parent_keyid', 'row2', 0, '~') returns:
623 * keyid parent_id level branch
624 * ------+-----------+--------+-----------------------
626 * row4 row2 1 row2~row4
627 * row6 row4 2 row2~row4~row6
628 * row8 row6 3 row2~row4~row6~row8
629 * row5 row2 1 row2~row5
630 * row9 row5 2 row2~row5~row9
633 PG_FUNCTION_INFO_V1(connectby_text);
635 #define CONNECTBY_NCOLS 4
636 #define CONNECTBY_NCOLS_NOBRANCH 3
639 connectby_text(PG_FUNCTION_ARGS)
641 char *relname = GET_STR(PG_GETARG_TEXT_P(0));
642 char *key_fld = GET_STR(PG_GETARG_TEXT_P(1));
643 char *parent_key_fld = GET_STR(PG_GETARG_TEXT_P(2));
644 char *start_with = GET_STR(PG_GETARG_TEXT_P(3));
645 int max_depth = PG_GETARG_INT32(4);
646 char *branch_delim = NULL;
647 bool show_branch = false;
648 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
650 AttInMetadata *attinmeta;
651 MemoryContext per_query_ctx;
652 MemoryContext oldcontext;
654 if (fcinfo->nargs == 6)
656 branch_delim = GET_STR(PG_GETARG_TEXT_P(5));
660 per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
661 oldcontext = MemoryContextSwitchTo(per_query_ctx);
663 /* get the requested return tuple description */
664 tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
666 /* does it meet our needs */
667 validateConnectbyTupleDesc(tupdesc, show_branch);
669 /* OK, use it then */
670 attinmeta = TupleDescGetAttInMetadata(tupdesc);
672 /* check to see if caller supports us returning a tuplestore */
673 if (!rsinfo->allowedModes & SFRM_Materialize)
674 elog(ERROR, "connectby requires Materialize mode, but it is not "
675 "allowed in this context");
678 rsinfo->returnMode = SFRM_Materialize;
679 rsinfo->setResult = connectby(relname,
688 rsinfo->setDesc = tupdesc;
690 MemoryContextSwitchTo(oldcontext);
693 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
694 * tuples are in our tuplestore and passed back through
695 * rsinfo->setResult. rsinfo->setDesc is set to the tuple description
696 * that we actually used to build our tuples with, so the caller can
697 * verify we did what it was expecting.
703 * connectby - does the real work for connectby_text()
705 static Tuplestorestate *
706 connectby(char *relname,
708 char *parent_key_fld,
713 MemoryContext per_query_ctx,
714 AttInMetadata *attinmeta)
716 Tuplestorestate *tupstore = NULL;
718 MemoryContext oldcontext;
720 /* Connect to SPI manager */
721 if ((ret = SPI_connect()) < 0)
722 elog(ERROR, "connectby: SPI_connect returned %d", ret);
724 /* switch to longer term context to create the tuple store */
725 oldcontext = MemoryContextSwitchTo(per_query_ctx);
727 /* initialize our tuplestore */
728 tupstore = tuplestore_begin_heap(true, SortMem);
730 MemoryContextSwitchTo(oldcontext);
732 /* now go get the whole tree */
733 tupstore = build_tuplestore_recursively(key_fld,
738 start_with, /* current_branch */
739 0, /* initial level is 0 */
748 oldcontext = MemoryContextSwitchTo(per_query_ctx);
749 tuplestore_donestoring(tupstore);
750 MemoryContextSwitchTo(oldcontext);
755 static Tuplestorestate *
756 build_tuplestore_recursively(char *key_fld,
757 char *parent_key_fld,
765 MemoryContext per_query_ctx,
766 AttInMetadata *attinmeta,
767 Tuplestorestate *tupstore)
769 TupleDesc tupdesc = attinmeta->tupdesc;
770 MemoryContext oldcontext;
771 StringInfo sql = makeStringInfo();
775 if (max_depth > 0 && level > max_depth)
778 /* Build initial sql statement */
779 appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL",
780 quote_ident_cstr(key_fld),
781 quote_ident_cstr(parent_key_fld),
782 quote_ident_cstr(relname),
783 quote_ident_cstr(parent_key_fld),
785 quote_ident_cstr(key_fld));
787 /* Retrieve the desired rows */
788 ret = SPI_exec(sql->data, 0);
789 proc = SPI_processed;
791 /* Check for qualifying tuples */
792 if ((ret == SPI_OK_SELECT) && (proc > 0))
796 SPITupleTable *tuptable = SPI_tuptable;
797 TupleDesc spi_tupdesc = tuptable->tupdesc;
800 char *current_key_parent;
801 char current_level[INT32_STRLEN];
802 char *current_branch;
804 StringInfo branchstr = NULL;
806 /* start a new branch */
807 branchstr = makeStringInfo();
810 values = (char **) palloc(CONNECTBY_NCOLS * sizeof(char *));
812 values = (char **) palloc(CONNECTBY_NCOLS_NOBRANCH * sizeof(char *));
814 /* First time through, do a little setup */
818 * Check that return tupdesc is compatible with the one we got
819 * from the query, but only at level 0 -- no need to check
823 if (!compatConnectbyTupleDescs(tupdesc, spi_tupdesc))
824 elog(ERROR, "connectby: return and sql tuple descriptions are "
827 /* root value is the one we initially start with */
828 values[0] = start_with;
830 /* root value has no parent */
833 /* root level is 0 */
834 sprintf(current_level, "%d", level);
835 values[2] = current_level;
837 /* root branch is just starting root value */
839 values[3] = start_with;
841 /* construct the tuple */
842 tuple = BuildTupleFromCStrings(attinmeta, values);
844 /* switch to long lived context while storing the tuple */
845 oldcontext = MemoryContextSwitchTo(per_query_ctx);
848 tuplestore_puttuple(tupstore, tuple);
850 /* now reset the context */
851 MemoryContextSwitchTo(oldcontext);
853 /* increment level */
857 for (i = 0; i < proc; i++)
859 /* initialize branch for this pass */
860 appendStringInfo(branchstr, "%s", branch);
862 /* get the next sql result tuple */
863 spi_tuple = tuptable->vals[i];
865 /* get the current key and parent */
866 current_key = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
867 current_key_parent = pstrdup(SPI_getvalue(spi_tuple, spi_tupdesc, 2));
869 /* check to see if this key is also an ancestor */
870 if (strstr(branchstr->data, current_key))
871 elog(ERROR, "infinite recursion detected");
873 /* get the current level */
874 sprintf(current_level, "%d", level);
876 /* extend the branch */
877 appendStringInfo(branchstr, "%s%s", branch_delim, current_key);
878 current_branch = branchstr->data;
881 values[0] = pstrdup(current_key);
882 values[1] = current_key_parent;
883 values[2] = current_level;
885 values[3] = current_branch;
887 tuple = BuildTupleFromCStrings(attinmeta, values);
890 xpfree(current_key_parent);
892 /* switch to long lived context while storing the tuple */
893 oldcontext = MemoryContextSwitchTo(per_query_ctx);
895 /* store the tuple for later use */
896 tuplestore_puttuple(tupstore, tuple);
898 /* now reset the context */
899 MemoryContextSwitchTo(oldcontext);
901 heap_freetuple(tuple);
903 /* recurse using current_key_parent as the new start_with */
904 tupstore = build_tuplestore_recursively(key_fld,
917 /* reset branch for next pass */
918 xpfree(branchstr->data);
919 initStringInfo(branchstr);
927 * Check expected (query runtime) tupdesc suitable for Connectby
930 validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch)
932 /* are there the correct number of columns */
935 if (tupdesc->natts != CONNECTBY_NCOLS)
936 elog(ERROR, "Query-specified return tuple not valid for Connectby: "
937 "wrong number of columns");
941 if (tupdesc->natts != CONNECTBY_NCOLS_NOBRANCH)
942 elog(ERROR, "Query-specified return tuple not valid for Connectby: "
943 "wrong number of columns");
946 /* check that the types of the first two columns match */
947 if (tupdesc->attrs[0]->atttypid != tupdesc->attrs[1]->atttypid)
948 elog(ERROR, "Query-specified return tuple not valid for Connectby: "
949 "first two columns must be the same type");
951 /* check that the type of the third column is INT4 */
952 if (tupdesc->attrs[2]->atttypid != INT4OID)
953 elog(ERROR, "Query-specified return tuple not valid for Connectby: "
954 "third column must be type %s", format_type_be(INT4OID));
956 /* check that the type of the forth column is TEXT if applicable */
957 if (show_branch && tupdesc->attrs[3]->atttypid != TEXTOID)
958 elog(ERROR, "Query-specified return tuple not valid for Connectby: "
959 "third column must be type %s", format_type_be(TEXTOID));
961 /* OK, the tupdesc is valid for our purposes */
965 * Check if spi sql tupdesc and return tupdesc are compatible
968 compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
973 /* check the key_fld types match */
974 ret_atttypid = ret_tupdesc->attrs[0]->atttypid;
975 sql_atttypid = sql_tupdesc->attrs[0]->atttypid;
976 if (ret_atttypid != sql_atttypid)
977 elog(ERROR, "compatConnectbyTupleDescs: SQL key field datatype does "
978 "not match return key field datatype");
980 /* check the parent_key_fld types match */
981 ret_atttypid = ret_tupdesc->attrs[1]->atttypid;
982 sql_atttypid = sql_tupdesc->attrs[1]->atttypid;
983 if (ret_atttypid != sql_atttypid)
984 elog(ERROR, "compatConnectbyTupleDescs: SQL parent key field datatype "
985 "does not match return parent key field datatype");
987 /* OK, the two tupdescs are compatible for our purposes */
992 * Check if two tupdescs match in type of attributes
995 compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
998 Form_pg_attribute ret_attr;
1000 Form_pg_attribute sql_attr;
1003 /* check the rowid types match */
1004 ret_atttypid = ret_tupdesc->attrs[0]->atttypid;
1005 sql_atttypid = sql_tupdesc->attrs[0]->atttypid;
1006 if (ret_atttypid != sql_atttypid)
1007 elog(ERROR, "compatCrosstabTupleDescs: SQL rowid datatype does not match"
1008 " return rowid datatype");
1011 * - attribute [1] of the sql tuple is the category; no need to check
1012 * it - attribute [2] of the sql tuple should match attributes [1] to
1013 * [natts] of the return tuple
1015 sql_attr = sql_tupdesc->attrs[2];
1016 for (i = 1; i < ret_tupdesc->natts; i++)
1018 ret_attr = ret_tupdesc->attrs[i];
1020 if (ret_attr->atttypid != sql_attr->atttypid)
1024 /* OK, the two tupdescs are compatible for our purposes */
1029 make_crosstab_tupledesc(TupleDesc spi_tupdesc, int num_catagories)
1031 Form_pg_attribute sql_attr;
1036 char attname[NAMEDATALEN];
1040 * We need to build a tuple description with one column for the
1041 * rowname, and num_catagories columns for the values. Each must be of
1042 * the same type as the corresponding spi result input column.
1044 natts = num_catagories + 1;
1045 tupdesc = CreateTemplateTupleDesc(natts, false);
1047 /* first the rowname column */
1050 sql_attr = spi_tupdesc->attrs[0];
1051 sql_atttypid = sql_attr->atttypid;
1053 strcpy(attname, "rowname");
1055 TupleDescInitEntry(tupdesc, attnum, attname, sql_atttypid,
1058 /* now the catagory values columns */
1059 sql_attr = spi_tupdesc->attrs[2];
1060 sql_atttypid = sql_attr->atttypid;
1062 for (i = 0; i < num_catagories; i++)
1066 sprintf(attname, "category_%d", i + 1);
1067 TupleDescInitEntry(tupdesc, attnum, attname, sql_atttypid,
1075 * Return a properly quoted identifier.
1076 * Uses quote_ident in quote.c
1079 quote_ident_cstr(char *rawstr)
1085 rawstr_text = DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(rawstr)));
1086 result_text = DatumGetTextP(DirectFunctionCall1(quote_ident, PointerGetDatum(rawstr_text)));
1087 result = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(result_text)));