PostgreSQL Source Code  git master
pg_publication.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * pg_publication.c
4  * publication C API manipulation
5  *
6  * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  * IDENTIFICATION
10  * src/backend/catalog/pg_publication.c
11  *
12  *-------------------------------------------------------------------------
13  */
14 
15 #include "postgres.h"
16 
17 #include "access/genam.h"
18 #include "access/heapam.h"
19 #include "access/htup_details.h"
20 #include "access/tableam.h"
21 #include "access/xact.h"
22 #include "catalog/catalog.h"
23 #include "catalog/dependency.h"
24 #include "catalog/index.h"
25 #include "catalog/indexing.h"
26 #include "catalog/namespace.h"
27 #include "catalog/partition.h"
28 #include "catalog/objectaccess.h"
29 #include "catalog/objectaddress.h"
30 #include "catalog/pg_inherits.h"
31 #include "catalog/pg_namespace.h"
32 #include "catalog/pg_publication.h"
35 #include "catalog/pg_type.h"
37 #include "funcapi.h"
38 #include "miscadmin.h"
39 #include "utils/array.h"
40 #include "utils/builtins.h"
41 #include "utils/catcache.h"
42 #include "utils/fmgroids.h"
43 #include "utils/inval.h"
44 #include "utils/lsyscache.h"
45 #include "utils/rel.h"
46 #include "utils/syscache.h"
47 
48 static void publication_translate_columns(Relation targetrel, List *columns,
49  int *natts, AttrNumber **attrs);
50 
51 /*
52  * Check if relation can be in given publication and throws appropriate
53  * error if not.
54  */
55 static void
57 {
58  /* Must be a regular or partitioned table */
59  if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
60  RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
61  ereport(ERROR,
62  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
63  errmsg("cannot add relation \"%s\" to publication",
64  RelationGetRelationName(targetrel)),
65  errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
66 
67  /* Can't be system table */
68  if (IsCatalogRelation(targetrel))
69  ereport(ERROR,
70  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
71  errmsg("cannot add relation \"%s\" to publication",
72  RelationGetRelationName(targetrel)),
73  errdetail("This operation is not supported for system tables.")));
74 
75  /* UNLOGGED and TEMP relations cannot be part of publication. */
76  if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
77  ereport(ERROR,
78  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
79  errmsg("cannot add relation \"%s\" to publication",
80  RelationGetRelationName(targetrel)),
81  errdetail("This operation is not supported for temporary tables.")));
82  else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
83  ereport(ERROR,
84  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
85  errmsg("cannot add relation \"%s\" to publication",
86  RelationGetRelationName(targetrel)),
87  errdetail("This operation is not supported for unlogged tables.")));
88 }
89 
90 /*
91  * Check if schema can be in given publication and throw appropriate error if
92  * not.
93  */
94 static void
96 {
97  /* Can't be system namespace */
98  if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid))
99  ereport(ERROR,
100  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
101  errmsg("cannot add schema \"%s\" to publication",
102  get_namespace_name(schemaid)),
103  errdetail("This operation is not supported for system schemas.")));
104 
105  /* Can't be temporary namespace */
106  if (isAnyTempNamespace(schemaid))
107  ereport(ERROR,
108  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
109  errmsg("cannot add schema \"%s\" to publication",
110  get_namespace_name(schemaid)),
111  errdetail("Temporary schemas cannot be replicated.")));
112 }
113 
114 /*
115  * Returns if relation represented by oid and Form_pg_class entry
116  * is publishable.
117  *
118  * Does same checks as check_publication_add_relation() above, but does not
119  * need relation to be opened and also does not throw errors.
120  *
121  * XXX This also excludes all tables with relid < FirstNormalObjectId,
122  * ie all tables created during initdb. This mainly affects the preinstalled
123  * information_schema. IsCatalogRelationOid() only excludes tables with
124  * relid < FirstUnpinnedObjectId, making that test rather redundant,
125  * but really we should get rid of the FirstNormalObjectId test not
126  * IsCatalogRelationOid. We can't do so today because we don't want
127  * information_schema tables to be considered publishable; but this test
128  * is really inadequate for that, since the information_schema could be
129  * dropped and reloaded and then it'll be considered publishable. The best
130  * long-term solution may be to add a "relispublishable" bool to pg_class,
131  * and depend on that instead of OID checks.
132  */
133 static bool
135 {
136  return (reltuple->relkind == RELKIND_RELATION ||
137  reltuple->relkind == RELKIND_PARTITIONED_TABLE) &&
138  !IsCatalogRelationOid(relid) &&
139  reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
140  relid >= FirstNormalObjectId;
141 }
142 
143 /*
144  * Another variant of is_publishable_class(), taking a Relation.
145  */
146 bool
148 {
149  return is_publishable_class(RelationGetRelid(rel), rel->rd_rel);
150 }
151 
152 /*
153  * SQL-callable variant of the above
154  *
155  * This returns null when the relation does not exist. This is intended to be
156  * used for example in psql to avoid gratuitous errors when there are
157  * concurrent catalog changes.
158  */
159 Datum
161 {
162  Oid relid = PG_GETARG_OID(0);
163  HeapTuple tuple;
164  bool result;
165 
166  tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
167  if (!HeapTupleIsValid(tuple))
168  PG_RETURN_NULL();
169  result = is_publishable_class(relid, (Form_pg_class) GETSTRUCT(tuple));
170  ReleaseSysCache(tuple);
171  PG_RETURN_BOOL(result);
172 }
173 
174 /*
175  * Filter out the partitions whose parent tables were also specified in
176  * the publication.
177  */
178 static List *
180 {
181  List *result = NIL;
182  ListCell *lc;
183  ListCell *lc2;
184 
185  foreach(lc, relids)
186  {
187  bool skip = false;
188  List *ancestors = NIL;
189  Oid relid = lfirst_oid(lc);
190 
191  if (get_rel_relispartition(relid))
192  ancestors = get_partition_ancestors(relid);
193 
194  foreach(lc2, ancestors)
195  {
196  Oid ancestor = lfirst_oid(lc2);
197 
198  /* Check if the parent table exists in the published table list. */
199  if (list_member_oid(relids, ancestor))
200  {
201  skip = true;
202  break;
203  }
204  }
205 
206  if (!skip)
207  result = lappend_oid(result, relid);
208  }
209 
210  return result;
211 }
212 
213 /*
214  * Returns true if any schema is associated with the publication, false if no
215  * schema is associated with the publication.
216  */
217 bool
219 {
220  Relation pubschsrel;
221  ScanKeyData scankey;
222  SysScanDesc scan;
223  HeapTuple tup;
224  bool result = false;
225 
226  pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
227  ScanKeyInit(&scankey,
228  Anum_pg_publication_namespace_pnpubid,
229  BTEqualStrategyNumber, F_OIDEQ,
230  ObjectIdGetDatum(pubid));
231 
232  scan = systable_beginscan(pubschsrel,
233  PublicationNamespacePnnspidPnpubidIndexId,
234  true, NULL, 1, &scankey);
235  tup = systable_getnext(scan);
236  result = HeapTupleIsValid(tup);
237 
238  systable_endscan(scan);
239  table_close(pubschsrel, AccessShareLock);
240 
241  return result;
242 }
243 
244 /*
245  * Gets the relations based on the publication partition option for a specified
246  * relation.
247  */
248 List *
250  Oid relid)
251 {
252  if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
253  pub_partopt != PUBLICATION_PART_ROOT)
254  {
255  List *all_parts = find_all_inheritors(relid, NoLock,
256  NULL);
257 
258  if (pub_partopt == PUBLICATION_PART_ALL)
259  result = list_concat(result, all_parts);
260  else if (pub_partopt == PUBLICATION_PART_LEAF)
261  {
262  ListCell *lc;
263 
264  foreach(lc, all_parts)
265  {
266  Oid partOid = lfirst_oid(lc);
267 
268  if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
269  result = lappend_oid(result, partOid);
270  }
271  }
272  else
273  Assert(false);
274  }
275  else
276  result = lappend_oid(result, relid);
277 
278  return result;
279 }
280 
281 /*
282  * Returns the relid of the topmost ancestor that is published via this
283  * publication if any and set its ancestor level to ancestor_level,
284  * otherwise returns InvalidOid.
285  *
286  * The ancestor_level value allows us to compare the results for multiple
287  * publications, and decide which value is higher up.
288  *
289  * Note that the list of ancestors should be ordered such that the topmost
290  * ancestor is at the end of the list.
291  */
292 Oid
293 GetTopMostAncestorInPublication(Oid puboid, List *ancestors, int *ancestor_level)
294 {
295  ListCell *lc;
296  Oid topmost_relid = InvalidOid;
297  int level = 0;
298 
299  /*
300  * Find the "topmost" ancestor that is in this publication.
301  */
302  foreach(lc, ancestors)
303  {
304  Oid ancestor = lfirst_oid(lc);
305  List *apubids = GetRelationPublications(ancestor);
306  List *aschemaPubids = NIL;
307 
308  level++;
309 
310  if (list_member_oid(apubids, puboid))
311  {
312  topmost_relid = ancestor;
313 
314  if (ancestor_level)
315  *ancestor_level = level;
316  }
317  else
318  {
319  aschemaPubids = GetSchemaPublications(get_rel_namespace(ancestor));
320  if (list_member_oid(aschemaPubids, puboid))
321  {
322  topmost_relid = ancestor;
323 
324  if (ancestor_level)
325  *ancestor_level = level;
326  }
327  }
328 
329  list_free(apubids);
330  list_free(aschemaPubids);
331  }
332 
333  return topmost_relid;
334 }
335 
336 /*
337  * Insert new publication / relation mapping.
338  */
341  bool if_not_exists)
342 {
343  Relation rel;
344  HeapTuple tup;
345  Datum values[Natts_pg_publication_rel];
346  bool nulls[Natts_pg_publication_rel];
347  Relation targetrel = pri->relation;
348  Oid relid = RelationGetRelid(targetrel);
349  Oid pubreloid;
350  Publication *pub = GetPublication(pubid);
351  AttrNumber *attarray = NULL;
352  int natts = 0;
353  ObjectAddress myself,
354  referenced;
355  List *relids = NIL;
356 
357  rel = table_open(PublicationRelRelationId, RowExclusiveLock);
358 
359  /*
360  * Check for duplicates. Note that this does not really prevent
361  * duplicates, it's here just to provide nicer error message in common
362  * case. The real protection is the unique key on the catalog.
363  */
365  ObjectIdGetDatum(pubid)))
366  {
368 
369  if (if_not_exists)
370  return InvalidObjectAddress;
371 
372  ereport(ERROR,
374  errmsg("relation \"%s\" is already member of publication \"%s\"",
375  RelationGetRelationName(targetrel), pub->name)));
376  }
377 
379 
380  /*
381  * Translate column names to attnums and make sure the column list
382  * contains only allowed elements (no system or generated columns etc.).
383  * Also build an array of attnums, for storing in the catalog.
384  */
386  &natts, &attarray);
387 
388  /* Form a tuple. */
389  memset(values, 0, sizeof(values));
390  memset(nulls, false, sizeof(nulls));
391 
392  pubreloid = GetNewOidWithIndex(rel, PublicationRelObjectIndexId,
393  Anum_pg_publication_rel_oid);
394  values[Anum_pg_publication_rel_oid - 1] = ObjectIdGetDatum(pubreloid);
395  values[Anum_pg_publication_rel_prpubid - 1] =
396  ObjectIdGetDatum(pubid);
397  values[Anum_pg_publication_rel_prrelid - 1] =
398  ObjectIdGetDatum(relid);
399 
400  /* Add qualifications, if available */
401  if (pri->whereClause != NULL)
402  values[Anum_pg_publication_rel_prqual - 1] = CStringGetTextDatum(nodeToString(pri->whereClause));
403  else
404  nulls[Anum_pg_publication_rel_prqual - 1] = true;
405 
406  /* Add column list, if available */
407  if (pri->columns)
408  values[Anum_pg_publication_rel_prattrs - 1] = PointerGetDatum(buildint2vector(attarray, natts));
409  else
410  nulls[Anum_pg_publication_rel_prattrs - 1] = true;
411 
412  tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
413 
414  /* Insert tuple into catalog. */
415  CatalogTupleInsert(rel, tup);
416  heap_freetuple(tup);
417 
418  /* Register dependencies as needed */
419  ObjectAddressSet(myself, PublicationRelRelationId, pubreloid);
420 
421  /* Add dependency on the publication */
422  ObjectAddressSet(referenced, PublicationRelationId, pubid);
423  recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
424 
425  /* Add dependency on the relation */
426  ObjectAddressSet(referenced, RelationRelationId, relid);
427  recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
428 
429  /* Add dependency on the objects mentioned in the qualifications */
430  if (pri->whereClause)
431  recordDependencyOnSingleRelExpr(&myself, pri->whereClause, relid,
433  false);
434 
435  /* Add dependency on the columns, if any are listed */
436  for (int i = 0; i < natts; i++)
437  {
438  ObjectAddressSubSet(referenced, RelationRelationId, relid, attarray[i]);
439  recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
440  }
441 
442  /* Close the table. */
444 
445  /*
446  * Invalidate relcache so that publication info is rebuilt.
447  *
448  * For the partitioned tables, we must invalidate all partitions contained
449  * in the respective partition hierarchies, not just the one explicitly
450  * mentioned in the publication. This is required because we implicitly
451  * publish the child tables when the parent table is published.
452  */
454  relid);
455 
457 
458  return myself;
459 }
460 
461 /* qsort comparator for attnums */
462 static int
463 compare_int16(const void *a, const void *b)
464 {
465  int av = *(const int16 *) a;
466  int bv = *(const int16 *) b;
467 
468  /* this can't overflow if int is wider than int16 */
469  return (av - bv);
470 }
471 
472 /*
473  * Translate a list of column names to an array of attribute numbers
474  * and a Bitmapset with them; verify that each attribute is appropriate
475  * to have in a publication column list (no system or generated attributes,
476  * no duplicates). Additional checks with replica identity are done later;
477  * see check_publication_columns.
478  *
479  * Note that the attribute numbers are *not* offset by
480  * FirstLowInvalidHeapAttributeNumber; system columns are forbidden so this
481  * is okay.
482  */
483 static void
485  int *natts, AttrNumber **attrs)
486 {
487  AttrNumber *attarray = NULL;
488  Bitmapset *set = NULL;
489  ListCell *lc;
490  int n = 0;
491  TupleDesc tupdesc = RelationGetDescr(targetrel);
492 
493  /* Bail out when no column list defined. */
494  if (!columns)
495  return;
496 
497  /*
498  * Translate list of columns to attnums. We prohibit system attributes and
499  * make sure there are no duplicate columns.
500  */
501  attarray = palloc(sizeof(AttrNumber) * list_length(columns));
502  foreach(lc, columns)
503  {
504  char *colname = strVal(lfirst(lc));
505  AttrNumber attnum = get_attnum(RelationGetRelid(targetrel), colname);
506 
507  if (attnum == InvalidAttrNumber)
508  ereport(ERROR,
509  errcode(ERRCODE_UNDEFINED_COLUMN),
510  errmsg("column \"%s\" of relation \"%s\" does not exist",
511  colname, RelationGetRelationName(targetrel)));
512 
514  ereport(ERROR,
515  errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
516  errmsg("cannot use system column \"%s\" in publication column list",
517  colname));
518 
519  if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
520  ereport(ERROR,
521  errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
522  errmsg("cannot use generated column \"%s\" in publication column list",
523  colname));
524 
525  if (bms_is_member(attnum, set))
526  ereport(ERROR,
528  errmsg("duplicate column \"%s\" in publication column list",
529  colname));
530 
531  set = bms_add_member(set, attnum);
532  attarray[n++] = attnum;
533  }
534 
535  /* Be tidy, so that the catalog representation is always sorted */
536  qsort(attarray, n, sizeof(AttrNumber), compare_int16);
537 
538  *natts = n;
539  *attrs = attarray;
540 
541  bms_free(set);
542 }
543 
544 /*
545  * Transform a column list (represented by an array Datum) to a bitmapset.
546  *
547  * If columns isn't NULL, add the column numbers to that set.
548  *
549  * If mcxt isn't NULL, build the bitmapset in that context.
550  */
551 Bitmapset *
553 {
554  Bitmapset *result = NULL;
555  ArrayType *arr;
556  int nelems;
557  int16 *elems;
558  MemoryContext oldcxt = NULL;
559 
560  /*
561  * If an existing bitmap was provided, use it. Otherwise just use NULL and
562  * build a new bitmap.
563  */
564  if (columns)
565  result = columns;
566 
567  arr = DatumGetArrayTypeP(pubcols);
568  nelems = ARR_DIMS(arr)[0];
569  elems = (int16 *) ARR_DATA_PTR(arr);
570 
571  /* If a memory context was specified, switch to it. */
572  if (mcxt)
573  oldcxt = MemoryContextSwitchTo(mcxt);
574 
575  for (int i = 0; i < nelems; i++)
576  result = bms_add_member(result, elems[i]);
577 
578  if (mcxt)
579  MemoryContextSwitchTo(oldcxt);
580 
581  return result;
582 }
583 
584 /*
585  * Insert new publication / schema mapping.
586  */
588 publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
589 {
590  Relation rel;
591  HeapTuple tup;
592  Datum values[Natts_pg_publication_namespace];
593  bool nulls[Natts_pg_publication_namespace];
594  Oid psschid;
595  Publication *pub = GetPublication(pubid);
596  List *schemaRels = NIL;
597  ObjectAddress myself,
598  referenced;
599 
600  rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
601 
602  /*
603  * Check for duplicates. Note that this does not really prevent
604  * duplicates, it's here just to provide nicer error message in common
605  * case. The real protection is the unique key on the catalog.
606  */
608  ObjectIdGetDatum(schemaid),
609  ObjectIdGetDatum(pubid)))
610  {
612 
613  if (if_not_exists)
614  return InvalidObjectAddress;
615 
616  ereport(ERROR,
618  errmsg("schema \"%s\" is already member of publication \"%s\"",
619  get_namespace_name(schemaid), pub->name)));
620  }
621 
623 
624  /* Form a tuple */
625  memset(values, 0, sizeof(values));
626  memset(nulls, false, sizeof(nulls));
627 
628  psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
629  Anum_pg_publication_namespace_oid);
630  values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
631  values[Anum_pg_publication_namespace_pnpubid - 1] =
632  ObjectIdGetDatum(pubid);
633  values[Anum_pg_publication_namespace_pnnspid - 1] =
634  ObjectIdGetDatum(schemaid);
635 
636  tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
637 
638  /* Insert tuple into catalog */
639  CatalogTupleInsert(rel, tup);
640  heap_freetuple(tup);
641 
642  ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
643 
644  /* Add dependency on the publication */
645  ObjectAddressSet(referenced, PublicationRelationId, pubid);
646  recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
647 
648  /* Add dependency on the schema */
649  ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
650  recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
651 
652  /* Close the table */
654 
655  /*
656  * Invalidate relcache so that publication info is rebuilt. See
657  * publication_add_relation for why we need to consider all the
658  * partitions.
659  */
660  schemaRels = GetSchemaPublicationRelations(schemaid,
662  InvalidatePublicationRels(schemaRels);
663 
664  return myself;
665 }
666 
667 /* Gets list of publication oids for a relation */
668 List *
670 {
671  List *result = NIL;
672  CatCList *pubrellist;
673  int i;
674 
675  /* Find all publications associated with the relation. */
677  ObjectIdGetDatum(relid));
678  for (i = 0; i < pubrellist->n_members; i++)
679  {
680  HeapTuple tup = &pubrellist->members[i]->tuple;
681  Oid pubid = ((Form_pg_publication_rel) GETSTRUCT(tup))->prpubid;
682 
683  result = lappend_oid(result, pubid);
684  }
685 
686  ReleaseSysCacheList(pubrellist);
687 
688  return result;
689 }
690 
691 /*
692  * Gets list of relation oids for a publication.
693  *
694  * This should only be used FOR TABLE publications, the FOR ALL TABLES
695  * should use GetAllTablesPublicationRelations().
696  */
697 List *
699 {
700  List *result;
701  Relation pubrelsrel;
702  ScanKeyData scankey;
703  SysScanDesc scan;
704  HeapTuple tup;
705 
706  /* Find all publications associated with the relation. */
707  pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
708 
709  ScanKeyInit(&scankey,
710  Anum_pg_publication_rel_prpubid,
711  BTEqualStrategyNumber, F_OIDEQ,
712  ObjectIdGetDatum(pubid));
713 
714  scan = systable_beginscan(pubrelsrel, PublicationRelPrpubidIndexId,
715  true, NULL, 1, &scankey);
716 
717  result = NIL;
718  while (HeapTupleIsValid(tup = systable_getnext(scan)))
719  {
721 
722  pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
723  result = GetPubPartitionOptionRelations(result, pub_partopt,
724  pubrel->prrelid);
725  }
726 
727  systable_endscan(scan);
728  table_close(pubrelsrel, AccessShareLock);
729 
730  /* Now sort and de-duplicate the result list */
731  list_sort(result, list_oid_cmp);
732  list_deduplicate_oid(result);
733 
734  return result;
735 }
736 
737 /*
738  * Gets list of publication oids for publications marked as FOR ALL TABLES.
739  */
740 List *
742 {
743  List *result;
744  Relation rel;
745  ScanKeyData scankey;
746  SysScanDesc scan;
747  HeapTuple tup;
748 
749  /* Find all publications that are marked as for all tables. */
750  rel = table_open(PublicationRelationId, AccessShareLock);
751 
752  ScanKeyInit(&scankey,
753  Anum_pg_publication_puballtables,
754  BTEqualStrategyNumber, F_BOOLEQ,
755  BoolGetDatum(true));
756 
757  scan = systable_beginscan(rel, InvalidOid, false,
758  NULL, 1, &scankey);
759 
760  result = NIL;
761  while (HeapTupleIsValid(tup = systable_getnext(scan)))
762  {
763  Oid oid = ((Form_pg_publication) GETSTRUCT(tup))->oid;
764 
765  result = lappend_oid(result, oid);
766  }
767 
768  systable_endscan(scan);
770 
771  return result;
772 }
773 
774 /*
775  * Gets list of all relation published by FOR ALL TABLES publication(s).
776  *
777  * If the publication publishes partition changes via their respective root
778  * partitioned tables, we must exclude partitions in favor of including the
779  * root partitioned tables.
780  */
781 List *
783 {
784  Relation classRel;
785  ScanKeyData key[1];
786  TableScanDesc scan;
787  HeapTuple tuple;
788  List *result = NIL;
789 
790  classRel = table_open(RelationRelationId, AccessShareLock);
791 
792  ScanKeyInit(&key[0],
793  Anum_pg_class_relkind,
794  BTEqualStrategyNumber, F_CHAREQ,
795  CharGetDatum(RELKIND_RELATION));
796 
797  scan = table_beginscan_catalog(classRel, 1, key);
798 
799  while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
800  {
801  Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
802  Oid relid = relForm->oid;
803 
804  if (is_publishable_class(relid, relForm) &&
805  !(relForm->relispartition && pubviaroot))
806  result = lappend_oid(result, relid);
807  }
808 
809  table_endscan(scan);
810 
811  if (pubviaroot)
812  {
813  ScanKeyInit(&key[0],
814  Anum_pg_class_relkind,
815  BTEqualStrategyNumber, F_CHAREQ,
816  CharGetDatum(RELKIND_PARTITIONED_TABLE));
817 
818  scan = table_beginscan_catalog(classRel, 1, key);
819 
820  while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
821  {
822  Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
823  Oid relid = relForm->oid;
824 
825  if (is_publishable_class(relid, relForm) &&
826  !relForm->relispartition)
827  result = lappend_oid(result, relid);
828  }
829 
830  table_endscan(scan);
831  }
832 
833  table_close(classRel, AccessShareLock);
834  return result;
835 }
836 
837 /*
838  * Gets the list of schema oids for a publication.
839  *
840  * This should only be used FOR TABLES IN SCHEMA publications.
841  */
842 List *
844 {
845  List *result = NIL;
846  Relation pubschsrel;
847  ScanKeyData scankey;
848  SysScanDesc scan;
849  HeapTuple tup;
850 
851  /* Find all schemas associated with the publication */
852  pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
853 
854  ScanKeyInit(&scankey,
855  Anum_pg_publication_namespace_pnpubid,
856  BTEqualStrategyNumber, F_OIDEQ,
857  ObjectIdGetDatum(pubid));
858 
859  scan = systable_beginscan(pubschsrel,
860  PublicationNamespacePnnspidPnpubidIndexId,
861  true, NULL, 1, &scankey);
862  while (HeapTupleIsValid(tup = systable_getnext(scan)))
863  {
865 
867 
868  result = lappend_oid(result, pubsch->pnnspid);
869  }
870 
871  systable_endscan(scan);
872  table_close(pubschsrel, AccessShareLock);
873 
874  return result;
875 }
876 
877 /*
878  * Gets the list of publication oids associated with a specified schema.
879  */
880 List *
882 {
883  List *result = NIL;
884  CatCList *pubschlist;
885  int i;
886 
887  /* Find all publications associated with the schema */
889  ObjectIdGetDatum(schemaid));
890  for (i = 0; i < pubschlist->n_members; i++)
891  {
892  HeapTuple tup = &pubschlist->members[i]->tuple;
893  Oid pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
894 
895  result = lappend_oid(result, pubid);
896  }
897 
898  ReleaseSysCacheList(pubschlist);
899 
900  return result;
901 }
902 
903 /*
904  * Get the list of publishable relation oids for a specified schema.
905  */
906 List *
908 {
909  Relation classRel;
910  ScanKeyData key[1];
911  TableScanDesc scan;
912  HeapTuple tuple;
913  List *result = NIL;
914 
915  Assert(OidIsValid(schemaid));
916 
917  classRel = table_open(RelationRelationId, AccessShareLock);
918 
919  ScanKeyInit(&key[0],
920  Anum_pg_class_relnamespace,
921  BTEqualStrategyNumber, F_OIDEQ,
922  schemaid);
923 
924  /* get all the relations present in the specified schema */
925  scan = table_beginscan_catalog(classRel, 1, key);
926  while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
927  {
928  Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
929  Oid relid = relForm->oid;
930  char relkind;
931 
932  if (!is_publishable_class(relid, relForm))
933  continue;
934 
935  relkind = get_rel_relkind(relid);
936  if (relkind == RELKIND_RELATION)
937  result = lappend_oid(result, relid);
938  else if (relkind == RELKIND_PARTITIONED_TABLE)
939  {
940  List *partitionrels = NIL;
941 
942  /*
943  * It is quite possible that some of the partitions are in a
944  * different schema than the parent table, so we need to get such
945  * partitions separately.
946  */
947  partitionrels = GetPubPartitionOptionRelations(partitionrels,
948  pub_partopt,
949  relForm->oid);
950  result = list_concat_unique_oid(result, partitionrels);
951  }
952  }
953 
954  table_endscan(scan);
955  table_close(classRel, AccessShareLock);
956  return result;
957 }
958 
959 /*
960  * Gets the list of all relations published by FOR TABLES IN SCHEMA
961  * publication.
962  */
963 List *
965 {
966  List *result = NIL;
967  List *pubschemalist = GetPublicationSchemas(pubid);
968  ListCell *cell;
969 
970  foreach(cell, pubschemalist)
971  {
972  Oid schemaid = lfirst_oid(cell);
973  List *schemaRels = NIL;
974 
975  schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
976  result = list_concat(result, schemaRels);
977  }
978 
979  return result;
980 }
981 
982 /*
983  * Get publication using oid
984  *
985  * The Publication struct and its data are palloc'ed here.
986  */
987 Publication *
989 {
990  HeapTuple tup;
991  Publication *pub;
992  Form_pg_publication pubform;
993 
995  if (!HeapTupleIsValid(tup))
996  elog(ERROR, "cache lookup failed for publication %u", pubid);
997 
998  pubform = (Form_pg_publication) GETSTRUCT(tup);
999 
1000  pub = (Publication *) palloc(sizeof(Publication));
1001  pub->oid = pubid;
1002  pub->name = pstrdup(NameStr(pubform->pubname));
1003  pub->alltables = pubform->puballtables;
1004  pub->pubactions.pubinsert = pubform->pubinsert;
1005  pub->pubactions.pubupdate = pubform->pubupdate;
1006  pub->pubactions.pubdelete = pubform->pubdelete;
1007  pub->pubactions.pubtruncate = pubform->pubtruncate;
1008  pub->pubviaroot = pubform->pubviaroot;
1009 
1010  ReleaseSysCache(tup);
1011 
1012  return pub;
1013 }
1014 
1015 /*
1016  * Get Publication using name.
1017  */
1018 Publication *
1019 GetPublicationByName(const char *pubname, bool missing_ok)
1020 {
1021  Oid oid;
1022 
1023  oid = get_publication_oid(pubname, missing_ok);
1024 
1025  return OidIsValid(oid) ? GetPublication(oid) : NULL;
1026 }
1027 
1028 /*
1029  * Returns information of tables in a publication.
1030  */
1031 Datum
1033 {
1034 #define NUM_PUBLICATION_TABLES_ELEM 3
1035  FuncCallContext *funcctx;
1036  char *pubname = text_to_cstring(PG_GETARG_TEXT_PP(0));
1037  Publication *publication;
1038  List *tables;
1039 
1040  /* stuff done only on the first call of the function */
1041  if (SRF_IS_FIRSTCALL())
1042  {
1043  TupleDesc tupdesc;
1044  MemoryContext oldcontext;
1045 
1046  /* create a function context for cross-call persistence */
1047  funcctx = SRF_FIRSTCALL_INIT();
1048 
1049  /* switch to memory context appropriate for multiple function calls */
1050  oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1051 
1052  publication = GetPublicationByName(pubname, false);
1053 
1054  /*
1055  * Publications support partitioned tables, although all changes are
1056  * replicated using leaf partition identity and schema, so we only
1057  * need those.
1058  */
1059  if (publication->alltables)
1060  {
1061  tables = GetAllTablesPublicationRelations(publication->pubviaroot);
1062  }
1063  else
1064  {
1065  List *relids,
1066  *schemarelids;
1067 
1068  relids = GetPublicationRelations(publication->oid,
1069  publication->pubviaroot ?
1072  schemarelids = GetAllSchemaPublicationRelations(publication->oid,
1073  publication->pubviaroot ?
1076  tables = list_concat_unique_oid(relids, schemarelids);
1077 
1078  /*
1079  * If the publication publishes partition changes via their
1080  * respective root partitioned tables, we must exclude partitions
1081  * in favor of including the root partitioned tables. Otherwise,
1082  * the function could return both the child and parent tables
1083  * which could cause data of the child table to be
1084  * double-published on the subscriber side.
1085  */
1086  if (publication->pubviaroot)
1087  tables = filter_partitions(tables);
1088  }
1089 
1090  /* Construct a tuple descriptor for the result rows. */
1092  TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relid",
1093  OIDOID, -1, 0);
1094  TupleDescInitEntry(tupdesc, (AttrNumber) 2, "attrs",
1095  INT2VECTOROID, -1, 0);
1096  TupleDescInitEntry(tupdesc, (AttrNumber) 3, "qual",
1097  PG_NODE_TREEOID, -1, 0);
1098 
1099  funcctx->tuple_desc = BlessTupleDesc(tupdesc);
1100  funcctx->user_fctx = (void *) tables;
1101 
1102  MemoryContextSwitchTo(oldcontext);
1103  }
1104 
1105  /* stuff done on every call of the function */
1106  funcctx = SRF_PERCALL_SETUP();
1107  tables = (List *) funcctx->user_fctx;
1108 
1109  if (funcctx->call_cntr < list_length(tables))
1110  {
1111  HeapTuple pubtuple = NULL;
1112  HeapTuple rettuple;
1113  Oid relid = list_nth_oid(tables, funcctx->call_cntr);
1114  Oid schemaid = get_rel_namespace(relid);
1116  bool nulls[NUM_PUBLICATION_TABLES_ELEM] = {0};
1117 
1118  /*
1119  * Form tuple with appropriate data.
1120  */
1121 
1122  publication = GetPublicationByName(pubname, false);
1123 
1124  values[0] = ObjectIdGetDatum(relid);
1125 
1126  /*
1127  * We don't consider row filters or column lists for FOR ALL TABLES or
1128  * FOR TABLES IN SCHEMA publications.
1129  */
1130  if (!publication->alltables &&
1132  ObjectIdGetDatum(schemaid),
1133  ObjectIdGetDatum(publication->oid)))
1135  ObjectIdGetDatum(relid),
1136  ObjectIdGetDatum(publication->oid));
1137 
1138  if (HeapTupleIsValid(pubtuple))
1139  {
1140  /* Lookup the column list attribute. */
1141  values[1] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple,
1142  Anum_pg_publication_rel_prattrs,
1143  &(nulls[1]));
1144 
1145  /* Null indicates no filter. */
1146  values[2] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple,
1147  Anum_pg_publication_rel_prqual,
1148  &(nulls[2]));
1149  }
1150  else
1151  {
1152  nulls[1] = true;
1153  nulls[2] = true;
1154  }
1155 
1156  rettuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
1157 
1158  SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(rettuple));
1159  }
1160 
1161  SRF_RETURN_DONE(funcctx);
1162 }
#define ARR_DATA_PTR(a)
Definition: array.h:315
#define DatumGetArrayTypeP(X)
Definition: array.h:254
#define ARR_DIMS(a)
Definition: array.h:287
int16 AttrNumber
Definition: attnum.h:21
#define AttrNumberIsForUserDefinedAttr(attributeNumber)
Definition: attnum.h:41
#define InvalidAttrNumber
Definition: attnum.h:23
void bms_free(Bitmapset *a)
Definition: bitmapset.c:208
bool bms_is_member(int x, const Bitmapset *a)
Definition: bitmapset.c:427
Bitmapset * bms_add_member(Bitmapset *a, int x)
Definition: bitmapset.c:738
static Datum values[MAXATTR]
Definition: bootstrap.c:156
#define CStringGetTextDatum(s)
Definition: builtins.h:85
#define NameStr(name)
Definition: c.h:682
signed short int16
Definition: c.h:429
#define OidIsValid(objectId)
Definition: c.h:711
bool IsToastNamespace(Oid namespaceId)
Definition: catalog.c:202
Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
Definition: catalog.c:393
bool IsCatalogNamespace(Oid namespaceId)
Definition: catalog.c:184
bool IsCatalogRelation(Relation relation)
Definition: catalog.c:105
bool IsCatalogRelationOid(Oid relid)
Definition: catalog.c:122
void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, DependencyType self_behavior, bool reverse_self)
Definition: dependency.c:1639
@ DEPENDENCY_AUTO
Definition: dependency.h:34
@ DEPENDENCY_NORMAL
Definition: dependency.h:33
int errdetail(const char *fmt,...)
Definition: elog.c:1039
int errcode(int sqlerrcode)
Definition: elog.c:695
int errmsg(const char *fmt,...)
Definition: elog.c:906
#define ERROR
Definition: elog.h:35
#define ereport(elevel,...)
Definition: elog.h:145
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
Definition: execTuples.c:2071
#define PG_GETARG_OID(n)
Definition: fmgr.h:275
#define PG_GETARG_TEXT_PP(n)
Definition: fmgr.h:309
#define PG_RETURN_NULL()
Definition: fmgr.h:345
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
#define PG_RETURN_BOOL(x)
Definition: fmgr.h:359
#define SRF_IS_FIRSTCALL()
Definition: funcapi.h:303
#define SRF_PERCALL_SETUP()
Definition: funcapi.h:307
#define SRF_RETURN_NEXT(_funcctx, _result)
Definition: funcapi.h:309
#define SRF_FIRSTCALL_INIT()
Definition: funcapi.h:305
static Datum HeapTupleGetDatum(const HeapTupleData *tuple)
Definition: funcapi.h:230
#define SRF_RETURN_DONE(_funcctx)
Definition: funcapi.h:327
void systable_endscan(SysScanDesc sysscan)
Definition: genam.c:598
HeapTuple systable_getnext(SysScanDesc sysscan)
Definition: genam.c:505
SysScanDesc systable_beginscan(Relation heapRelation, Oid indexId, bool indexOK, Snapshot snapshot, int nkeys, ScanKey key)
Definition: genam.c:386
HeapTuple heap_getnext(TableScanDesc sscan, ScanDirection direction)
Definition: heapam.c:1296
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, Datum *values, bool *isnull)
Definition: heaptuple.c:1020
void heap_freetuple(HeapTuple htup)
Definition: heaptuple.c:1338
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
#define GETSTRUCT(TUP)
Definition: htup_details.h:649
void CatalogTupleInsert(Relation heapRel, HeapTuple tup)
Definition: indexing.c:221
int2vector * buildint2vector(const int16 *int2s, int n)
Definition: int.c:114
int b
Definition: isn.c:70
int a
Definition: isn.c:69
int i
Definition: isn.c:73
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:77
Assert(fmt[strlen(fmt) - 1] !='\n')
void list_sort(List *list, list_sort_comparator cmp)
Definition: list.c:1673
List * list_concat_unique_oid(List *list1, const List *list2)
Definition: list.c:1468
List * lappend_oid(List *list, Oid datum)
Definition: list.c:374
void list_deduplicate_oid(List *list)
Definition: list.c:1494
int list_oid_cmp(const ListCell *p1, const ListCell *p2)
Definition: list.c:1706
void list_free(List *list)
Definition: list.c:1545
bool list_member_oid(const List *list, Oid datum)
Definition: list.c:721
List * list_concat(List *list1, const List *list2)
Definition: list.c:560
#define NoLock
Definition: lockdefs.h:34
#define AccessShareLock
Definition: lockdefs.h:36
#define RowExclusiveLock
Definition: lockdefs.h:38
bool get_rel_relispartition(Oid relid)
Definition: lsyscache.c:2009
AttrNumber get_attnum(Oid relid, const char *attname)
Definition: lsyscache.c:857
char * get_namespace_name(Oid nspid)
Definition: lsyscache.c:3327
char get_rel_relkind(Oid relid)
Definition: lsyscache.c:1985
Oid get_publication_oid(const char *pubname, bool missing_ok)
Definition: lsyscache.c:3590
Oid get_rel_namespace(Oid relid)
Definition: lsyscache.c:1934
char * pstrdup(const char *in)
Definition: mcxt.c:1392
void * palloc(Size size)
Definition: mcxt.c:1145
bool isAnyTempNamespace(Oid namespaceId)
Definition: namespace.c:3241
const ObjectAddress InvalidObjectAddress
#define ObjectAddressSet(addr, class_id, object_id)
Definition: objectaddress.h:40
#define ObjectAddressSubSet(addr, class_id, object_id, object_sub_id)
Definition: objectaddress.h:33
char * nodeToString(const void *obj)
Definition: outfuncs.c:873
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:131
List * get_partition_ancestors(Oid relid)
Definition: partition.c:133
int16 attnum
Definition: pg_attribute.h:83
static const struct exclude_list_item skip[]
Definition: pg_checksums.c:116
int errdetail_relkind_not_supported(char relkind)
Definition: pg_class.c:24
FormData_pg_class * Form_pg_class
Definition: pg_class.h:153
void recordDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced, DependencyType behavior)
Definition: pg_depend.c:44
List * find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **numparents)
Definition: pg_inherits.c:256
#define lfirst(lc)
Definition: pg_list.h:170
static int list_length(const List *l)
Definition: pg_list.h:150
#define NIL
Definition: pg_list.h:66
static Oid list_nth_oid(const List *list, int n)
Definition: pg_list.h:319
#define lfirst_oid(lc)
Definition: pg_list.h:172
bool is_schema_publication(Oid pubid)
List * GetPublicationSchemas(Oid pubid)
Publication * GetPublicationByName(const char *pubname, bool missing_ok)
List * GetSchemaPublications(Oid schemaid)
Publication * GetPublication(Oid pubid)
List * GetRelationPublications(Oid relid)
ObjectAddress publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri, bool if_not_exists)
List * GetPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
Datum pg_get_publication_tables(PG_FUNCTION_ARGS)
List * GetAllTablesPublications(void)
static void check_publication_add_relation(Relation targetrel)
List * GetAllTablesPublicationRelations(bool pubviaroot)
Oid GetTopMostAncestorInPublication(Oid puboid, List *ancestors, int *ancestor_level)
List * GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt, Oid relid)
static bool is_publishable_class(Oid relid, Form_pg_class reltuple)
#define NUM_PUBLICATION_TABLES_ELEM
static void check_publication_add_schema(Oid schemaid)
static int compare_int16(const void *a, const void *b)
static List * filter_partitions(List *relids)
List * GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
Bitmapset * pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols, MemoryContext mcxt)
Datum pg_relation_is_publishable(PG_FUNCTION_ARGS)
bool is_publishable_relation(Relation rel)
List * GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
static void publication_translate_columns(Relation targetrel, List *columns, int *natts, AttrNumber **attrs)
FormData_pg_publication * Form_pg_publication
PublicationPartOpt
@ PUBLICATION_PART_LEAF
@ PUBLICATION_PART_ROOT
@ PUBLICATION_PART_ALL
FormData_pg_publication_namespace * Form_pg_publication_namespace
FormData_pg_publication_rel * Form_pg_publication_rel
#define qsort(a, b, c, d)
Definition: port.h:441
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:670
uintptr_t Datum
Definition: postgres.h:412
static Datum BoolGetDatum(bool X)
Definition: postgres.h:450
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:600
static Datum CharGetDatum(char X)
Definition: postgres.h:470
#define InvalidOid
Definition: postgres_ext.h:36
unsigned int Oid
Definition: postgres_ext.h:31
void InvalidatePublicationRels(List *relids)
#define RelationGetForm(relation)
Definition: rel.h:495
#define RelationGetRelid(relation)
Definition: rel.h:501
#define RelationGetDescr(relation)
Definition: rel.h:527
#define RelationGetRelationName(relation)
Definition: rel.h:535
void ScanKeyInit(ScanKey entry, AttrNumber attributeNumber, StrategyNumber strategy, RegProcedure procedure, Datum argument)
Definition: scankey.c:76
@ ForwardScanDirection
Definition: sdir.h:26
struct @10::@11 av[32]
#define BTEqualStrategyNumber
Definition: stratnum.h:31
#define ERRCODE_DUPLICATE_OBJECT
Definition: streamutil.c:32
void * user_fctx
Definition: funcapi.h:82
uint64 call_cntr
Definition: funcapi.h:65
MemoryContext multi_call_memory_ctx
Definition: funcapi.h:101
TupleDesc tuple_desc
Definition: funcapi.h:112
Definition: pg_list.h:52
PublicationActions pubactions
Form_pg_class rd_rel
Definition: rel.h:110
CatCTup * members[FLEXIBLE_ARRAY_MEMBER]
Definition: catcache.h:178
int n_members
Definition: catcache.h:176
HeapTupleData tuple
Definition: catcache.h:121
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:1221
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:1173
Datum SysCacheGetAttr(int cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition: syscache.c:1434
#define SearchSysCacheCopy2(cacheId, key1, key2)
Definition: syscache.h:181
@ PUBLICATIONOID
Definition: syscache.h:83
@ RELOID
Definition: syscache.h:89
@ PUBLICATIONNAMESPACEMAP
Definition: syscache.h:82
@ PUBLICATIONRELMAP
Definition: syscache.h:85
#define ReleaseSysCacheList(x)
Definition: syscache.h:222
#define SearchSysCacheExists2(cacheId, key1, key2)
Definition: syscache.h:190
#define SearchSysCacheList1(cacheId, key1)
Definition: syscache.h:215
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:126
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:40
TableScanDesc table_beginscan_catalog(Relation relation, int nkeys, struct ScanKeyData *key)
Definition: tableam.c:112
static void table_endscan(TableScanDesc scan)
Definition: tableam.h:993
#define FirstNormalObjectId
Definition: transam.h:197
TupleDesc CreateTemplateTupleDesc(int natts)
Definition: tupdesc.c:45
void TupleDescInitEntry(TupleDesc desc, AttrNumber attributeNumber, const char *attributeName, Oid oidtypeid, int32 typmod, int attdim)
Definition: tupdesc.c:583
#define TupleDescAttr(tupdesc, i)
Definition: tupdesc.h:92
#define strVal(v)
Definition: value.h:82
char * text_to_cstring(const text *t)
Definition: varlena.c:222