You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tablefunc.c 43KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592
  1. /*
  2. * contrib/tablefunc/tablefunc.c
  3. *
  4. *
  5. * tablefunc
  6. *
  7. * Sample to demonstrate C functions which return setof scalar
  8. * and setof composite.
  9. * Joe Conway <mail@joeconway.com>
  10. * And contributors:
  11. * Nabil Sayegh <postgresql@e-trolley.de>
  12. *
  13. * Copyright (c) 2002-2019, PostgreSQL Global Development Group
  14. *
  15. * Permission to use, copy, modify, and distribute this software and its
  16. * documentation for any purpose, without fee, and without a written agreement
  17. * is hereby granted, provided that the above copyright notice and this
  18. * paragraph and the following two paragraphs appear in all copies.
  19. *
  20. * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
  21. * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
  22. * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
  23. * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
  24. * POSSIBILITY OF SUCH DAMAGE.
  25. *
  26. * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
  27. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  28. * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
  29. * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
  30. * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  31. *
  32. */
  33. #include "postgres.h"
  34. #include <math.h>
  35. #include "access/htup_details.h"
  36. #include "catalog/pg_type.h"
  37. #include "executor/spi.h"
  38. #include "funcapi.h"
  39. #include "lib/stringinfo.h"
  40. #include "miscadmin.h"
  41. #include "utils/builtins.h"
  42. #include "tablefunc.h"
  43. PG_MODULE_MAGIC;
  44. static HTAB *load_categories_hash(char *cats_sql, MemoryContext per_query_ctx);
  45. static Tuplestorestate *get_crosstab_tuplestore(char *sql,
  46. HTAB *crosstab_hash,
  47. TupleDesc tupdesc,
  48. MemoryContext per_query_ctx,
  49. bool randomAccess);
  50. static void validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial);
  51. static bool compatCrosstabTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
  52. static void compatConnectbyTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
  53. static void get_normal_pair(float8 *x1, float8 *x2);
  54. static Tuplestorestate *connectby(char *relname,
  55. char *key_fld,
  56. char *parent_key_fld,
  57. char *orderby_fld,
  58. char *branch_delim,
  59. char *start_with,
  60. int max_depth,
  61. bool show_branch,
  62. bool show_serial,
  63. MemoryContext per_query_ctx,
  64. bool randomAccess,
  65. AttInMetadata *attinmeta);
  66. static void build_tuplestore_recursively(char *key_fld,
  67. char *parent_key_fld,
  68. char *relname,
  69. char *orderby_fld,
  70. char *branch_delim,
  71. char *start_with,
  72. char *branch,
  73. int level,
  74. int *serial,
  75. int max_depth,
  76. bool show_branch,
  77. bool show_serial,
  78. MemoryContext per_query_ctx,
  79. AttInMetadata *attinmeta,
  80. Tuplestorestate *tupstore);
  81. typedef struct
  82. {
  83. float8 mean; /* mean of the distribution */
  84. float8 stddev; /* stddev of the distribution */
  85. float8 carry_val; /* hold second generated value */
  86. bool use_carry; /* use second generated value */
  87. } normal_rand_fctx;
  88. #define xpfree(var_) \
  89. do { \
  90. if (var_ != NULL) \
  91. { \
  92. pfree(var_); \
  93. var_ = NULL; \
  94. } \
  95. } while (0)
  96. #define xpstrdup(tgtvar_, srcvar_) \
  97. do { \
  98. if (srcvar_) \
  99. tgtvar_ = pstrdup(srcvar_); \
  100. else \
  101. tgtvar_ = NULL; \
  102. } while (0)
  103. #define xstreq(tgtvar_, srcvar_) \
  104. (((tgtvar_ == NULL) && (srcvar_ == NULL)) || \
  105. ((tgtvar_ != NULL) && (srcvar_ != NULL) && (strcmp(tgtvar_, srcvar_) == 0)))
  106. /* sign, 10 digits, '\0' */
  107. #define INT32_STRLEN 12
  108. /* stored info for a crosstab category */
  109. typedef struct crosstab_cat_desc
  110. {
  111. char *catname; /* full category name */
  112. uint64 attidx; /* zero based */
  113. } crosstab_cat_desc;
  114. #define MAX_CATNAME_LEN NAMEDATALEN
  115. #define INIT_CATS 64
  116. #define crosstab_HashTableLookup(HASHTAB, CATNAME, CATDESC) \
  117. do { \
  118. crosstab_HashEnt *hentry; char key[MAX_CATNAME_LEN]; \
  119. \
  120. MemSet(key, 0, MAX_CATNAME_LEN); \
  121. snprintf(key, MAX_CATNAME_LEN - 1, "%s", CATNAME); \
  122. hentry = (crosstab_HashEnt*) hash_search(HASHTAB, \
  123. key, HASH_FIND, NULL); \
  124. if (hentry) \
  125. CATDESC = hentry->catdesc; \
  126. else \
  127. CATDESC = NULL; \
  128. } while(0)
  129. #define crosstab_HashTableInsert(HASHTAB, CATDESC) \
  130. do { \
  131. crosstab_HashEnt *hentry; bool found; char key[MAX_CATNAME_LEN]; \
  132. \
  133. MemSet(key, 0, MAX_CATNAME_LEN); \
  134. snprintf(key, MAX_CATNAME_LEN - 1, "%s", CATDESC->catname); \
  135. hentry = (crosstab_HashEnt*) hash_search(HASHTAB, \
  136. key, HASH_ENTER, &found); \
  137. if (found) \
  138. ereport(ERROR, \
  139. (errcode(ERRCODE_DUPLICATE_OBJECT), \
  140. errmsg("duplicate category name"))); \
  141. hentry->catdesc = CATDESC; \
  142. } while(0)
  143. /* hash table */
  144. typedef struct crosstab_hashent
  145. {
  146. char internal_catname[MAX_CATNAME_LEN];
  147. crosstab_cat_desc *catdesc;
  148. } crosstab_HashEnt;
  149. /*
  150. * normal_rand - return requested number of random values
  151. * with a Gaussian (Normal) distribution.
  152. *
  153. * inputs are int numvals, float8 mean, and float8 stddev
  154. * returns setof float8
  155. */
  156. PG_FUNCTION_INFO_V1(normal_rand);
  157. Datum
  158. normal_rand(PG_FUNCTION_ARGS)
  159. {
  160. FuncCallContext *funcctx;
  161. uint64 call_cntr;
  162. uint64 max_calls;
  163. normal_rand_fctx *fctx;
  164. float8 mean;
  165. float8 stddev;
  166. float8 carry_val;
  167. bool use_carry;
  168. MemoryContext oldcontext;
  169. /* stuff done only on the first call of the function */
  170. if (SRF_IS_FIRSTCALL())
  171. {
  172. /* create a function context for cross-call persistence */
  173. funcctx = SRF_FIRSTCALL_INIT();
  174. /*
  175. * switch to memory context appropriate for multiple function calls
  176. */
  177. oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
  178. /* total number of tuples to be returned */
  179. funcctx->max_calls = PG_GETARG_UINT32(0);
  180. /* allocate memory for user context */
  181. fctx = (normal_rand_fctx *) palloc(sizeof(normal_rand_fctx));
  182. /*
  183. * Use fctx to keep track of upper and lower bounds from call to call.
  184. * It will also be used to carry over the spare value we get from the
  185. * Box-Muller algorithm so that we only actually calculate a new value
  186. * every other call.
  187. */
  188. fctx->mean = PG_GETARG_FLOAT8(1);
  189. fctx->stddev = PG_GETARG_FLOAT8(2);
  190. fctx->carry_val = 0;
  191. fctx->use_carry = false;
  192. funcctx->user_fctx = fctx;
  193. MemoryContextSwitchTo(oldcontext);
  194. }
  195. /* stuff done on every call of the function */
  196. funcctx = SRF_PERCALL_SETUP();
  197. call_cntr = funcctx->call_cntr;
  198. max_calls = funcctx->max_calls;
  199. fctx = funcctx->user_fctx;
  200. mean = fctx->mean;
  201. stddev = fctx->stddev;
  202. carry_val = fctx->carry_val;
  203. use_carry = fctx->use_carry;
  204. if (call_cntr < max_calls) /* do when there is more left to send */
  205. {
  206. float8 result;
  207. if (use_carry)
  208. {
  209. /*
  210. * reset use_carry and use second value obtained on last pass
  211. */
  212. fctx->use_carry = false;
  213. result = carry_val;
  214. }
  215. else
  216. {
  217. float8 normval_1;
  218. float8 normval_2;
  219. /* Get the next two normal values */
  220. get_normal_pair(&normval_1, &normval_2);
  221. /* use the first */
  222. result = mean + (stddev * normval_1);
  223. /* and save the second */
  224. fctx->carry_val = mean + (stddev * normval_2);
  225. fctx->use_carry = true;
  226. }
  227. /* send the result */
  228. SRF_RETURN_NEXT(funcctx, Float8GetDatum(result));
  229. }
  230. else
  231. /* do when there is no more left */
  232. SRF_RETURN_DONE(funcctx);
  233. }
  234. /*
  235. * get_normal_pair()
  236. * Assigns normally distributed (Gaussian) values to a pair of provided
  237. * parameters, with mean 0, standard deviation 1.
  238. *
  239. * This routine implements Algorithm P (Polar method for normal deviates)
  240. * from Knuth's _The_Art_of_Computer_Programming_, Volume 2, 3rd ed., pages
  241. * 122-126. Knuth cites his source as "The polar method", G. E. P. Box, M. E.
  242. * Muller, and G. Marsaglia, _Annals_Math,_Stat._ 29 (1958), 610-611.
  243. *
  244. */
  245. static void
  246. get_normal_pair(float8 *x1, float8 *x2)
  247. {
  248. float8 u1,
  249. u2,
  250. v1,
  251. v2,
  252. s;
  253. do
  254. {
  255. u1 = (float8) random() / (float8) MAX_RANDOM_VALUE;
  256. u2 = (float8) random() / (float8) MAX_RANDOM_VALUE;
  257. v1 = (2.0 * u1) - 1.0;
  258. v2 = (2.0 * u2) - 1.0;
  259. s = v1 * v1 + v2 * v2;
  260. } while (s >= 1.0);
  261. if (s == 0)
  262. {
  263. *x1 = 0;
  264. *x2 = 0;
  265. }
  266. else
  267. {
  268. s = sqrt((-2.0 * log(s)) / s);
  269. *x1 = v1 * s;
  270. *x2 = v2 * s;
  271. }
  272. }
  273. /*
  274. * crosstab - create a crosstab of rowids and values columns from a
  275. * SQL statement returning one rowid column, one category column,
  276. * and one value column.
  277. *
  278. * e.g. given sql which produces:
  279. *
  280. * rowid cat value
  281. * ------+-------+-------
  282. * row1 cat1 val1
  283. * row1 cat2 val2
  284. * row1 cat3 val3
  285. * row1 cat4 val4
  286. * row2 cat1 val5
  287. * row2 cat2 val6
  288. * row2 cat3 val7
  289. * row2 cat4 val8
  290. *
  291. * crosstab returns:
  292. * <===== values columns =====>
  293. * rowid cat1 cat2 cat3 cat4
  294. * ------+-------+-------+-------+-------
  295. * row1 val1 val2 val3 val4
  296. * row2 val5 val6 val7 val8
  297. *
  298. * NOTES:
  299. * 1. SQL result must be ordered by 1,2.
  300. * 2. The number of values columns depends on the tuple description
  301. * of the function's declared return type. The return type's columns
  302. * must match the datatypes of the SQL query's result. The datatype
  303. * of the category column can be anything, however.
  304. * 3. Missing values (i.e. not enough adjacent rows of same rowid to
  305. * fill the number of result values columns) are filled in with nulls.
  306. * 4. Extra values (i.e. too many adjacent rows of same rowid to fill
  307. * the number of result values columns) are skipped.
  308. * 5. Rows with all nulls in the values columns are skipped.
  309. */
  310. PG_FUNCTION_INFO_V1(crosstab);
  311. Datum
  312. crosstab(PG_FUNCTION_ARGS)
  313. {
  314. char *sql = text_to_cstring(PG_GETARG_TEXT_PP(0));
  315. ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  316. Tuplestorestate *tupstore;
  317. TupleDesc tupdesc;
  318. uint64 call_cntr;
  319. uint64 max_calls;
  320. AttInMetadata *attinmeta;
  321. SPITupleTable *spi_tuptable;
  322. TupleDesc spi_tupdesc;
  323. bool firstpass;
  324. char *lastrowid;
  325. int i;
  326. int num_categories;
  327. MemoryContext per_query_ctx;
  328. MemoryContext oldcontext;
  329. int ret;
  330. uint64 proc;
  331. /* check to see if caller supports us returning a tuplestore */
  332. if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
  333. ereport(ERROR,
  334. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  335. errmsg("set-valued function called in context that cannot accept a set")));
  336. if (!(rsinfo->allowedModes & SFRM_Materialize))
  337. ereport(ERROR,
  338. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  339. errmsg("materialize mode required, but it is not " \
  340. "allowed in this context")));
  341. per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
  342. /* Connect to SPI manager */
  343. if ((ret = SPI_connect()) < 0)
  344. /* internal error */
  345. elog(ERROR, "crosstab: SPI_connect returned %d", ret);
  346. /* Retrieve the desired rows */
  347. ret = SPI_execute(sql, true, 0);
  348. proc = SPI_processed;
  349. /* If no qualifying tuples, fall out early */
  350. if (ret != SPI_OK_SELECT || proc == 0)
  351. {
  352. SPI_finish();
  353. rsinfo->isDone = ExprEndResult;
  354. PG_RETURN_NULL();
  355. }
  356. spi_tuptable = SPI_tuptable;
  357. spi_tupdesc = spi_tuptable->tupdesc;
  358. /*----------
  359. * The provided SQL query must always return three columns.
  360. *
  361. * 1. rowname
  362. * the label or identifier for each row in the final result
  363. * 2. category
  364. * the label or identifier for each column in the final result
  365. * 3. values
  366. * the value for each column in the final result
  367. *----------
  368. */
  369. if (spi_tupdesc->natts != 3)
  370. ereport(ERROR,
  371. (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  372. errmsg("invalid source data SQL statement"),
  373. errdetail("The provided SQL must return 3 "
  374. "columns: rowid, category, and values.")));
  375. /* get a tuple descriptor for our result type */
  376. switch (get_call_result_type(fcinfo, NULL, &tupdesc))
  377. {
  378. case TYPEFUNC_COMPOSITE:
  379. /* success */
  380. break;
  381. case TYPEFUNC_RECORD:
  382. /* failed to determine actual type of RECORD */
  383. ereport(ERROR,
  384. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  385. errmsg("function returning record called in context "
  386. "that cannot accept type record")));
  387. break;
  388. default:
  389. /* result type isn't composite */
  390. ereport(ERROR,
  391. (errcode(ERRCODE_DATATYPE_MISMATCH),
  392. errmsg("return type must be a row type")));
  393. break;
  394. }
  395. /*
  396. * Check that return tupdesc is compatible with the data we got from SPI,
  397. * at least based on number and type of attributes
  398. */
  399. if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc))
  400. ereport(ERROR,
  401. (errcode(ERRCODE_SYNTAX_ERROR),
  402. errmsg("return and sql tuple descriptions are " \
  403. "incompatible")));
  404. /*
  405. * switch to long-lived memory context
  406. */
  407. oldcontext = MemoryContextSwitchTo(per_query_ctx);
  408. /* make sure we have a persistent copy of the result tupdesc */
  409. tupdesc = CreateTupleDescCopy(tupdesc);
  410. /* initialize our tuplestore in long-lived context */
  411. tupstore =
  412. tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
  413. false, work_mem);
  414. MemoryContextSwitchTo(oldcontext);
  415. /*
  416. * Generate attribute metadata needed later to produce tuples from raw C
  417. * strings
  418. */
  419. attinmeta = TupleDescGetAttInMetadata(tupdesc);
  420. /* total number of tuples to be examined */
  421. max_calls = proc;
  422. /* the return tuple always must have 1 rowid + num_categories columns */
  423. num_categories = tupdesc->natts - 1;
  424. firstpass = true;
  425. lastrowid = NULL;
  426. for (call_cntr = 0; call_cntr < max_calls; call_cntr++)
  427. {
  428. bool skip_tuple = false;
  429. char **values;
  430. /* allocate and zero space */
  431. values = (char **) palloc0((1 + num_categories) * sizeof(char *));
  432. /*
  433. * now loop through the sql results and assign each value in sequence
  434. * to the next category
  435. */
  436. for (i = 0; i < num_categories; i++)
  437. {
  438. HeapTuple spi_tuple;
  439. char *rowid;
  440. /* see if we've gone too far already */
  441. if (call_cntr >= max_calls)
  442. break;
  443. /* get the next sql result tuple */
  444. spi_tuple = spi_tuptable->vals[call_cntr];
  445. /* get the rowid from the current sql result tuple */
  446. rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
  447. /*
  448. * If this is the first pass through the values for this rowid,
  449. * set the first column to rowid
  450. */
  451. if (i == 0)
  452. {
  453. xpstrdup(values[0], rowid);
  454. /*
  455. * Check to see if the rowid is the same as that of the last
  456. * tuple sent -- if so, skip this tuple entirely
  457. */
  458. if (!firstpass && xstreq(lastrowid, rowid))
  459. {
  460. xpfree(rowid);
  461. skip_tuple = true;
  462. break;
  463. }
  464. }
  465. /*
  466. * If rowid hasn't changed on us, continue building the output
  467. * tuple.
  468. */
  469. if (xstreq(rowid, values[0]))
  470. {
  471. /*
  472. * Get the next category item value, which is always attribute
  473. * number three.
  474. *
  475. * Be careful to assign the value to the array index based on
  476. * which category we are presently processing.
  477. */
  478. values[1 + i] = SPI_getvalue(spi_tuple, spi_tupdesc, 3);
  479. /*
  480. * increment the counter since we consume a row for each
  481. * category, but not for last pass because the outer loop will
  482. * do that for us
  483. */
  484. if (i < (num_categories - 1))
  485. call_cntr++;
  486. xpfree(rowid);
  487. }
  488. else
  489. {
  490. /*
  491. * We'll fill in NULLs for the missing values, but we need to
  492. * decrement the counter since this sql result row doesn't
  493. * belong to the current output tuple.
  494. */
  495. call_cntr--;
  496. xpfree(rowid);
  497. break;
  498. }
  499. }
  500. if (!skip_tuple)
  501. {
  502. HeapTuple tuple;
  503. /* build the tuple and store it */
  504. tuple = BuildTupleFromCStrings(attinmeta, values);
  505. tuplestore_puttuple(tupstore, tuple);
  506. heap_freetuple(tuple);
  507. }
  508. /* Remember current rowid */
  509. xpfree(lastrowid);
  510. xpstrdup(lastrowid, values[0]);
  511. firstpass = false;
  512. /* Clean up */
  513. for (i = 0; i < num_categories + 1; i++)
  514. if (values[i] != NULL)
  515. pfree(values[i]);
  516. pfree(values);
  517. }
  518. /* let the caller know we're sending back a tuplestore */
  519. rsinfo->returnMode = SFRM_Materialize;
  520. rsinfo->setResult = tupstore;
  521. rsinfo->setDesc = tupdesc;
  522. /* release SPI related resources (and return to caller's context) */
  523. SPI_finish();
  524. return (Datum) 0;
  525. }
  526. /*
  527. * crosstab_hash - reimplement crosstab as materialized function and
  528. * properly deal with missing values (i.e. don't pack remaining
  529. * values to the left)
  530. *
  531. * crosstab - create a crosstab of rowids and values columns from a
  532. * SQL statement returning one rowid column, one category column,
  533. * and one value column.
  534. *
  535. * e.g. given sql which produces:
  536. *
  537. * rowid cat value
  538. * ------+-------+-------
  539. * row1 cat1 val1
  540. * row1 cat2 val2
  541. * row1 cat4 val4
  542. * row2 cat1 val5
  543. * row2 cat2 val6
  544. * row2 cat3 val7
  545. * row2 cat4 val8
  546. *
  547. * crosstab returns:
  548. * <===== values columns =====>
  549. * rowid cat1 cat2 cat3 cat4
  550. * ------+-------+-------+-------+-------
  551. * row1 val1 val2 null val4
  552. * row2 val5 val6 val7 val8
  553. *
  554. * NOTES:
  555. * 1. SQL result must be ordered by 1.
  556. * 2. The number of values columns depends on the tuple description
  557. * of the function's declared return type.
  558. * 3. Missing values (i.e. missing category) are filled in with nulls.
  559. * 4. Extra values (i.e. not in category results) are skipped.
  560. */
  561. PG_FUNCTION_INFO_V1(crosstab_hash);
  562. Datum
  563. crosstab_hash(PG_FUNCTION_ARGS)
  564. {
  565. char *sql = text_to_cstring(PG_GETARG_TEXT_PP(0));
  566. char *cats_sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
  567. ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  568. TupleDesc tupdesc;
  569. MemoryContext per_query_ctx;
  570. MemoryContext oldcontext;
  571. HTAB *crosstab_hash;
  572. /* check to see if caller supports us returning a tuplestore */
  573. if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
  574. ereport(ERROR,
  575. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  576. errmsg("set-valued function called in context that cannot accept a set")));
  577. if (!(rsinfo->allowedModes & SFRM_Materialize) ||
  578. rsinfo->expectedDesc == NULL)
  579. ereport(ERROR,
  580. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  581. errmsg("materialize mode required, but it is not " \
  582. "allowed in this context")));
  583. per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
  584. oldcontext = MemoryContextSwitchTo(per_query_ctx);
  585. /* get the requested return tuple description */
  586. tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
  587. /*
  588. * Check to make sure we have a reasonable tuple descriptor
  589. *
  590. * Note we will attempt to coerce the values into whatever the return
  591. * attribute type is and depend on the "in" function to complain if
  592. * needed.
  593. */
  594. if (tupdesc->natts < 2)
  595. ereport(ERROR,
  596. (errcode(ERRCODE_SYNTAX_ERROR),
  597. errmsg("query-specified return tuple and " \
  598. "crosstab function are not compatible")));
  599. /* load up the categories hash table */
  600. crosstab_hash = load_categories_hash(cats_sql, per_query_ctx);
  601. /* let the caller know we're sending back a tuplestore */
  602. rsinfo->returnMode = SFRM_Materialize;
  603. /* now go build it */
  604. rsinfo->setResult = get_crosstab_tuplestore(sql,
  605. crosstab_hash,
  606. tupdesc,
  607. per_query_ctx,
  608. rsinfo->allowedModes & SFRM_Materialize_Random);
  609. /*
  610. * SFRM_Materialize mode expects us to return a NULL Datum. The actual
  611. * tuples are in our tuplestore and passed back through rsinfo->setResult.
  612. * rsinfo->setDesc is set to the tuple description that we actually used
  613. * to build our tuples with, so the caller can verify we did what it was
  614. * expecting.
  615. */
  616. rsinfo->setDesc = tupdesc;
  617. MemoryContextSwitchTo(oldcontext);
  618. return (Datum) 0;
  619. }
  620. /*
  621. * load up the categories hash table
  622. */
  623. static HTAB *
  624. load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
  625. {
  626. HTAB *crosstab_hash;
  627. HASHCTL ctl;
  628. int ret;
  629. uint64 proc;
  630. MemoryContext SPIcontext;
  631. /* initialize the category hash table */
  632. MemSet(&ctl, 0, sizeof(ctl));
  633. ctl.keysize = MAX_CATNAME_LEN;
  634. ctl.entrysize = sizeof(crosstab_HashEnt);
  635. ctl.hcxt = per_query_ctx;
  636. /*
  637. * use INIT_CATS, defined above as a guess of how many hash table entries
  638. * to create, initially
  639. */
  640. crosstab_hash = hash_create("crosstab hash",
  641. INIT_CATS,
  642. &ctl,
  643. HASH_ELEM | HASH_CONTEXT);
  644. /* Connect to SPI manager */
  645. if ((ret = SPI_connect()) < 0)
  646. /* internal error */
  647. elog(ERROR, "load_categories_hash: SPI_connect returned %d", ret);
  648. /* Retrieve the category name rows */
  649. ret = SPI_execute(cats_sql, true, 0);
  650. proc = SPI_processed;
  651. /* Check for qualifying tuples */
  652. if ((ret == SPI_OK_SELECT) && (proc > 0))
  653. {
  654. SPITupleTable *spi_tuptable = SPI_tuptable;
  655. TupleDesc spi_tupdesc = spi_tuptable->tupdesc;
  656. uint64 i;
  657. /*
  658. * The provided categories SQL query must always return one column:
  659. * category - the label or identifier for each column
  660. */
  661. if (spi_tupdesc->natts != 1)
  662. ereport(ERROR,
  663. (errcode(ERRCODE_SYNTAX_ERROR),
  664. errmsg("provided \"categories\" SQL must " \
  665. "return 1 column of at least one row")));
  666. for (i = 0; i < proc; i++)
  667. {
  668. crosstab_cat_desc *catdesc;
  669. char *catname;
  670. HeapTuple spi_tuple;
  671. /* get the next sql result tuple */
  672. spi_tuple = spi_tuptable->vals[i];
  673. /* get the category from the current sql result tuple */
  674. catname = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
  675. SPIcontext = MemoryContextSwitchTo(per_query_ctx);
  676. catdesc = (crosstab_cat_desc *) palloc(sizeof(crosstab_cat_desc));
  677. catdesc->catname = catname;
  678. catdesc->attidx = i;
  679. /* Add the proc description block to the hashtable */
  680. crosstab_HashTableInsert(crosstab_hash, catdesc);
  681. MemoryContextSwitchTo(SPIcontext);
  682. }
  683. }
  684. if (SPI_finish() != SPI_OK_FINISH)
  685. /* internal error */
  686. elog(ERROR, "load_categories_hash: SPI_finish() failed");
  687. return crosstab_hash;
  688. }
  689. /*
  690. * create and populate the crosstab tuplestore using the provided source query
  691. */
  692. static Tuplestorestate *
  693. get_crosstab_tuplestore(char *sql,
  694. HTAB *crosstab_hash,
  695. TupleDesc tupdesc,
  696. MemoryContext per_query_ctx,
  697. bool randomAccess)
  698. {
  699. Tuplestorestate *tupstore;
  700. int num_categories = hash_get_num_entries(crosstab_hash);
  701. AttInMetadata *attinmeta = TupleDescGetAttInMetadata(tupdesc);
  702. char **values;
  703. HeapTuple tuple;
  704. int ret;
  705. uint64 proc;
  706. /* initialize our tuplestore (while still in query context!) */
  707. tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
  708. /* Connect to SPI manager */
  709. if ((ret = SPI_connect()) < 0)
  710. /* internal error */
  711. elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret);
  712. /* Now retrieve the crosstab source rows */
  713. ret = SPI_execute(sql, true, 0);
  714. proc = SPI_processed;
  715. /* Check for qualifying tuples */
  716. if ((ret == SPI_OK_SELECT) && (proc > 0))
  717. {
  718. SPITupleTable *spi_tuptable = SPI_tuptable;
  719. TupleDesc spi_tupdesc = spi_tuptable->tupdesc;
  720. int ncols = spi_tupdesc->natts;
  721. char *rowid;
  722. char *lastrowid = NULL;
  723. bool firstpass = true;
  724. uint64 i;
  725. int j;
  726. int result_ncols;
  727. if (num_categories == 0)
  728. {
  729. /* no qualifying category tuples */
  730. ereport(ERROR,
  731. (errcode(ERRCODE_SYNTAX_ERROR),
  732. errmsg("provided \"categories\" SQL must " \
  733. "return 1 column of at least one row")));
  734. }
  735. /*
  736. * The provided SQL query must always return at least three columns:
  737. *
  738. * 1. rowname the label for each row - column 1 in the final result
  739. * 2. category the label for each value-column in the final result 3.
  740. * value the values used to populate the value-columns
  741. *
  742. * If there are more than three columns, the last two are taken as
  743. * "category" and "values". The first column is taken as "rowname".
  744. * Additional columns (2 thru N-2) are assumed the same for the same
  745. * "rowname", and are copied into the result tuple from the first time
  746. * we encounter a particular rowname.
  747. */
  748. if (ncols < 3)
  749. ereport(ERROR,
  750. (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  751. errmsg("invalid source data SQL statement"),
  752. errdetail("The provided SQL must return 3 " \
  753. " columns; rowid, category, and values.")));
  754. result_ncols = (ncols - 2) + num_categories;
  755. /* Recheck to make sure we tuple descriptor still looks reasonable */
  756. if (tupdesc->natts != result_ncols)
  757. ereport(ERROR,
  758. (errcode(ERRCODE_SYNTAX_ERROR),
  759. errmsg("invalid return type"),
  760. errdetail("Query-specified return " \
  761. "tuple has %d columns but crosstab " \
  762. "returns %d.", tupdesc->natts, result_ncols)));
  763. /* allocate space */
  764. values = (char **) palloc(result_ncols * sizeof(char *));
  765. /* and make sure it's clear */
  766. memset(values, '\0', result_ncols * sizeof(char *));
  767. for (i = 0; i < proc; i++)
  768. {
  769. HeapTuple spi_tuple;
  770. crosstab_cat_desc *catdesc;
  771. char *catname;
  772. /* get the next sql result tuple */
  773. spi_tuple = spi_tuptable->vals[i];
  774. /* get the rowid from the current sql result tuple */
  775. rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
  776. /*
  777. * if we're on a new output row, grab the column values up to
  778. * column N-2 now
  779. */
  780. if (firstpass || !xstreq(lastrowid, rowid))
  781. {
  782. /*
  783. * a new row means we need to flush the old one first, unless
  784. * we're on the very first row
  785. */
  786. if (!firstpass)
  787. {
  788. /* rowid changed, flush the previous output row */
  789. tuple = BuildTupleFromCStrings(attinmeta, values);
  790. tuplestore_puttuple(tupstore, tuple);
  791. for (j = 0; j < result_ncols; j++)
  792. xpfree(values[j]);
  793. }
  794. values[0] = rowid;
  795. for (j = 1; j < ncols - 2; j++)
  796. values[j] = SPI_getvalue(spi_tuple, spi_tupdesc, j + 1);
  797. /* we're no longer on the first pass */
  798. firstpass = false;
  799. }
  800. /* look up the category and fill in the appropriate column */
  801. catname = SPI_getvalue(spi_tuple, spi_tupdesc, ncols - 1);
  802. if (catname != NULL)
  803. {
  804. crosstab_HashTableLookup(crosstab_hash, catname, catdesc);
  805. if (catdesc)
  806. values[catdesc->attidx + ncols - 2] =
  807. SPI_getvalue(spi_tuple, spi_tupdesc, ncols);
  808. }
  809. xpfree(lastrowid);
  810. xpstrdup(lastrowid, rowid);
  811. }
  812. /* flush the last output row */
  813. tuple = BuildTupleFromCStrings(attinmeta, values);
  814. tuplestore_puttuple(tupstore, tuple);
  815. }
  816. if (SPI_finish() != SPI_OK_FINISH)
  817. /* internal error */
  818. elog(ERROR, "get_crosstab_tuplestore: SPI_finish() failed");
  819. tuplestore_donestoring(tupstore);
  820. return tupstore;
  821. }
  822. /*
  823. * connectby_text - produce a result set from a hierarchical (parent/child)
  824. * table.
  825. *
  826. * e.g. given table foo:
  827. *
  828. * keyid parent_keyid pos
  829. * ------+------------+--
  830. * row1 NULL 0
  831. * row2 row1 0
  832. * row3 row1 0
  833. * row4 row2 1
  834. * row5 row2 0
  835. * row6 row4 0
  836. * row7 row3 0
  837. * row8 row6 0
  838. * row9 row5 0
  839. *
  840. *
  841. * connectby(text relname, text keyid_fld, text parent_keyid_fld
  842. * [, text orderby_fld], text start_with, int max_depth
  843. * [, text branch_delim])
  844. * connectby('foo', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') returns:
  845. *
  846. * keyid parent_id level branch serial
  847. * ------+-----------+--------+-----------------------
  848. * row2 NULL 0 row2 1
  849. * row5 row2 1 row2~row5 2
  850. * row9 row5 2 row2~row5~row9 3
  851. * row4 row2 1 row2~row4 4
  852. * row6 row4 2 row2~row4~row6 5
  853. * row8 row6 3 row2~row4~row6~row8 6
  854. *
  855. */
  856. PG_FUNCTION_INFO_V1(connectby_text);
  857. #define CONNECTBY_NCOLS 4
  858. #define CONNECTBY_NCOLS_NOBRANCH 3
  859. Datum
  860. connectby_text(PG_FUNCTION_ARGS)
  861. {
  862. char *relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
  863. char *key_fld = text_to_cstring(PG_GETARG_TEXT_PP(1));
  864. char *parent_key_fld = text_to_cstring(PG_GETARG_TEXT_PP(2));
  865. char *start_with = text_to_cstring(PG_GETARG_TEXT_PP(3));
  866. int max_depth = PG_GETARG_INT32(4);
  867. char *branch_delim = NULL;
  868. bool show_branch = false;
  869. bool show_serial = false;
  870. ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  871. TupleDesc tupdesc;
  872. AttInMetadata *attinmeta;
  873. MemoryContext per_query_ctx;
  874. MemoryContext oldcontext;
  875. /* check to see if caller supports us returning a tuplestore */
  876. if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
  877. ereport(ERROR,
  878. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  879. errmsg("set-valued function called in context that cannot accept a set")));
  880. if (!(rsinfo->allowedModes & SFRM_Materialize) ||
  881. rsinfo->expectedDesc == NULL)
  882. ereport(ERROR,
  883. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  884. errmsg("materialize mode required, but it is not " \
  885. "allowed in this context")));
  886. if (fcinfo->nargs == 6)
  887. {
  888. branch_delim = text_to_cstring(PG_GETARG_TEXT_PP(5));
  889. show_branch = true;
  890. }
  891. else
  892. /* default is no show, tilde for the delimiter */
  893. branch_delim = pstrdup("~");
  894. per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
  895. oldcontext = MemoryContextSwitchTo(per_query_ctx);
  896. /* get the requested return tuple description */
  897. tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
  898. /* does it meet our needs */
  899. validateConnectbyTupleDesc(tupdesc, show_branch, show_serial);
  900. /* OK, use it then */
  901. attinmeta = TupleDescGetAttInMetadata(tupdesc);
  902. /* OK, go to work */
  903. rsinfo->returnMode = SFRM_Materialize;
  904. rsinfo->setResult = connectby(relname,
  905. key_fld,
  906. parent_key_fld,
  907. NULL,
  908. branch_delim,
  909. start_with,
  910. max_depth,
  911. show_branch,
  912. show_serial,
  913. per_query_ctx,
  914. rsinfo->allowedModes & SFRM_Materialize_Random,
  915. attinmeta);
  916. rsinfo->setDesc = tupdesc;
  917. MemoryContextSwitchTo(oldcontext);
  918. /*
  919. * SFRM_Materialize mode expects us to return a NULL Datum. The actual
  920. * tuples are in our tuplestore and passed back through rsinfo->setResult.
  921. * rsinfo->setDesc is set to the tuple description that we actually used
  922. * to build our tuples with, so the caller can verify we did what it was
  923. * expecting.
  924. */
  925. return (Datum) 0;
  926. }
  927. PG_FUNCTION_INFO_V1(connectby_text_serial);
  928. Datum
  929. connectby_text_serial(PG_FUNCTION_ARGS)
  930. {
  931. char *relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
  932. char *key_fld = text_to_cstring(PG_GETARG_TEXT_PP(1));
  933. char *parent_key_fld = text_to_cstring(PG_GETARG_TEXT_PP(2));
  934. char *orderby_fld = text_to_cstring(PG_GETARG_TEXT_PP(3));
  935. char *start_with = text_to_cstring(PG_GETARG_TEXT_PP(4));
  936. int max_depth = PG_GETARG_INT32(5);
  937. char *branch_delim = NULL;
  938. bool show_branch = false;
  939. bool show_serial = true;
  940. ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  941. TupleDesc tupdesc;
  942. AttInMetadata *attinmeta;
  943. MemoryContext per_query_ctx;
  944. MemoryContext oldcontext;
  945. /* check to see if caller supports us returning a tuplestore */
  946. if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
  947. ereport(ERROR,
  948. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  949. errmsg("set-valued function called in context that cannot accept a set")));
  950. if (!(rsinfo->allowedModes & SFRM_Materialize) ||
  951. rsinfo->expectedDesc == NULL)
  952. ereport(ERROR,
  953. (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  954. errmsg("materialize mode required, but it is not " \
  955. "allowed in this context")));
  956. if (fcinfo->nargs == 7)
  957. {
  958. branch_delim = text_to_cstring(PG_GETARG_TEXT_PP(6));
  959. show_branch = true;
  960. }
  961. else
  962. /* default is no show, tilde for the delimiter */
  963. branch_delim = pstrdup("~");
  964. per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
  965. oldcontext = MemoryContextSwitchTo(per_query_ctx);
  966. /* get the requested return tuple description */
  967. tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
  968. /* does it meet our needs */
  969. validateConnectbyTupleDesc(tupdesc, show_branch, show_serial);
  970. /* OK, use it then */
  971. attinmeta = TupleDescGetAttInMetadata(tupdesc);
  972. /* OK, go to work */
  973. rsinfo->returnMode = SFRM_Materialize;
  974. rsinfo->setResult = connectby(relname,
  975. key_fld,
  976. parent_key_fld,
  977. orderby_fld,
  978. branch_delim,
  979. start_with,
  980. max_depth,
  981. show_branch,
  982. show_serial,
  983. per_query_ctx,
  984. rsinfo->allowedModes & SFRM_Materialize_Random,
  985. attinmeta);
  986. rsinfo->setDesc = tupdesc;
  987. MemoryContextSwitchTo(oldcontext);
  988. /*
  989. * SFRM_Materialize mode expects us to return a NULL Datum. The actual
  990. * tuples are in our tuplestore and passed back through rsinfo->setResult.
  991. * rsinfo->setDesc is set to the tuple description that we actually used
  992. * to build our tuples with, so the caller can verify we did what it was
  993. * expecting.
  994. */
  995. return (Datum) 0;
  996. }
  997. /*
  998. * connectby - does the real work for connectby_text()
  999. */
  1000. static Tuplestorestate *
  1001. connectby(char *relname,
  1002. char *key_fld,
  1003. char *parent_key_fld,
  1004. char *orderby_fld,
  1005. char *branch_delim,
  1006. char *start_with,
  1007. int max_depth,
  1008. bool show_branch,
  1009. bool show_serial,
  1010. MemoryContext per_query_ctx,
  1011. bool randomAccess,
  1012. AttInMetadata *attinmeta)
  1013. {
  1014. Tuplestorestate *tupstore = NULL;
  1015. int ret;
  1016. MemoryContext oldcontext;
  1017. int serial = 1;
  1018. /* Connect to SPI manager */
  1019. if ((ret = SPI_connect()) < 0)
  1020. /* internal error */
  1021. elog(ERROR, "connectby: SPI_connect returned %d", ret);
  1022. /* switch to longer term context to create the tuple store */
  1023. oldcontext = MemoryContextSwitchTo(per_query_ctx);
  1024. /* initialize our tuplestore */
  1025. tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
  1026. MemoryContextSwitchTo(oldcontext);
  1027. /* now go get the whole tree */
  1028. build_tuplestore_recursively(key_fld,
  1029. parent_key_fld,
  1030. relname,
  1031. orderby_fld,
  1032. branch_delim,
  1033. start_with,
  1034. start_with, /* current_branch */
  1035. 0, /* initial level is 0 */
  1036. &serial, /* initial serial is 1 */
  1037. max_depth,
  1038. show_branch,
  1039. show_serial,
  1040. per_query_ctx,
  1041. attinmeta,
  1042. tupstore);
  1043. SPI_finish();
  1044. return tupstore;
  1045. }
  1046. static void
  1047. build_tuplestore_recursively(char *key_fld,
  1048. char *parent_key_fld,
  1049. char *relname,
  1050. char *orderby_fld,
  1051. char *branch_delim,
  1052. char *start_with,
  1053. char *branch,
  1054. int level,
  1055. int *serial,
  1056. int max_depth,
  1057. bool show_branch,
  1058. bool show_serial,
  1059. MemoryContext per_query_ctx,
  1060. AttInMetadata *attinmeta,
  1061. Tuplestorestate *tupstore)
  1062. {
  1063. TupleDesc tupdesc = attinmeta->tupdesc;
  1064. int ret;
  1065. uint64 proc;
  1066. int serial_column;
  1067. StringInfoData sql;
  1068. char **values;
  1069. char *current_key;
  1070. char *current_key_parent;
  1071. char current_level[INT32_STRLEN];
  1072. char serial_str[INT32_STRLEN];
  1073. char *current_branch;
  1074. HeapTuple tuple;
  1075. if (max_depth > 0 && level > max_depth)
  1076. return;
  1077. initStringInfo(&sql);
  1078. /* Build initial sql statement */
  1079. if (!show_serial)
  1080. {
  1081. appendStringInfo(&sql, "SELECT %s, %s FROM %s WHERE %s = %s AND %s IS NOT NULL AND %s <> %s",
  1082. key_fld,
  1083. parent_key_fld,
  1084. relname,
  1085. parent_key_fld,
  1086. quote_literal_cstr(start_with),
  1087. key_fld, key_fld, parent_key_fld);
  1088. serial_column = 0;
  1089. }
  1090. else
  1091. {
  1092. appendStringInfo(&sql, "SELECT %s, %s FROM %s WHERE %s = %s AND %s IS NOT NULL AND %s <> %s ORDER BY %s",
  1093. key_fld,
  1094. parent_key_fld,
  1095. relname,
  1096. parent_key_fld,
  1097. quote_literal_cstr(start_with),
  1098. key_fld, key_fld, parent_key_fld,
  1099. orderby_fld);
  1100. serial_column = 1;
  1101. }
  1102. if (show_branch)
  1103. values = (char **) palloc((CONNECTBY_NCOLS + serial_column) * sizeof(char *));
  1104. else
  1105. values = (char **) palloc((CONNECTBY_NCOLS_NOBRANCH + serial_column) * sizeof(char *));
  1106. /* First time through, do a little setup */
  1107. if (level == 0)
  1108. {
  1109. /* root value is the one we initially start with */
  1110. values[0] = start_with;
  1111. /* root value has no parent */
  1112. values[1] = NULL;
  1113. /* root level is 0 */
  1114. sprintf(current_level, "%d", level);
  1115. values[2] = current_level;
  1116. /* root branch is just starting root value */
  1117. if (show_branch)
  1118. values[3] = start_with;
  1119. /* root starts the serial with 1 */
  1120. if (show_serial)
  1121. {
  1122. sprintf(serial_str, "%d", (*serial)++);
  1123. if (show_branch)
  1124. values[4] = serial_str;
  1125. else
  1126. values[3] = serial_str;
  1127. }
  1128. /* construct the tuple */
  1129. tuple = BuildTupleFromCStrings(attinmeta, values);
  1130. /* now store it */
  1131. tuplestore_puttuple(tupstore, tuple);
  1132. /* increment level */
  1133. level++;
  1134. }
  1135. /* Retrieve the desired rows */
  1136. ret = SPI_execute(sql.data, true, 0);
  1137. proc = SPI_processed;
  1138. /* Check for qualifying tuples */
  1139. if ((ret == SPI_OK_SELECT) && (proc > 0))
  1140. {
  1141. HeapTuple spi_tuple;
  1142. SPITupleTable *tuptable = SPI_tuptable;
  1143. TupleDesc spi_tupdesc = tuptable->tupdesc;
  1144. uint64 i;
  1145. StringInfoData branchstr;
  1146. StringInfoData chk_branchstr;
  1147. StringInfoData chk_current_key;
  1148. /*
  1149. * Check that return tupdesc is compatible with the one we got from
  1150. * the query.
  1151. */
  1152. compatConnectbyTupleDescs(tupdesc, spi_tupdesc);
  1153. initStringInfo(&branchstr);
  1154. initStringInfo(&chk_branchstr);
  1155. initStringInfo(&chk_current_key);
  1156. for (i = 0; i < proc; i++)
  1157. {
  1158. /* initialize branch for this pass */
  1159. appendStringInfoString(&branchstr, branch);
  1160. appendStringInfo(&chk_branchstr, "%s%s%s", branch_delim, branch, branch_delim);
  1161. /* get the next sql result tuple */
  1162. spi_tuple = tuptable->vals[i];
  1163. /* get the current key (might be NULL) */
  1164. current_key = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
  1165. /* get the parent key (might be NULL) */
  1166. current_key_parent = SPI_getvalue(spi_tuple, spi_tupdesc, 2);
  1167. /* get the current level */
  1168. sprintf(current_level, "%d", level);
  1169. /* check to see if this key is also an ancestor */
  1170. if (current_key)
  1171. {
  1172. appendStringInfo(&chk_current_key, "%s%s%s",
  1173. branch_delim, current_key, branch_delim);
  1174. if (strstr(chk_branchstr.data, chk_current_key.data))
  1175. ereport(ERROR,
  1176. (errcode(ERRCODE_INVALID_RECURSION),
  1177. errmsg("infinite recursion detected")));
  1178. }
  1179. /* OK, extend the branch */
  1180. if (current_key)
  1181. appendStringInfo(&branchstr, "%s%s", branch_delim, current_key);
  1182. current_branch = branchstr.data;
  1183. /* build a tuple */
  1184. values[0] = current_key;
  1185. values[1] = current_key_parent;
  1186. values[2] = current_level;
  1187. if (show_branch)
  1188. values[3] = current_branch;
  1189. if (show_serial)
  1190. {
  1191. sprintf(serial_str, "%d", (*serial)++);
  1192. if (show_branch)
  1193. values[4] = serial_str;
  1194. else
  1195. values[3] = serial_str;
  1196. }
  1197. tuple = BuildTupleFromCStrings(attinmeta, values);
  1198. /* store the tuple for later use */
  1199. tuplestore_puttuple(tupstore, tuple);
  1200. heap_freetuple(tuple);
  1201. /* recurse using current_key as the new start_with */
  1202. if (current_key)
  1203. build_tuplestore_recursively(key_fld,
  1204. parent_key_fld,
  1205. relname,
  1206. orderby_fld,
  1207. branch_delim,
  1208. current_key,
  1209. current_branch,
  1210. level + 1,
  1211. serial,
  1212. max_depth,
  1213. show_branch,
  1214. show_serial,
  1215. per_query_ctx,
  1216. attinmeta,
  1217. tupstore);
  1218. xpfree(current_key);
  1219. xpfree(current_key_parent);
  1220. /* reset branch for next pass */
  1221. resetStringInfo(&branchstr);
  1222. resetStringInfo(&chk_branchstr);
  1223. resetStringInfo(&chk_current_key);
  1224. }
  1225. xpfree(branchstr.data);
  1226. xpfree(chk_branchstr.data);
  1227. xpfree(chk_current_key.data);
  1228. }
  1229. }
  1230. /*
  1231. * Check expected (query runtime) tupdesc suitable for Connectby
  1232. */
  1233. static void
  1234. validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial)
  1235. {
  1236. int serial_column = 0;
  1237. if (show_serial)
  1238. serial_column = 1;
  1239. /* are there the correct number of columns */
  1240. if (show_branch)
  1241. {
  1242. if (td->natts != (CONNECTBY_NCOLS + serial_column))
  1243. ereport(ERROR,
  1244. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1245. errmsg("invalid return type"),
  1246. errdetail("Query-specified return tuple has " \
  1247. "wrong number of columns.")));
  1248. }
  1249. else
  1250. {
  1251. if (td->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column)
  1252. ereport(ERROR,
  1253. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1254. errmsg("invalid return type"),
  1255. errdetail("Query-specified return tuple has " \
  1256. "wrong number of columns.")));
  1257. }
  1258. /* check that the types of the first two columns match */
  1259. if (TupleDescAttr(td, 0)->atttypid != TupleDescAttr(td, 1)->atttypid)
  1260. ereport(ERROR,
  1261. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1262. errmsg("invalid return type"),
  1263. errdetail("First two columns must be the same type.")));
  1264. /* check that the type of the third column is INT4 */
  1265. if (TupleDescAttr(td, 2)->atttypid != INT4OID)
  1266. ereport(ERROR,
  1267. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1268. errmsg("invalid return type"),
  1269. errdetail("Third column must be type %s.",
  1270. format_type_be(INT4OID))));
  1271. /* check that the type of the fourth column is TEXT if applicable */
  1272. if (show_branch && TupleDescAttr(td, 3)->atttypid != TEXTOID)
  1273. ereport(ERROR,
  1274. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1275. errmsg("invalid return type"),
  1276. errdetail("Fourth column must be type %s.",
  1277. format_type_be(TEXTOID))));
  1278. /* check that the type of the fifth column is INT4 */
  1279. if (show_branch && show_serial &&
  1280. TupleDescAttr(td, 4)->atttypid != INT4OID)
  1281. ereport(ERROR,
  1282. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1283. errmsg("query-specified return tuple not valid for Connectby: "
  1284. "fifth column must be type %s",
  1285. format_type_be(INT4OID))));
  1286. /* check that the type of the fifth column is INT4 */
  1287. if (!show_branch && show_serial &&
  1288. TupleDescAttr(td, 3)->atttypid != INT4OID)
  1289. ereport(ERROR,
  1290. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1291. errmsg("query-specified return tuple not valid for Connectby: "
  1292. "fourth column must be type %s",
  1293. format_type_be(INT4OID))));
  1294. /* OK, the tupdesc is valid for our purposes */
  1295. }
  1296. /*
  1297. * Check if spi sql tupdesc and return tupdesc are compatible
  1298. */
  1299. static void
  1300. compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
  1301. {
  1302. Oid ret_atttypid;
  1303. Oid sql_atttypid;
  1304. int32 ret_atttypmod;
  1305. int32 sql_atttypmod;
  1306. /*
  1307. * Result must have at least 2 columns.
  1308. */
  1309. if (sql_tupdesc->natts < 2)
  1310. ereport(ERROR,
  1311. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1312. errmsg("invalid return type"),
  1313. errdetail("Query must return at least two columns.")));
  1314. /*
  1315. * These columns must match the result type indicated by the calling
  1316. * query.
  1317. */
  1318. ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid;
  1319. sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid;
  1320. ret_atttypmod = TupleDescAttr(ret_tupdesc, 0)->atttypmod;
  1321. sql_atttypmod = TupleDescAttr(sql_tupdesc, 0)->atttypmod;
  1322. if (ret_atttypid != sql_atttypid ||
  1323. (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
  1324. ereport(ERROR,
  1325. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1326. errmsg("invalid return type"),
  1327. errdetail("SQL key field type %s does " \
  1328. "not match return key field type %s.",
  1329. format_type_with_typemod(ret_atttypid, ret_atttypmod),
  1330. format_type_with_typemod(sql_atttypid, sql_atttypmod))));
  1331. ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid;
  1332. sql_atttypid = TupleDescAttr(sql_tupdesc, 1)->atttypid;
  1333. ret_atttypmod = TupleDescAttr(ret_tupdesc, 1)->atttypmod;
  1334. sql_atttypmod = TupleDescAttr(sql_tupdesc, 1)->atttypmod;
  1335. if (ret_atttypid != sql_atttypid ||
  1336. (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
  1337. ereport(ERROR,
  1338. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1339. errmsg("invalid return type"),
  1340. errdetail("SQL parent key field type %s does " \
  1341. "not match return parent key field type %s.",
  1342. format_type_with_typemod(ret_atttypid, ret_atttypmod),
  1343. format_type_with_typemod(sql_atttypid, sql_atttypmod))));
  1344. /* OK, the two tupdescs are compatible for our purposes */
  1345. }
  1346. /*
  1347. * Check if two tupdescs match in type of attributes
  1348. */
  1349. static bool
  1350. compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
  1351. {
  1352. int i;
  1353. Form_pg_attribute ret_attr;
  1354. Oid ret_atttypid;
  1355. Form_pg_attribute sql_attr;
  1356. Oid sql_atttypid;
  1357. if (ret_tupdesc->natts < 2 ||
  1358. sql_tupdesc->natts < 3)
  1359. return false;
  1360. /* check the rowid types match */
  1361. ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid;
  1362. sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid;
  1363. if (ret_atttypid != sql_atttypid)
  1364. ereport(ERROR,
  1365. (errcode(ERRCODE_DATATYPE_MISMATCH),
  1366. errmsg("invalid return type"),
  1367. errdetail("SQL rowid datatype does not match " \
  1368. "return rowid datatype.")));
  1369. /*
  1370. * - attribute [1] of the sql tuple is the category; no need to check it -
  1371. * attribute [2] of the sql tuple should match attributes [1] to [natts]
  1372. * of the return tuple
  1373. */
  1374. sql_attr = TupleDescAttr(sql_tupdesc, 2);
  1375. for (i = 1; i < ret_tupdesc->natts; i++)
  1376. {
  1377. ret_attr = TupleDescAttr(ret_tupdesc, i);
  1378. if (ret_attr->atttypid != sql_attr->atttypid)
  1379. return false;
  1380. }
  1381. /* OK, the two tupdescs are compatible for our purposes */
  1382. return true;
  1383. }