attndims, typndims still not enforced, but make the value within a sane threshold

Started by jian heover 1 year ago36 messages
#1jian he
jian.universality@gmail.com

hi.

while looking at tablecmd.c, BuildDescForRelation
attdim = list_length(entry->typeName->arrayBounds);
if (attdim > PG_INT16_MAX)
ereport(ERROR,
errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many array dimensions"))

makes me related to array_in refactor previously we did.
at first, i thought it should be "if (attdim > MAXDIM)"

pg_attribute attndims description in [1]https://www.postgresql.org/docs/current/catalog-pg-attribute.html
attndims int2
Number of dimensions, if the column is an array type; otherwise 0.
(Presently, the number of dimensions of an array is not enforced, so
any nonzero value effectively means “it's an array”.)

pg_type typndims description in [2]https://www.postgresql.org/docs/current/catalog-pg-type.html
typndims int4
typndims is the number of array dimensions for a domain over an array
(that is, typbasetype is an array type). Zero for types other than
domains over array types.

since array_in is the only source of the real array data.
MAXDIM (6) ensure the max dimension is 6.

Can we error out at the stage "create table", "create domain"
time if the attndims or typndims is larger than MAXDIM (6) ?

for example, error out the following queries immediately
create table t112(a int[][] [][] [][] [][][]);
create domain d_text_arr text [1]https://www.postgresql.org/docs/current/catalog-pg-attribute.html[][][][][][][];

in the doc, we can still say "the number of dimensions of an array is
not enforced",
but attndims, typndims value would be within a sane threshold.

We can change typndims from int4 to int2,
so array type's dimension is consistent with domain type's dimension.
but it seems with the change, pg_type occupies the same amount of
storage as int4.

[1]: https://www.postgresql.org/docs/current/catalog-pg-attribute.html
[2]: https://www.postgresql.org/docs/current/catalog-pg-type.html

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: jian he (#1)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

jian he <jian.universality@gmail.com> writes:

Can we error out at the stage "create table", "create domain"
time if the attndims or typndims is larger than MAXDIM (6) ?

The last time this was discussed, I think the conclusion was
we should remove attndims and typndims entirely on the grounds
that they're useless. I certainly don't see a point in adding
more logic that could give the misleading impression that they
mean something.

regards, tom lane

#3jian he
jian.universality@gmail.com
In reply to: Tom Lane (#2)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Wed, Sep 18, 2024 at 10:10 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

jian he <jian.universality@gmail.com> writes:

Can we error out at the stage "create table", "create domain"
time if the attndims or typndims is larger than MAXDIM (6) ?

The last time this was discussed, I think the conclusion was
we should remove attndims and typndims entirely on the grounds
that they're useless. I certainly don't see a point in adding
more logic that could give the misleading impression that they
mean something.

https://commitfest.postgresql.org/43/
search "dim" or "pg_attribute", no relevant result,
i am assuming, nobody doing work to remove attndims and typndims entirely?
If so, I will try to make one.

#4jian he
jian.universality@gmail.com
In reply to: jian he (#3)
1 attachment(s)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Wed, Sep 18, 2024 at 10:35 PM jian he <jian.universality@gmail.com> wrote:

The last time this was discussed, I think the conclusion was
we should remove attndims and typndims entirely on the grounds
that they're useless. I certainly don't see a point in adding
more logic that could give the misleading impression that they
mean something.

attached patch removes attndims and typndims entirely.
some tests skipped in my local my machine, not skipped are all OK.

Attachments:

v1-0001-remove-pg_attribute-attndims-and-pg_type-typndims.patchtext/x-patch; charset=US-ASCII; name=v1-0001-remove-pg_attribute-attndims-and-pg_type-typndims.patchDownload
From 4a473172b576a02aa4ac46af5de752e1f42d8922 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 20 Sep 2024 09:48:36 +0800
Subject: [PATCH v1 1/1] remove pg_attribute attndims and pg_type typndims

---
 contrib/dblink/dblink.c                       |  8 +-
 contrib/pg_buffercache/pg_buffercache_pages.c | 18 ++---
 contrib/pg_visibility/pg_visibility.c         |  8 +-
 doc/src/sgml/catalogs.sgml                    | 23 ------
 src/backend/access/brin/brin_tuple.c          |  2 +-
 src/backend/access/common/tupdesc.c           | 20 +----
 src/backend/access/gin/ginutil.c              |  5 +-
 src/backend/access/gist/gistscan.c            |  4 +-
 src/backend/access/transam/twophase.c         | 10 +--
 src/backend/access/transam/xlogfuncs.c        |  4 +-
 src/backend/backup/basebackup_copy.c          | 10 +--
 src/backend/bootstrap/bootstrap.c             | 11 ---
 src/backend/catalog/genbki.pl                 |  2 -
 src/backend/catalog/heap.c                    |  3 -
 src/backend/catalog/index.c                   |  1 -
 src/backend/catalog/pg_publication.c          |  8 +-
 src/backend/catalog/pg_type.c                 |  3 -
 src/backend/catalog/toasting.c                |  6 +-
 src/backend/commands/explain.c                |  2 +-
 src/backend/commands/functioncmds.c           |  3 +-
 src/backend/commands/sequence.c               |  4 +-
 src/backend/commands/tablecmds.c              | 13 +--
 src/backend/commands/typecmds.c               | 11 ---
 src/backend/executor/execSRF.c                |  6 +-
 src/backend/executor/execTuples.c             |  6 +-
 src/backend/executor/nodeFunctionscan.c       |  6 +-
 src/backend/parser/parse_relation.c           |  9 +--
 src/backend/parser/parse_target.c             |  3 +-
 src/backend/parser/parse_type.c               |  1 +
 .../libpqwalreceiver/libpqwalreceiver.c       |  2 +-
 src/backend/replication/walsender.c           | 30 +++----
 src/backend/utils/adt/acl.c                   |  9 +--
 src/backend/utils/adt/genfile.c               | 12 +--
 src/backend/utils/adt/lockfuncs.c             | 33 ++++----
 src/backend/utils/adt/orderedsetaggs.c        |  3 +-
 src/backend/utils/adt/pgstatfuncs.c           | 79 +++++++++----------
 src/backend/utils/adt/tsvector_op.c           |  6 +-
 src/backend/utils/fmgr/funcapi.c              | 33 +++-----
 src/backend/utils/misc/guc_funcs.c            | 50 ++++++------
 src/include/access/tupdesc.h                  |  7 +-
 src/include/catalog/pg_attribute.h            |  5 --
 src/include/catalog/pg_type.h                 |  7 --
 src/pl/plpgsql/src/pl_comp.c                  |  4 +-
 .../injection_points/injection_stats_fixed.c  | 10 +--
 .../modules/test_predtest/test_predtest.c     | 16 ++--
 45 files changed, 198 insertions(+), 318 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index c1c82eb4dd..04938991f9 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -874,7 +874,7 @@ materializeResult(FunctionCallInfo fcinfo, PGconn *conn, PGresult *res)
 			 */
 			tupdesc = CreateTemplateTupleDesc(1);
 			TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
-							   TEXTOID, -1, 0);
+							   TEXTOID, -1);
 			ntuples = 1;
 			nfields = 1;
 		}
@@ -1048,7 +1048,7 @@ materializeQueryResult(FunctionCallInfo fcinfo,
 			 */
 			tupdesc = CreateTemplateTupleDesc(1);
 			TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
-							   TEXTOID, -1, 0);
+							   TEXTOID, -1);
 			attinmeta = TupleDescGetAttInMetadata(tupdesc);
 
 			oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
@@ -1534,9 +1534,9 @@ dblink_get_pkey(PG_FUNCTION_ARGS)
 		 */
 		tupdesc = CreateTemplateTupleDesc(2);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "position",
-						   INT4OID, -1, 0);
+						   INT4OID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "colname",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 
 		/*
 		 * Generate attribute metadata needed later to produce tuples from raw
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 3ae0a018e1..a72e641e0c 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -106,25 +106,25 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 		/* Construct a tuple descriptor for the result rows. */
 		tupledesc = CreateTemplateTupleDesc(expected_tupledesc->natts);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid",
-						   INT4OID, -1, 0);
+						   INT4OID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relforknumber",
-						   INT2OID, -1, 0);
+						   INT2OID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 6, "relblocknumber",
-						   INT8OID, -1, 0);
+						   INT8OID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 7, "isdirty",
-						   BOOLOID, -1, 0);
+						   BOOLOID, -1);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 8, "usage_count",
-						   INT2OID, -1, 0);
+						   INT2OID, -1);
 
 		if (expected_tupledesc->natts == NUM_BUFFERCACHE_PAGES_ELEM)
 			TupleDescInitEntry(tupledesc, (AttrNumber) 9, "pinning_backends",
-							   INT4OID, -1, 0);
+							   INT4OID, -1);
 
 		fctx->tupdesc = BlessTupleDesc(tupledesc);
 
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 724122b1bc..b1fd64b488 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -465,11 +465,11 @@ pg_visibility_tupdesc(bool include_blkno, bool include_pd)
 		++maxattr;
 	tupdesc = CreateTemplateTupleDesc(maxattr);
 	if (include_blkno)
-		TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0);
-	TupleDescInitEntry(tupdesc, ++a, "all_visible", BOOLOID, -1, 0);
-	TupleDescInitEntry(tupdesc, ++a, "all_frozen", BOOLOID, -1, 0);
+		TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1);
+	TupleDescInitEntry(tupdesc, ++a, "all_visible", BOOLOID, -1);
+	TupleDescInitEntry(tupdesc, ++a, "all_frozen", BOOLOID, -1);
 	if (include_pd)
-		TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1, 0);
+		TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1);
 	Assert(a == maxattr);
 
 	return BlessTupleDesc(tupdesc);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bfb97865e1..460e478fd7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1210,17 +1210,6 @@
       </para></entry>
      </row>
 
-     <row>
-      <entry role="catalog_table_entry"><para role="column_definition">
-       <structfield>attndims</structfield> <type>int2</type>
-      </para>
-      <para>
-       Number of dimensions, if the column is an array type; otherwise 0.
-       (Presently, the number of dimensions of an array is not enforced,
-       so any nonzero value effectively means <quote>it's an array</quote>.)
-      </para></entry>
-     </row>
-
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>attbyval</structfield> <type>bool</type>
@@ -9499,18 +9488,6 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
-     <row>
-      <entry role="catalog_table_entry"><para role="column_definition">
-       <structfield>typndims</structfield> <type>int4</type>
-      </para>
-      <para>
-       <structfield>typndims</structfield> is the number of array dimensions
-       for a domain over an array (that is, <structfield>typbasetype</structfield> is
-       an array type).
-       Zero for types other than domains over array types.
-      </para></entry>
-     </row>
-
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>typcollation</structfield> <type>oid</type>
diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index 997eb6d822..7d0dd21440 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -79,7 +79,7 @@ brtuple_disk_tupdesc(BrinDesc *brdesc)
 			for (j = 0; j < brdesc->bd_info[i]->oi_nstored; j++)
 				TupleDescInitEntry(tupdesc, attno++, NULL,
 								   brdesc->bd_info[i]->oi_typcache[j]->type_id,
-								   -1, 0);
+								   -1);
 		}
 
 		MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 47379fef22..58557abaa5 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -452,8 +452,6 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (attr1->attlen != attr2->attlen)
 			return false;
-		if (attr1->attndims != attr2->attndims)
-			return false;
 		if (attr1->atttypmod != attr2->atttypmod)
 			return false;
 		if (attr1->attbyval != attr2->attbyval)
@@ -575,9 +573,6 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
  * This is used to check whether two record types are compatible, whether
  * function return row types are the same, and other similar situations.
  *
- * (XXX There was some discussion whether attndims should be checked here, but
- * for now it has been decided not to.)
- *
  * Note: We deliberately do not check the tdtypmod field.  This allows
  * typcache.c to use this routine to see if a cached record type matches a
  * requested type.
@@ -652,8 +647,7 @@ TupleDescInitEntry(TupleDesc desc,
 				   AttrNumber attributeNumber,
 				   const char *attributeName,
 				   Oid oidtypeid,
-				   int32 typmod,
-				   int attdim)
+				   int32 typmod)
 {
 	HeapTuple	tuple;
 	Form_pg_type typeForm;
@@ -665,8 +659,6 @@ TupleDescInitEntry(TupleDesc desc,
 	Assert(PointerIsValid(desc));
 	Assert(attributeNumber >= 1);
 	Assert(attributeNumber <= desc->natts);
-	Assert(attdim >= 0);
-	Assert(attdim <= PG_INT16_MAX);
 
 	/*
 	 * initialize the attribute fields
@@ -689,7 +681,6 @@ TupleDescInitEntry(TupleDesc desc,
 	att->atttypmod = typmod;
 
 	att->attnum = attributeNumber;
-	att->attndims = attdim;
 
 	att->attnotnull = false;
 	att->atthasdef = false;
@@ -727,8 +718,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 						  AttrNumber attributeNumber,
 						  const char *attributeName,
 						  Oid oidtypeid,
-						  int32 typmod,
-						  int attdim)
+						  int32 typmod)
 {
 	Form_pg_attribute att;
 
@@ -736,8 +726,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	Assert(PointerIsValid(desc));
 	Assert(attributeNumber >= 1);
 	Assert(attributeNumber <= desc->natts);
-	Assert(attdim >= 0);
-	Assert(attdim <= PG_INT16_MAX);
+
 
 	/* initialize the attribute fields */
 	att = TupleDescAttr(desc, attributeNumber - 1);
@@ -751,7 +740,6 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	att->atttypmod = typmod;
 
 	att->attnum = attributeNumber;
-	att->attndims = attdim;
 
 	att->attnotnull = false;
 	att->atthasdef = false;
@@ -885,7 +873,7 @@ BuildDescFromLists(const List *names, const List *types, const List *typmods, co
 
 		attnum++;
 
-		TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod, 0);
+		TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod);
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
 	}
 
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 830d67fbc2..2041e58369 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -117,11 +117,10 @@ initGinState(GinState *state, Relation index)
 			state->tupdesc[i] = CreateTemplateTupleDesc(2);
 
 			TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
-							   INT2OID, -1, 0);
+							   INT2OID, -1);
 			TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
 							   attr->atttypid,
-							   attr->atttypmod,
-							   attr->attndims);
+							   attr->atttypmod);
 			TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
 										attr->attcollation);
 		}
diff --git a/src/backend/access/gist/gistscan.c b/src/backend/access/gist/gistscan.c
index de472e1637..353df42b4e 100644
--- a/src/backend/access/gist/gistscan.c
+++ b/src/backend/access/gist/gistscan.c
@@ -190,7 +190,7 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
 		{
 			TupleDescInitEntry(so->giststate->fetchTupdesc, attno, NULL,
 							   scan->indexRelation->rd_opcintype[attno - 1],
-							   -1, 0);
+							   -1);
 		}
 
 		for (; attno <= natts; attno++)
@@ -199,7 +199,7 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
 			TupleDescInitEntry(so->giststate->fetchTupdesc, attno, NULL,
 							   TupleDescAttr(so->giststate->leafTupdesc,
 											 attno - 1)->atttypid,
-							   -1, 0);
+							   -1);
 		}
 		scan->xs_hitupdesc = so->giststate->fetchTupdesc;
 
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index e98286d768..4a38d50dd2 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -730,15 +730,15 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
 		/* this had better match pg_prepared_xacts view in system_views.sql */
 		tupdesc = CreateTemplateTupleDesc(5);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "transaction",
-						   XIDOID, -1, 0);
+						   XIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "gid",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "prepared",
-						   TIMESTAMPTZOID, -1, 0);
+						   TIMESTAMPTZOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "ownerid",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "dbid",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 3e3d2bb618..29f4be3662 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -398,9 +398,9 @@ pg_walfile_name_offset(PG_FUNCTION_ARGS)
 	 */
 	resultTupleDesc = CreateTemplateTupleDesc(2);
 	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name",
-					   TEXTOID, -1, 0);
+					   TEXTOID, -1);
 	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset",
-					   INT4OID, -1, 0);
+					   INT4OID, -1);
 
 	resultTupleDesc = BlessTupleDesc(resultTupleDesc);
 
diff --git a/src/backend/backup/basebackup_copy.c b/src/backend/backup/basebackup_copy.c
index 0b4ee5f2a2..10f241d5f5 100644
--- a/src/backend/backup/basebackup_copy.c
+++ b/src/backend/backup/basebackup_copy.c
@@ -349,13 +349,13 @@ SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli)
 	dest = CreateDestReceiver(DestRemoteSimple);
 
 	tupdesc = CreateTemplateTupleDesc(2);
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "recptr", TEXTOID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "recptr", TEXTOID, -1);
 
 	/*
 	 * int8 may seem like a surprising data type for this, but in theory int4
 	 * would not be wide enough for this, as TimeLineID is unsigned.
 	 */
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "tli", INT8OID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "tli", INT8OID, -1);
 
 	/* send RowDescription */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
@@ -385,9 +385,9 @@ SendTablespaceList(List *tablespaces)
 	dest = CreateDestReceiver(DestRemoteSimple);
 
 	tupdesc = CreateTemplateTupleDesc(3);
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "spcoid", OIDOID, -1, 0);
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "spclocation", TEXTOID, -1, 0);
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "size", INT8OID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "spcoid", OIDOID, -1);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "spclocation", TEXTOID, -1);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "size", INT8OID, -1);
 
 	/* send RowDescription */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 7637581a18..20d40befee 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -515,11 +515,6 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 		attrtypes[attnum]->attstorage = Ap->am_typ.typstorage;
 		attrtypes[attnum]->attcompression = InvalidCompressionMethod;
 		attrtypes[attnum]->attcollation = Ap->am_typ.typcollation;
-		/* if an array type, assume 1-dimensional attribute */
-		if (Ap->am_typ.typelem != InvalidOid && Ap->am_typ.typlen < 0)
-			attrtypes[attnum]->attndims = 1;
-		else
-			attrtypes[attnum]->attndims = 0;
 	}
 	else
 	{
@@ -530,12 +525,6 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 		attrtypes[attnum]->attstorage = TypInfo[typeoid].storage;
 		attrtypes[attnum]->attcompression = InvalidCompressionMethod;
 		attrtypes[attnum]->attcollation = TypInfo[typeoid].collation;
-		/* if an array type, assume 1-dimensional attribute */
-		if (TypInfo[typeoid].elem != InvalidOid &&
-			attrtypes[attnum]->attlen < 0)
-			attrtypes[attnum]->attndims = 1;
-		else
-			attrtypes[attnum]->attndims = 0;
 	}
 
 	/*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 94afdc5491..d6711713e0 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -947,8 +947,6 @@ sub morph_row_for_pgattr
 	$row->{attalign} = $type->{typalign};
 	$row->{attstorage} = $type->{typstorage};
 
-	# set attndims if it's an array type
-	$row->{attndims} = $type->{typcategory} eq 'A' ? '1' : '0';
 
 	# collation-aware catalog columns must use C collation
 	$row->{attcollation} =
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 78e59384d1..ba6fb20ee1 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -742,7 +742,6 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
 		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
-		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int16GetDatum(attrs->attndims);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
@@ -1062,7 +1061,6 @@ AddNewRelationType(const char *typeName,
 				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
 				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
 				   -1,			/* typmod */
-				   0,			/* array dimensions for typBaseType */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* rowtypes never have a collation */
 }
@@ -1385,7 +1383,6 @@ heap_create_with_catalog(const char *relname,
 				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
 				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
 				   -1,			/* typmod */
-				   0,			/* array dimensions for typBaseType */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* rowtypes never have a collation */
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b2b3ecb524..4935f95826 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -351,7 +351,6 @@ ConstructTupleDescriptor(Relation heapRelation,
 
 			to->atttypid = from->atttypid;
 			to->attlen = from->attlen;
-			to->attndims = from->attndims;
 			to->atttypmod = from->atttypmod;
 			to->attbyval = from->attbyval;
 			to->attalign = from->attalign;
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 7fe5fe2b86..71abb17fb8 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1131,13 +1131,13 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
 		/* Construct a tuple descriptor for the result rows. */
 		tupdesc = CreateTemplateTupleDesc(NUM_PUBLICATION_TABLES_ELEM);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pubid",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "relid",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "attrs",
-						   INT2VECTOROID, -1, 0);
+						   INT2VECTOROID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "qual",
-						   PG_NODE_TREEOID, -1, 0);
+						   PG_NODE_TREEOID, -1);
 
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 		funcctx->user_fctx = (void *) table_infos;
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 395dec8ed8..388ae22645 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -118,7 +118,6 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(false);
 	values[Anum_pg_type_typbasetype - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_type_typtypmod - 1] = Int32GetDatum(-1);
-	values[Anum_pg_type_typndims - 1] = Int32GetDatum(0);
 	values[Anum_pg_type_typcollation - 1] = ObjectIdGetDatum(InvalidOid);
 	nulls[Anum_pg_type_typdefaultbin - 1] = true;
 	nulls[Anum_pg_type_typdefault - 1] = true;
@@ -221,7 +220,6 @@ TypeCreate(Oid newTypeOid,
 		   char alignment,
 		   char storage,
 		   int32 typeMod,
-		   int32 typNDims,		/* Array dimensions for baseType */
 		   bool typeNotNull,
 		   Oid typeCollation)
 {
@@ -376,7 +374,6 @@ TypeCreate(Oid newTypeOid,
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(typeNotNull);
 	values[Anum_pg_type_typbasetype - 1] = ObjectIdGetDatum(baseType);
 	values[Anum_pg_type_typtypmod - 1] = Int32GetDatum(typeMod);
-	values[Anum_pg_type_typndims - 1] = Int32GetDatum(typNDims);
 	values[Anum_pg_type_typcollation - 1] = ObjectIdGetDatum(typeCollation);
 
 	/*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 738bc46ae8..b698efa75a 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -204,15 +204,15 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "chunk_id",
 					   OIDOID,
-					   -1, 0);
+					   -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
 					   "chunk_seq",
 					   INT4OID,
-					   -1, 0);
+					   -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3,
 					   "chunk_data",
 					   BYTEAOID,
-					   -1, 0);
+					   -1);
 
 	/*
 	 * Ensure that the toast table doesn't itself get toasted, or we'll be
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index aaec439892..73d3352a55 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -417,7 +417,7 @@ ExplainResultDesc(ExplainStmt *stmt)
 	/* Need a tuple descriptor representing a single TEXT or XML column */
 	tupdesc = CreateTemplateTupleDesc(1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
-					   result_type, -1, 0);
+					   result_type, -1);
 	return tupdesc;
 }
 
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index d43b89d3ef..74e431d2b4 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -2400,8 +2400,7 @@ CallStmtResultDesc(CallStmt *stmt)
 							   i + 1,
 							   NameStr(att->attname),
 							   exprType(outarg),
-							   -1,
-							   0);
+							   -1);
 		}
 	}
 
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0188e8bbd5..907a3f386c 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1798,9 +1798,9 @@ pg_get_sequence_data(PG_FUNCTION_ARGS)
 
 	resultTupleDesc = CreateTemplateTupleDesc(PG_GET_SEQUENCE_DATA_COLS);
 	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "last_value",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "is_called",
-					   BOOLOID, -1, 0);
+					   BOOLOID, -1);
 	resultTupleDesc = BlessTupleDesc(resultTupleDesc);
 
 	init_sequence(relid, &elm, &seqrel);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2d703aa22e..ccdde1e988 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1291,7 +1291,6 @@ BuildDescForRelation(const List *columns)
 	Oid			atttypid;
 	int32		atttypmod;
 	Oid			attcollation;
-	int			attdim;
 
 	/*
 	 * allocate a new tuple descriptor
@@ -1322,11 +1321,6 @@ BuildDescForRelation(const List *columns)
 			aclcheck_error_type(aclresult, atttypid);
 
 		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
-		attdim = list_length(entry->typeName->arrayBounds);
-		if (attdim > PG_INT16_MAX)
-			ereport(ERROR,
-					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-					errmsg("too many array dimensions"));
 
 		if (entry->typeName->setof)
 			ereport(ERROR,
@@ -1335,7 +1329,7 @@ BuildDescForRelation(const List *columns)
 							attname)));
 
 		TupleDescInitEntry(desc, attnum, attname,
-						   atttypid, atttypmod, attdim);
+						   atttypid, atttypmod);
 		att = TupleDescAttr(desc, attnum - 1);
 
 		/* Override TupleDescInitEntry's settings as requested */
@@ -13404,11 +13398,6 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	attTup->atttypid = targettype;
 	attTup->atttypmod = targettypmod;
 	attTup->attcollation = targetcollid;
-	if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
-		ereport(ERROR,
-				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				errmsg("too many array dimensions"));
-	attTup->attndims = list_length(typeName->arrayBounds);
 	attTup->attlen = tform->typlen;
 	attTup->attbyval = tform->typbyval;
 	attTup->attalign = tform->typalign;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 2a6550de90..d25027e2b3 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -599,7 +599,6 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 				   alignment,	/* required alignment */
 				   storage,		/* TOAST strategy */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array Dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   collation);	/* type's collation */
 	Assert(typoid == address.objectId);
@@ -641,7 +640,6 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 			   alignment,		/* see above */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
 			   false,			/* Type NOT NULL */
 			   collation);		/* type's collation */
 
@@ -719,7 +717,6 @@ DefineDomain(CreateDomainStmt *stmt)
 	bool		saw_default = false;
 	bool		typNotNull = false;
 	bool		nullDefined = false;
-	int32		typNDims = list_length(stmt->typeName->arrayBounds);
 	HeapTuple	typeTup;
 	List	   *schema = stmt->constraints;
 	ListCell   *listptr;
@@ -1050,7 +1047,6 @@ DefineDomain(CreateDomainStmt *stmt)
 				   alignment,	/* required alignment */
 				   storage,		/* TOAST strategy */
 				   basetypeMod, /* typeMod value */
-				   typNDims,	/* Array dimensions for base type */
 				   typNotNull,	/* Type NOT NULL */
 				   domaincoll); /* type's collation */
 
@@ -1091,7 +1087,6 @@ DefineDomain(CreateDomainStmt *stmt)
 			   alignment,		/* see above */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
 			   false,			/* Type NOT NULL */
 			   domaincoll);		/* type's collation */
 
@@ -1213,7 +1208,6 @@ DefineEnum(CreateEnumStmt *stmt)
 				   TYPALIGN_INT,	/* int alignment */
 				   TYPSTORAGE_PLAIN,	/* TOAST strategy always plain */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation */
 
@@ -1254,7 +1248,6 @@ DefineEnum(CreateEnumStmt *stmt)
 			   TYPALIGN_INT,	/* enums have int align, so do their arrays */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* type's collation */
 
@@ -1555,7 +1548,6 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   alignment,	/* alignment */
 				   TYPSTORAGE_EXTENDED, /* TOAST strategy (always extended) */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(typoid == InvalidOid || typoid == address.objectId);
@@ -1622,7 +1614,6 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   alignment,	/* alignment */
 				   'x',			/* TOAST strategy (always extended) */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(multirangeOid == mltrngaddress.objectId);
@@ -1665,7 +1656,6 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   alignment,		/* alignment - same as range's */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* typcollation */
 
@@ -1704,7 +1694,6 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   alignment,		/* alignment - same as range's */
 			   'x',				/* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* typcollation */
 
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index 862d3085f8..7c7f91de69 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -270,8 +270,7 @@ ExecMakeTableFunctionResult(SetExprState *setexpr,
 									   (AttrNumber) 1,
 									   "column",
 									   funcrettype,
-									   -1,
-									   0);
+									   -1);
 					rsinfo.setDesc = tupdesc;
 				}
 				MemoryContextSwitchTo(oldcontext);
@@ -774,8 +773,7 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node,
 							   (AttrNumber) 1,
 							   NULL,
 							   funcrettype,
-							   -1,
-							   0);
+							   -1);
 			sexpr->funcResultDesc = tupdesc;
 			sexpr->funcReturnsTuple = false;
 		}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 00dc339615..549c7e4b6c 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -2063,8 +2063,7 @@ ExecTypeFromTLInternal(List *targetList, bool skipjunk)
 						   cur_resno,
 						   tle->resname,
 						   exprType((Node *) tle->expr),
-						   exprTypmod((Node *) tle->expr),
-						   0);
+						   exprTypmod((Node *) tle->expr));
 		TupleDescInitEntryCollation(typeInfo,
 									cur_resno,
 									exprCollation((Node *) tle->expr));
@@ -2097,8 +2096,7 @@ ExecTypeFromExprList(List *exprList)
 						   cur_resno,
 						   NULL,
 						   exprType(e),
-						   exprTypmod(e),
-						   0);
+						   exprTypmod(e));
 		TupleDescInitEntryCollation(typeInfo,
 									cur_resno,
 									exprCollation(e));
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 3fdd1c51e1..07f0f357ee 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -409,8 +409,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 								   (AttrNumber) 1,
 								   NULL,	/* don't care about the name here */
 								   funcrettype,
-								   -1,
-								   0);
+								   -1);
 				TupleDescInitEntryCollation(tupdesc,
 											(AttrNumber) 1,
 											exprCollation(funcexpr));
@@ -481,8 +480,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 							   ++attno,
 							   NULL,	/* don't care about the name here */
 							   INT8OID,
-							   -1,
-							   0);
+							   -1);
 		}
 
 		Assert(attno == natts);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 8075b1b8a1..55e9f20604 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1873,8 +1873,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 							   chooseScalarFunctionAlias(funcexpr, funcname,
 														 alias, nfuncs),
 							   funcrettype,
-							   exprTypmod(funcexpr),
-							   0);
+							   exprTypmod(funcexpr));
 			TupleDescInitEntryCollation(tupdesc,
 										(AttrNumber) 1,
 										exprCollation(funcexpr));
@@ -1919,8 +1918,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 								   (AttrNumber) i,
 								   attrname,
 								   attrtype,
-								   attrtypmod,
-								   0);
+								   attrtypmod);
 				TupleDescInitEntryCollation(tupdesc,
 											(AttrNumber) i,
 											attrcollation);
@@ -1999,8 +1997,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 							   (AttrNumber) ++natts,
 							   "ordinality",
 							   INT8OID,
-							   -1,
-							   0);
+							   -1);
 			/* no need to set collation */
 		}
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 76bf88c3ca..584d700092 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1563,8 +1563,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 			TupleDescInitEntry(tupleDesc, i,
 							   label,
 							   exprType(varnode),
-							   exprTypmod(varnode),
-							   0);
+							   exprTypmod(varnode));
 			TupleDescInitEntryCollation(tupleDesc, i,
 										exprCollation(varnode));
 			i++;
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index c1a937a43b..3a6261c1df 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
+#include "nodes/print.h"
 #include "parser/parse_type.h"
 #include "parser/parser.h"
 #include "utils/array.h"
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 97f957cd87..50e21616b3 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -1193,7 +1193,7 @@ libpqrcv_processTuples(PGresult *pgres, WalRcvExecResult *walres,
 	walres->tupledesc = CreateTemplateTupleDesc(nRetTypes);
 	for (coln = 0; coln < nRetTypes; coln++)
 		TupleDescInitEntry(walres->tupledesc, (AttrNumber) coln + 1,
-						   PQfname(pgres, coln), retTypes[coln], -1, 0);
+						   PQfname(pgres, coln), retTypes[coln], -1);
 	attinmeta = TupleDescGetAttInMetadata(walres->tupledesc);
 
 	/* No point in doing more here if there were no tuples returned. */
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c5f1009f37..bfa6d37799 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -451,13 +451,13 @@ IdentifySystem(void)
 	/* need a tuple descriptor representing four columns */
 	tupdesc = CreateTemplateTupleDesc(4);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "systemid",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "timeline",
-							  INT8OID, -1, 0);
+							  INT8OID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "xlogpos",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "dbname",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 
 	/* prepare for projection of tuples */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
@@ -497,12 +497,12 @@ ReadReplicationSlot(ReadReplicationSlotCmd *cmd)
 
 	tupdesc = CreateTemplateTupleDesc(READ_REPLICATION_SLOT_COLS);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "slot_type",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "restart_lsn",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	/* TimeLineID is unsigned, so int4 is not wide enough. */
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "restart_tli",
-							  INT8OID, -1, 0);
+							  INT8OID, -1);
 
 	memset(nulls, true, READ_REPLICATION_SLOT_COLS * sizeof(bool));
 
@@ -603,8 +603,8 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd)
 	 * the name of the history file, 2nd is the contents.
 	 */
 	tupdesc = CreateTemplateTupleDesc(2);
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "filename", TEXTOID, -1, 0);
-	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "content", TEXTOID, -1, 0);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "filename", TEXTOID, -1);
+	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "content", TEXTOID, -1);
 
 	TLHistoryFileName(histfname, cmd->timeline);
 	TLHistoryFilePath(path, cmd->timeline);
@@ -1017,9 +1017,9 @@ StartReplication(StartReplicationCmd *cmd)
 		 */
 		tupdesc = CreateTemplateTupleDesc(2);
 		TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "next_tli",
-								  INT8OID, -1, 0);
+								  INT8OID, -1);
 		TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "next_tli_startpos",
-								  TEXTOID, -1, 0);
+								  TEXTOID, -1);
 
 		/* prepare for projection of tuple */
 		tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
@@ -1360,13 +1360,13 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	 */
 	tupdesc = CreateTemplateTupleDesc(4);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "slot_name",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "consistent_point",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "snapshot_name",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 4, "output_plugin",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 
 	/* prepare for projection of tuples */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 2a716cc6b7..50174e1c56 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -1794,14 +1794,13 @@ aclexplode(PG_FUNCTION_ARGS)
 		 */
 		tupdesc = CreateTemplateTupleDesc(4);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "grantor",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "grantee",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "privilege_type",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_grantable",
-						   BOOLOID, -1, 0);
-
+						   BOOLOID, -1);
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
 		/* allocate memory for user context */
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 24b95c32b7..a547802d0c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -443,17 +443,17 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 */
 	tupdesc = CreateTemplateTupleDesc(6);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
-					   "size", INT8OID, -1, 0);
+					   "size", INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
-					   "access", TIMESTAMPTZOID, -1, 0);
+					   "access", TIMESTAMPTZOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3,
-					   "modification", TIMESTAMPTZOID, -1, 0);
+					   "modification", TIMESTAMPTZOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4,
-					   "change", TIMESTAMPTZOID, -1, 0);
+					   "change", TIMESTAMPTZOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
-					   "creation", TIMESTAMPTZOID, -1, 0);
+					   "creation", TIMESTAMPTZOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "isdir", BOOLOID, -1);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index e790f856ab..1bc526bf7a 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -114,38 +114,37 @@ pg_lock_status(PG_FUNCTION_ARGS)
 		/* this had better match function's declaration in pg_proc.h */
 		tupdesc = CreateTemplateTupleDesc(NUM_LOCK_STATUS_COLUMNS);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "locktype",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "relation",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "page",
-						   INT4OID, -1, 0);
+						   INT4OID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "tuple",
-						   INT2OID, -1, 0);
+						   INT2OID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 6, "virtualxid",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 7, "transactionid",
-						   XIDOID, -1, 0);
+						   XIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 8, "classid",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 9, "objid",
-						   OIDOID, -1, 0);
+						   OIDOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 10, "objsubid",
-						   INT2OID, -1, 0);
+						   INT2OID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 11, "virtualtransaction",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 12, "pid",
-						   INT4OID, -1, 0);
+						   INT4OID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 13, "mode",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 14, "granted",
-						   BOOLOID, -1, 0);
+						   BOOLOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 15, "fastpath",
-						   BOOLOID, -1, 0);
+						   BOOLOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 16, "waitstart",
-						   TIMESTAMPTZOID, -1, 0);
-
+						   TIMESTAMPTZOID, -1);
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
 		/*
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index 04b3f31827..34cb6d319a 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -230,8 +230,7 @@ ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples)
 								   (AttrNumber) ++natts,
 								   "flag",
 								   INT4OID,
-								   -1,
-								   0);
+								   -1);
 
 				FreeTupleDesc(qstate->tupdesc);
 				qstate->tupdesc = newdesc;
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9c23ac7c8c..c5eb9cd130 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -715,10 +715,9 @@ pg_stat_get_backend_subxact(PG_FUNCTION_ARGS)
 	/* Initialise attributes information in the tuple descriptor */
 	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_SUBXACT_COLS);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "subxact_count",
-					   INT4OID, -1, 0);
+					   INT4OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "subxact_overflow",
-					   BOOLOID, -1, 0);
-
+					   BOOLOID, -1);
 	BlessTupleDesc(tupdesc);
 
 	if ((local_beentry = pgstat_get_local_beentry_by_proc_number(procNumber)) != NULL)
@@ -1478,24 +1477,23 @@ pg_stat_get_wal(PG_FUNCTION_ARGS)
 	/* Initialise attributes information in the tuple descriptor */
 	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_WAL_COLS);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "wal_records",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "wal_fpi",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "wal_bytes",
-					   NUMERICOID, -1, 0);
+					   NUMERICOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "wal_buffers_full",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "wal_write",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "wal_sync",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "wal_write_time",
-					   FLOAT8OID, -1, 0);
+					   FLOAT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "wal_sync_time",
-					   FLOAT8OID, -1, 0);
+					   FLOAT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
-
+					   TIMESTAMPTZOID, -1);
 	BlessTupleDesc(tupdesc);
 
 	/* Get statistics about WAL activity */
@@ -1837,20 +1835,19 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
 	/* Initialise attributes information in the tuple descriptor */
 	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "archived_count",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "last_archived_wal",
-					   TEXTOID, -1, 0);
+					   TEXTOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "last_archived_time",
-					   TIMESTAMPTZOID, -1, 0);
+					   TIMESTAMPTZOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "failed_count",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "last_failed_wal",
-					   TEXTOID, -1, 0);
+					   TEXTOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "last_failed_time",
-					   TIMESTAMPTZOID, -1, 0);
+					   TIMESTAMPTZOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
-
+					   TIMESTAMPTZOID, -1);
 	BlessTupleDesc(tupdesc);
 
 	/* Get statistics about the archiver process */
@@ -1907,25 +1904,25 @@ pg_stat_get_replication_slot(PG_FUNCTION_ARGS)
 	/* Initialise attributes information in the tuple descriptor */
 	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_REPLICATION_SLOT_COLS);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "slot_name",
-					   TEXTOID, -1, 0);
+					   TEXTOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "spill_txns",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "spill_count",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "spill_bytes",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stream_txns",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "stream_count",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stream_bytes",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "total_txns",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "total_bytes",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
+					   TIMESTAMPTZOID, -1);
 	BlessTupleDesc(tupdesc);
 
 	namestrcpy(&slotname, text_to_cstring(slotname_text));
@@ -1981,25 +1978,25 @@ pg_stat_get_subscription_stats(PG_FUNCTION_ARGS)
 	/* Initialise attributes information in the tuple descriptor */
 	tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_SUBSCRIPTION_STATS_COLS);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "subid",
-					   OIDOID, -1, 0);
+					   OIDOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "apply_error_count",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "sync_error_count",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "confl_insert_exists",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "confl_update_origin_differs",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "confl_update_exists",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "confl_update_missing",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "confl_delete_origin_differs",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "confl_delete_missing",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "stats_reset",
-					   TIMESTAMPTZOID, -1, 0);
+					   TIMESTAMPTZOID, -1);
 	BlessTupleDesc(tupdesc);
 
 	if (!subentry)
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index 687adcbd69..3f22aefa66 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -644,11 +644,11 @@ tsvector_unnest(PG_FUNCTION_ARGS)
 
 		tupdesc = CreateTemplateTupleDesc(3);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "lexeme",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "positions",
-						   INT2ARRAYOID, -1, 0);
+						   INT2ARRAYOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "weights",
-						   TEXTARRAYOID, -1, 0);
+						   TEXTARRAYOID, -1);
 		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 			elog(ERROR, "return type must be a row type");
 		funcctx->tuple_desc = tupdesc;
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 05d763fa06..e59fe7496b 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -334,8 +334,7 @@ get_expr_result_type(Node *expr,
 			TupleDescInitEntry(tupdesc, i,
 							   colname,
 							   exprType(col),
-							   exprTypmod(col),
-							   0);
+							   exprTypmod(col));
 			TupleDescInitEntryCollation(tupdesc, i,
 										exprCollation(col));
 			i++;
@@ -978,32 +977,28 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   poly_actuals.anyelement_type,
-								   -1,
-								   0);
+								   -1);
 				TupleDescInitEntryCollation(tupdesc, i + 1, anycollation);
 				break;
 			case ANYARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   poly_actuals.anyarray_type,
-								   -1,
-								   0);
+								   -1);
 				TupleDescInitEntryCollation(tupdesc, i + 1, anycollation);
 				break;
 			case ANYRANGEOID:
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   poly_actuals.anyrange_type,
-								   -1,
-								   0);
+								   -1);
 				/* no collation should be attached to a range type */
 				break;
 			case ANYMULTIRANGEOID:
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   poly_actuals.anymultirange_type,
-								   -1,
-								   0);
+								   -1);
 				/* no collation should be attached to a multirange type */
 				break;
 			case ANYCOMPATIBLEOID:
@@ -1011,32 +1006,28 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   anyc_actuals.anyelement_type,
-								   -1,
-								   0);
+								   -1);
 				TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation);
 				break;
 			case ANYCOMPATIBLEARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   anyc_actuals.anyarray_type,
-								   -1,
-								   0);
+								   -1);
 				TupleDescInitEntryCollation(tupdesc, i + 1, anycompatcollation);
 				break;
 			case ANYCOMPATIBLERANGEOID:
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   anyc_actuals.anyrange_type,
-								   -1,
-								   0);
+								   -1);
 				/* no collation should be attached to a range type */
 				break;
 			case ANYCOMPATIBLEMULTIRANGEOID:
 				TupleDescInitEntry(tupdesc, i + 1,
 								   NameStr(att->attname),
 								   anyc_actuals.anymultirange_type,
-								   -1,
-								   0);
+								   -1);
 				/* no collation should be attached to a multirange type */
 				break;
 			default:
@@ -1849,8 +1840,7 @@ build_function_result_tupdesc_d(char prokind,
 		TupleDescInitEntry(desc, i + 1,
 						   outargnames[i],
 						   outargtypes[i],
-						   -1,
-						   0);
+						   -1);
 	}
 
 	return desc;
@@ -1968,8 +1958,7 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
 						   (AttrNumber) 1,
 						   attname,
 						   typeoid,
-						   -1,
-						   0);
+						   -1);
 	}
 	else if (functypclass == TYPEFUNC_RECORD)
 	{
diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c
index 9c9edd3d2f..43d3ef3f21 100644
--- a/src/backend/utils/misc/guc_funcs.c
+++ b/src/backend/utils/misc/guc_funcs.c
@@ -400,11 +400,11 @@ GetPGVariableResultDesc(const char *name)
 		/* need a tuple descriptor representing three TEXT columns */
 		tupdesc = CreateTemplateTupleDesc(3);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "description",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 	}
 	else
 	{
@@ -416,7 +416,7 @@ GetPGVariableResultDesc(const char *name)
 		/* need a tuple descriptor representing a single TEXT column */
 		tupdesc = CreateTemplateTupleDesc(1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, varname,
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 	}
 	return tupdesc;
 }
@@ -438,7 +438,7 @@ ShowGUCConfigOption(const char *name, DestReceiver *dest)
 	/* need a tuple descriptor representing a single TEXT column */
 	tupdesc = CreateTemplateTupleDesc(1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, varname,
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 
 	/* prepare for projection of tuples */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
@@ -468,11 +468,11 @@ ShowAllGUCConfig(DestReceiver *dest)
 	/* need a tuple descriptor representing three TEXT columns */
 	tupdesc = CreateTemplateTupleDesc(3);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "name",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "setting",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 	TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "description",
-							  TEXTOID, -1, 0);
+							  TEXTOID, -1);
 
 	/* prepare for projection of tuples */
 	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
@@ -874,39 +874,39 @@ show_all_settings(PG_FUNCTION_ARGS)
 		 */
 		tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_ATTS);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "unit",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "category",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 5, "short_desc",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 6, "extra_desc",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 7, "context",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 8, "vartype",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 9, "source",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_val",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 11, "max_val",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 12, "enumvals",
-						   TEXTARRAYOID, -1, 0);
+						   TEXTARRAYOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 13, "boot_val",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 14, "reset_val",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sourcefile",
-						   TEXTOID, -1, 0);
+						   TEXTOID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 16, "sourceline",
-						   INT4OID, -1, 0);
+						   INT4OID, -1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 17, "pending_restart",
-						   BOOLOID, -1, 0);
+						   BOOLOID, -1);
 
 		/*
 		 * Generate attribute metadata needed later to produce tuples from raw
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 8930a28d66..6b8006a1ae 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -133,16 +133,13 @@ extern void TupleDescInitEntry(TupleDesc desc,
 							   AttrNumber attributeNumber,
 							   const char *attributeName,
 							   Oid oidtypeid,
-							   int32 typmod,
-							   int attdim);
+							   int32 typmod);
 
 extern void TupleDescInitBuiltinEntry(TupleDesc desc,
 									  AttrNumber attributeNumber,
 									  const char *attributeName,
 									  Oid oidtypeid,
-									  int32 typmod,
-									  int attdim);
-
+									  int32 typmod);
 extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 1c62b8bfcb..337dee92e0 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -90,11 +90,6 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
 	 */
 	int32		atttypmod BKI_DEFAULT(-1);
 
-	/*
-	 * attndims is the declared number of dimensions, if an array type,
-	 * otherwise zero.
-	 */
-	int16		attndims;
 
 	/*
 	 * attbyval is a copy of the typbyval field from pg_type for this
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e925969732..951e89a538 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -214,12 +214,6 @@ CATALOG(pg_type,1247,TypeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71,TypeRelati
 	 */
 	int32		typtypmod BKI_DEFAULT(-1);
 
-	/*
-	 * typndims is the declared number of dimensions for an array domain type
-	 * (i.e., typbasetype is an array type).  Otherwise zero.
-	 */
-	int32		typndims BKI_DEFAULT(0);
-
 	/*
 	 * Collation: 0 if type cannot use collations, nonzero (typically
 	 * DEFAULT_COLLATION_OID) for collatable base types, possibly some other
@@ -378,7 +372,6 @@ extern ObjectAddress TypeCreate(Oid newTypeOid,
 								char alignment,
 								char storage,
 								int32 typeMod,
-								int32 typNDims,
 								bool typeNotNull,
 								Oid typeCollation);
 
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f1bce708d6..cc41c179df 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1980,8 +1980,8 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
 
 		TupleDescInitEntry(row->rowtupdesc, i + 1,
 						   var->refname,
-						   typoid, typmod,
-						   0);
+						   typoid, typmod);
+
 		TupleDescInitEntryCollation(row->rowtupdesc, i + 1, typcoll);
 	}
 
diff --git a/src/test/modules/injection_points/injection_stats_fixed.c b/src/test/modules/injection_points/injection_stats_fixed.c
index 2fed178b7a..63d7e8424c 100644
--- a/src/test/modules/injection_points/injection_stats_fixed.c
+++ b/src/test/modules/injection_points/injection_stats_fixed.c
@@ -181,15 +181,15 @@ injection_points_stats_fixed(PG_FUNCTION_ARGS)
 	/* Initialise attributes information in the tuple descriptor */
 	tupdesc = CreateTemplateTupleDesc(5);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "numattach",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "numdetach",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "numrun",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "numcached",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "numloaded",
-					   INT8OID, -1, 0);
+					   INT8OID, -1);
 	BlessTupleDesc(tupdesc);
 
 	values[0] = Int64GetDatum(stats->numattach);
diff --git a/src/test/modules/test_predtest/test_predtest.c b/src/test/modules/test_predtest/test_predtest.c
index 678b13ca30..a4daa004c8 100644
--- a/src/test/modules/test_predtest/test_predtest.c
+++ b/src/test/modules/test_predtest/test_predtest.c
@@ -215,21 +215,21 @@ test_predtest(PG_FUNCTION_ARGS)
 
 	tupdesc = CreateTemplateTupleDesc(8);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
-					   "strong_implied_by", BOOLOID, -1, 0);
+					   "strong_implied_by", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
-					   "weak_implied_by", BOOLOID, -1, 0);
+					   "weak_implied_by", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 3,
-					   "strong_refuted_by", BOOLOID, -1, 0);
+					   "strong_refuted_by", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 4,
-					   "weak_refuted_by", BOOLOID, -1, 0);
+					   "weak_refuted_by", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
-					   "s_i_holds", BOOLOID, -1, 0);
+					   "s_i_holds", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "w_i_holds", BOOLOID, -1, 0);
+					   "w_i_holds", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
-					   "s_r_holds", BOOLOID, -1, 0);
+					   "s_r_holds", BOOLOID, -1);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 8,
-					   "w_r_holds", BOOLOID, -1, 0);
+					   "w_r_holds", BOOLOID, -1);
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	values[0] = BoolGetDatum(strong_implied_by);
-- 
2.34.1

#5Junwang Zhao
zhjwpku@gmail.com
In reply to: jian he (#4)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Fri, Sep 20, 2024 at 10:11 AM jian he <jian.universality@gmail.com> wrote:

On Wed, Sep 18, 2024 at 10:35 PM jian he <jian.universality@gmail.com> wrote:

The last time this was discussed, I think the conclusion was
we should remove attndims and typndims entirely on the grounds
that they're useless. I certainly don't see a point in adding
more logic that could give the misleading impression that they
mean something.

attached patch removes attndims and typndims entirely.
some tests skipped in my local my machine, not skipped are all OK.

Should you also bump the catalog version?

--
Regards
Junwang Zhao

#6Michael Paquier
michael@paquier.xyz
In reply to: Junwang Zhao (#5)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Fri, Sep 20, 2024 at 11:51:49AM +0800, Junwang Zhao wrote:

Should you also bump the catalog version?

No need to worry about that when sending a patch because committers
take care of that when merging a patch into the tree. Doing that in
each patch submitted just creates more conflicts and work for patch
authors because they'd need to recolve conflicts each time a
catversion bump happens. And that can happen on a daily basis
sometimes depending on what is committed.
--
Michael

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#6)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Michael Paquier <michael@paquier.xyz> writes:

On Fri, Sep 20, 2024 at 11:51:49AM +0800, Junwang Zhao wrote:

Should you also bump the catalog version?

No need to worry about that when sending a patch because committers
take care of that when merging a patch into the tree. Doing that in
each patch submitted just creates more conflicts and work for patch
authors because they'd need to recolve conflicts each time a
catversion bump happens. And that can happen on a daily basis
sometimes depending on what is committed.

Right. Sometimes the committer forgets to do that :-(, which is
not great but it's not normally a big problem either. We've concluded
it's better to err in that direction than impose additional work
on patch submitters.

If you feel concerned about the point, best practice is to include a
mention that catversion bump is needed in your draft commit message.

regards, tom lane

#8Junwang Zhao
zhjwpku@gmail.com
In reply to: Tom Lane (#7)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Hi Tom and Michael,

On Fri, Sep 20, 2024 at 12:38 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Michael Paquier <michael@paquier.xyz> writes:

On Fri, Sep 20, 2024 at 11:51:49AM +0800, Junwang Zhao wrote:

Should you also bump the catalog version?

No need to worry about that when sending a patch because committers
take care of that when merging a patch into the tree. Doing that in
each patch submitted just creates more conflicts and work for patch
authors because they'd need to recolve conflicts each time a
catversion bump happens. And that can happen on a daily basis
sometimes depending on what is committed.

Right. Sometimes the committer forgets to do that :-(, which is
not great but it's not normally a big problem either. We've concluded
it's better to err in that direction than impose additional work
on patch submitters.

If you feel concerned about the point, best practice is to include a
mention that catversion bump is needed in your draft commit message.

regards, tom lane

Got it, thanks for both of your explanations.

--
Regards
Junwang Zhao

#9Bruce Momjian
bruce@momjian.us
In reply to: jian he (#4)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Fri, Sep 20, 2024 at 10:11:00AM +0800, jian he wrote:

On Wed, Sep 18, 2024 at 10:35 PM jian he <jian.universality@gmail.com> wrote:

The last time this was discussed, I think the conclusion was
we should remove attndims and typndims entirely on the grounds
that they're useless. I certainly don't see a point in adding
more logic that could give the misleading impression that they
mean something.

attached patch removes attndims and typndims entirely.
some tests skipped in my local my machine, not skipped are all OK.

I have been hoping for a patch links this because I feel the existence
of these system columns is deceptive since we don't honor them properly.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

When a patient asks the doctor, "Am I going to die?", he means
"Am I going to die soon?"

#10Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#7)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On 2024-09-20 Fr 12:38 AM, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

On Fri, Sep 20, 2024 at 11:51:49AM +0800, Junwang Zhao wrote:

Should you also bump the catalog version?

No need to worry about that when sending a patch because committers
take care of that when merging a patch into the tree. Doing that in
each patch submitted just creates more conflicts and work for patch
authors because they'd need to recolve conflicts each time a
catversion bump happens. And that can happen on a daily basis
sometimes depending on what is committed.

Right. Sometimes the committer forgets to do that :-(, which is
not great but it's not normally a big problem either. We've concluded
it's better to err in that direction than impose additional work
on patch submitters.

FWIW, I have a git pre-commit hook that helps avoid that. Essentially it
checks to see if there are changes in src/include/catalog but not in
catversion.h. That's not a 100% check, but it probably catches the vast
majority of changes that would require a catversion bump.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#11Kirill Reshke
reshkekirill@gmail.com
In reply to: jian he (#4)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Fri, 20 Sept 2024 at 07:11, jian he <jian.universality@gmail.com> wrote:

attached patch removes attndims and typndims entirely.

LGTM?
I see no open items in this thread. Are there any issues about v1?

--
Best regards,
Kirill Reshke

#12Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Kirill Reshke (#11)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On 2024-Dec-05, Kirill Reshke wrote:

On Fri, 20 Sept 2024 at 07:11, jian he <jian.universality@gmail.com> wrote:

attached patch removes attndims and typndims entirely.

LGTM?
I see no open items in this thread. Are there any issues about v1?

Should we leave TupleDescInitEntry()'s API alone, to avoid breaking the
compilation of every extension in the world? Maybe we could rename the
function (say to TupleDescInitializeEntry()) and use a define?

#define TupleDescInitEntry(a,b,c,d,e,f) TupleDescInitializeEntry(a,b,c,d,e)

Then the amount of churn is reduced, no extensions are broken, and we
can remove the define in a few years.

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"Debido a que la velocidad de la luz es mucho mayor que la del sonido,
algunas personas nos parecen brillantes un minuto antes
de escuchar las pelotudeces que dicen." (Roberto Fontanarrosa)

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#12)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

On 2024-Dec-05, Kirill Reshke wrote:

I see no open items in this thread. Are there any issues about v1?

Should we leave TupleDescInitEntry()'s API alone, to avoid breaking the
compilation of every extension in the world?

I fear the howls of pain from extension authors will be entirely
drowned out by the howls of pain from application authors whose
queries are broken by the disappearance of two catalog columns.
A quick search at codesearch.debian.net shows that while there's
not that many references to attndims, some of them are in such
never-heard-of-it applications as PHP. typndims has some outside
queries fetching it as well.

I don't think we can do this as presented. Maybe we could do
something like constrain the stored value to be only 0 or 1,
but how much does that improve matters?

regards, tom lane

#14Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#13)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 5, 2024 at 11:33:22AM -0500, Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

On 2024-Dec-05, Kirill Reshke wrote:

I see no open items in this thread. Are there any issues about v1?

Should we leave TupleDescInitEntry()'s API alone, to avoid breaking the
compilation of every extension in the world?

I fear the howls of pain from extension authors will be entirely
drowned out by the howls of pain from application authors whose
queries are broken by the disappearance of two catalog columns.
A quick search at codesearch.debian.net shows that while there's
not that many references to attndims, some of them are in such
never-heard-of-it applications as PHP. typndims has some outside
queries fetching it as well.

I don't think we can do this as presented. Maybe we could do
something like constrain the stored value to be only 0 or 1,
but how much does that improve matters?

Well, then the question becomes will we retain useless system columns
forever?

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#14)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Bruce Momjian <bruce@momjian.us> writes:

On Thu, Dec 5, 2024 at 11:33:22AM -0500, Tom Lane wrote:

I don't think we can do this as presented. Maybe we could do
something like constrain the stored value to be only 0 or 1,
but how much does that improve matters?

Well, then the question becomes will we retain useless system columns
forever?

If the choice is "not break PHP" versus "get rid of a vestigial column",
I know which one is the saner choice. There might be things that are
worth breaking PHP for, but this isn't one.

regards, tom lane

#16Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#15)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 5, 2024 at 12:00:30PM -0500, Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

On Thu, Dec 5, 2024 at 11:33:22AM -0500, Tom Lane wrote:

I don't think we can do this as presented. Maybe we could do
something like constrain the stored value to be only 0 or 1,
but how much does that improve matters?

Well, then the question becomes will we retain useless system columns
forever?

If the choice is "not break PHP" versus "get rid of a vestigial column",
I know which one is the saner choice. There might be things that are
worth breaking PHP for, but this isn't one.

I guess I just don't want to get into a situation where we have so many
useless backward-compatible system columns it is distracting.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#17Bruce Momjian
bruce@momjian.us
In reply to: Bruce Momjian (#16)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 5, 2024 at 12:03:30PM -0500, Bruce Momjian wrote:

On Thu, Dec 5, 2024 at 12:00:30PM -0500, Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

On Thu, Dec 5, 2024 at 11:33:22AM -0500, Tom Lane wrote:

I don't think we can do this as presented. Maybe we could do
something like constrain the stored value to be only 0 or 1,
but how much does that improve matters?

Well, then the question becomes will we retain useless system columns
forever?

If the choice is "not break PHP" versus "get rid of a vestigial column",
I know which one is the saner choice. There might be things that are
worth breaking PHP for, but this isn't one.

I guess I just don't want to get into a situation where we have so many
useless backward-compatible system columns it is distracting.

How about if we set attndims & typndims to zero in PG 18, and mention
that these fields are deprecated in the release notes. Then, in a few
years, we can remove the columns since interfaces hopefully would have
removed the useless references to the columns.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#17)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Bruce Momjian <bruce@momjian.us> writes:

How about if we set attndims & typndims to zero in PG 18, and mention
that these fields are deprecated in the release notes. Then, in a few
years, we can remove the columns since interfaces hopefully would have
removed the useless references to the columns.

You have a mighty optimistic view of what will happen. I predict
that if we do step (1), exactly nothing will happen in applications,
and step (2) will remain just as painful for them. (Assuming that
we remember to do step (2), which is no sure thing given our track
record for following through "in a few years".)

regards, tom lane

#19jian he
jian.universality@gmail.com
In reply to: Tom Lane (#18)
1 attachment(s)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

per above discussion:
for TupleDescInitEntry, TupleDescInitBuiltinEntry, I didn't change the
signature of it.
but i added:

if (attdim != 0)
elog(ERROR, "attdim should be zero");

otherwise it may have "unused parameter" warnings?

for the same reason in TypeCreate, I added:
if (typNDims != 0)
elog(ERROR, "typNDims should be zero");

i aslo changed TypeCreate typNDims related comments.
TupleDescInitEntry, TupleDescInitBuiltinEntry comments didn't say much
about attdim.

so overall the change is less pervasive, it would be easier to review.

Attachments:

v2-0001-remove-pg_attribute-attndims-and-pg_type-typndims.patchtext/x-patch; charset=US-ASCII; name=v2-0001-remove-pg_attribute-attndims-and-pg_type-typndims.patchDownload
From 21e1a0eddbfa41ae1239533d80ba9d2081cf0400 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 6 Dec 2024 10:18:07 +0800
Subject: [PATCH v2 1/1] remove pg_attribute attndims and pg_type typndims

discussion: https://postgr.es/m/CACJufxH0RxsxUQnAT2AVG08JFpA3C60L91_cEMM8JQd8P=12ow@mail.gmail.com
commitfest: https://commitfest.postgresql.org/50/5263/
---
 doc/src/sgml/catalogs.sgml          | 23 -----------------------
 src/backend/access/common/tupdesc.c | 17 ++++++-----------
 src/backend/access/gin/ginutil.c    |  2 +-
 src/backend/bootstrap/bootstrap.c   | 11 -----------
 src/backend/catalog/genbki.pl       |  3 ---
 src/backend/catalog/heap.c          |  5 ++---
 src/backend/catalog/index.c         |  1 -
 src/backend/catalog/pg_type.c       |  4 ++--
 src/backend/commands/tablecmds.c    | 13 +------------
 src/backend/commands/typecmds.c     | 21 ++++++++++-----------
 src/include/catalog/pg_attribute.h  |  6 ------
 src/include/catalog/pg_type.h       |  5 -----
 12 files changed, 22 insertions(+), 89 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bf3cee08a9..82322e5737 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1210,17 +1210,6 @@
       </para></entry>
      </row>
 
-     <row>
-      <entry role="catalog_table_entry"><para role="column_definition">
-       <structfield>attndims</structfield> <type>int2</type>
-      </para>
-      <para>
-       Number of dimensions, if the column is an array type; otherwise 0.
-       (Presently, the number of dimensions of an array is not enforced,
-       so any nonzero value effectively means <quote>it's an array</quote>.)
-      </para></entry>
-     </row>
-
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>attbyval</structfield> <type>bool</type>
@@ -9505,18 +9494,6 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
-     <row>
-      <entry role="catalog_table_entry"><para role="column_definition">
-       <structfield>typndims</structfield> <type>int4</type>
-      </para>
-      <para>
-       <structfield>typndims</structfield> is the number of array dimensions
-       for a domain over an array (that is, <structfield>typbasetype</structfield> is
-       an array type).
-       Zero for types other than domains over array types.
-      </para></entry>
-     </row>
-
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>typcollation</structfield> <type>oid</type>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 47379fef22..64dcf33108 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -452,8 +452,6 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (attr1->attlen != attr2->attlen)
 			return false;
-		if (attr1->attndims != attr2->attndims)
-			return false;
 		if (attr1->atttypmod != attr2->atttypmod)
 			return false;
 		if (attr1->attbyval != attr2->attbyval)
@@ -575,9 +573,6 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
  * This is used to check whether two record types are compatible, whether
  * function return row types are the same, and other similar situations.
  *
- * (XXX There was some discussion whether attndims should be checked here, but
- * for now it has been decided not to.)
- *
  * Note: We deliberately do not check the tdtypmod field.  This allows
  * typcache.c to use this routine to see if a cached record type matches a
  * requested type.
@@ -665,9 +660,10 @@ TupleDescInitEntry(TupleDesc desc,
 	Assert(PointerIsValid(desc));
 	Assert(attributeNumber >= 1);
 	Assert(attributeNumber <= desc->natts);
-	Assert(attdim >= 0);
-	Assert(attdim <= PG_INT16_MAX);
+	Assert(attdim == 0);
 
+	if (attdim != 0)
+		elog(ERROR, "attdim should be zero");
 	/*
 	 * initialize the attribute fields
 	 */
@@ -689,7 +685,6 @@ TupleDescInitEntry(TupleDesc desc,
 	att->atttypmod = typmod;
 
 	att->attnum = attributeNumber;
-	att->attndims = attdim;
 
 	att->attnotnull = false;
 	att->atthasdef = false;
@@ -736,8 +731,9 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	Assert(PointerIsValid(desc));
 	Assert(attributeNumber >= 1);
 	Assert(attributeNumber <= desc->natts);
-	Assert(attdim >= 0);
-	Assert(attdim <= PG_INT16_MAX);
+
+	if (attdim != 0)
+		elog(ERROR, "attdim should be zero");
 
 	/* initialize the attribute fields */
 	att = TupleDescAttr(desc, attributeNumber - 1);
@@ -751,7 +747,6 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 	att->atttypmod = typmod;
 
 	att->attnum = attributeNumber;
-	att->attndims = attdim;
 
 	att->attnotnull = false;
 	att->atthasdef = false;
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index 830d67fbc2..dfc493a385 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -121,7 +121,7 @@ initGinState(GinState *state, Relation index)
 			TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
 							   attr->atttypid,
 							   attr->atttypmod,
-							   attr->attndims);
+							   0);
 			TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
 										attr->attcollation);
 		}
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index a5217773ff..b06c3dd23b 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -538,11 +538,6 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 		attrtypes[attnum]->attstorage = Ap->am_typ.typstorage;
 		attrtypes[attnum]->attcompression = InvalidCompressionMethod;
 		attrtypes[attnum]->attcollation = Ap->am_typ.typcollation;
-		/* if an array type, assume 1-dimensional attribute */
-		if (Ap->am_typ.typelem != InvalidOid && Ap->am_typ.typlen < 0)
-			attrtypes[attnum]->attndims = 1;
-		else
-			attrtypes[attnum]->attndims = 0;
 	}
 	else
 	{
@@ -553,12 +548,6 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 		attrtypes[attnum]->attstorage = TypInfo[typeoid].storage;
 		attrtypes[attnum]->attcompression = InvalidCompressionMethod;
 		attrtypes[attnum]->attcollation = TypInfo[typeoid].collation;
-		/* if an array type, assume 1-dimensional attribute */
-		if (TypInfo[typeoid].elem != InvalidOid &&
-			attrtypes[attnum]->attlen < 0)
-			attrtypes[attnum]->attndims = 1;
-		else
-			attrtypes[attnum]->attndims = 0;
 	}
 
 	/*
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 94afdc5491..2f5579fa06 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -947,9 +947,6 @@ sub morph_row_for_pgattr
 	$row->{attalign} = $type->{typalign};
 	$row->{attstorage} = $type->{typstorage};
 
-	# set attndims if it's an array type
-	$row->{attndims} = $type->{typcategory} eq 'A' ? '1' : '0';
-
 	# collation-aware catalog columns must use C collation
 	$row->{attcollation} =
 	  $type->{typcollation} ne '0' ? $C_COLLATION_OID : 0;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d7b88b61dc..83fb91068c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -742,7 +742,6 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 		slot[slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attrs->attnum);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
 		slot[slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(attrs->atttypmod);
-		slot[slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int16GetDatum(attrs->attndims);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(attrs->attbyval);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(attrs->attalign);
 		slot[slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(attrs->attstorage);
@@ -1062,7 +1061,7 @@ AddNewRelationType(const char *typeName,
 				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
 				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
 				   -1,			/* typmod */
-				   0,			/* array dimensions for typBaseType */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* rowtypes never have a collation */
 }
@@ -1385,7 +1384,7 @@ heap_create_with_catalog(const char *relname,
 				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
 				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
 				   -1,			/* typmod */
-				   0,			/* array dimensions for typBaseType */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* rowtypes never have a collation */
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1c3a9e06d3..f1273517bf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -351,7 +351,6 @@ ConstructTupleDescriptor(Relation heapRelation,
 
 			to->atttypid = from->atttypid;
 			to->attlen = from->attlen;
-			to->attndims = from->attndims;
 			to->atttypmod = from->atttypmod;
 			to->attbyval = from->attbyval;
 			to->attalign = from->attalign;
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 395dec8ed8..dafde13971 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -118,7 +118,6 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(false);
 	values[Anum_pg_type_typbasetype - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_type_typtypmod - 1] = Int32GetDatum(-1);
-	values[Anum_pg_type_typndims - 1] = Int32GetDatum(0);
 	values[Anum_pg_type_typcollation - 1] = ObjectIdGetDatum(InvalidOid);
 	nulls[Anum_pg_type_typdefaultbin - 1] = true;
 	nulls[Anum_pg_type_typdefault - 1] = true;
@@ -238,6 +237,8 @@ TypeCreate(Oid newTypeOid,
 	int			i;
 	ObjectAddress address;
 
+	if (typNDims != 0)
+		elog(ERROR, "typNDims should be zero");
 	/*
 	 * We assume that the caller validated the arguments individually, but did
 	 * not check for bad combinations.
@@ -376,7 +377,6 @@ TypeCreate(Oid newTypeOid,
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(typeNotNull);
 	values[Anum_pg_type_typbasetype - 1] = ObjectIdGetDatum(baseType);
 	values[Anum_pg_type_typtypmod - 1] = Int32GetDatum(typeMod);
-	values[Anum_pg_type_typndims - 1] = Int32GetDatum(typNDims);
 	values[Anum_pg_type_typcollation - 1] = ObjectIdGetDatum(typeCollation);
 
 	/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6ccae4cb4a..abdfccf6bd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1335,7 +1335,6 @@ BuildDescForRelation(const List *columns)
 	Oid			atttypid;
 	int32		atttypmod;
 	Oid			attcollation;
-	int			attdim;
 
 	/*
 	 * allocate a new tuple descriptor
@@ -1366,11 +1365,6 @@ BuildDescForRelation(const List *columns)
 			aclcheck_error_type(aclresult, atttypid);
 
 		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
-		attdim = list_length(entry->typeName->arrayBounds);
-		if (attdim > PG_INT16_MAX)
-			ereport(ERROR,
-					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-					errmsg("too many array dimensions"));
 
 		if (entry->typeName->setof)
 			ereport(ERROR,
@@ -1379,7 +1373,7 @@ BuildDescForRelation(const List *columns)
 							attname)));
 
 		TupleDescInitEntry(desc, attnum, attname,
-						   atttypid, atttypmod, attdim);
+						   atttypid, atttypmod, 0);
 		att = TupleDescAttr(desc, attnum - 1);
 
 		/* Override TupleDescInitEntry's settings as requested */
@@ -13769,11 +13763,6 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	attTup->atttypid = targettype;
 	attTup->atttypmod = targettypmod;
 	attTup->attcollation = targetcollid;
-	if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
-		ereport(ERROR,
-				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				errmsg("too many array dimensions"));
-	attTup->attndims = list_length(typeName->arrayBounds);
 	attTup->attlen = tform->typlen;
 	attTup->attbyval = tform->typbyval;
 	attTup->attalign = tform->typalign;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index da591c0922..aa44abfb37 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -599,7 +599,7 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 				   alignment,	/* required alignment */
 				   storage,		/* TOAST strategy */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array Dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   collation);	/* type's collation */
 	Assert(typoid == address.objectId);
@@ -641,7 +641,7 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 			   alignment,		/* see above */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   collation);		/* type's collation */
 
@@ -719,7 +719,6 @@ DefineDomain(CreateDomainStmt *stmt)
 	bool		saw_default = false;
 	bool		typNotNull = false;
 	bool		nullDefined = false;
-	int32		typNDims = list_length(stmt->typeName->arrayBounds);
 	HeapTuple	typeTup;
 	List	   *schema = stmt->constraints;
 	ListCell   *listptr;
@@ -1058,7 +1057,7 @@ DefineDomain(CreateDomainStmt *stmt)
 				   alignment,	/* required alignment */
 				   storage,		/* TOAST strategy */
 				   basetypeMod, /* typeMod value */
-				   typNDims,	/* Array dimensions for base type */
+				   0,			/* no usage, must pass as zero */
 				   typNotNull,	/* Type NOT NULL */
 				   domaincoll); /* type's collation */
 
@@ -1099,7 +1098,7 @@ DefineDomain(CreateDomainStmt *stmt)
 			   alignment,		/* see above */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   domaincoll);		/* type's collation */
 
@@ -1221,7 +1220,7 @@ DefineEnum(CreateEnumStmt *stmt)
 				   TYPALIGN_INT,	/* int alignment */
 				   TYPSTORAGE_PLAIN,	/* TOAST strategy always plain */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation */
 
@@ -1262,7 +1261,7 @@ DefineEnum(CreateEnumStmt *stmt)
 			   TYPALIGN_INT,	/* enums have int align, so do their arrays */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* type's collation */
 
@@ -1563,7 +1562,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   alignment,	/* alignment */
 				   TYPSTORAGE_EXTENDED, /* TOAST strategy (always extended) */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(typoid == InvalidOid || typoid == address.objectId);
@@ -1630,7 +1629,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   alignment,	/* alignment */
 				   'x',			/* TOAST strategy (always extended) */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(multirangeOid == mltrngaddress.objectId);
@@ -1673,7 +1672,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   alignment,		/* alignment - same as range's */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* typcollation */
 
@@ -1712,7 +1711,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   alignment,		/* alignment - same as range's */
 			   'x',				/* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* typcollation */
 
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 1c62b8bfcb..2a6e594930 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -90,12 +90,6 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
 	 */
 	int32		atttypmod BKI_DEFAULT(-1);
 
-	/*
-	 * attndims is the declared number of dimensions, if an array type,
-	 * otherwise zero.
-	 */
-	int16		attndims;
-
 	/*
 	 * attbyval is a copy of the typbyval field from pg_type for this
 	 * attribute.  See atttypid comments above.
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e925969732..7b9d02c0e5 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -214,11 +214,6 @@ CATALOG(pg_type,1247,TypeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71,TypeRelati
 	 */
 	int32		typtypmod BKI_DEFAULT(-1);
 
-	/*
-	 * typndims is the declared number of dimensions for an array domain type
-	 * (i.e., typbasetype is an array type).  Otherwise zero.
-	 */
-	int32		typndims BKI_DEFAULT(0);
 
 	/*
 	 * Collation: 0 if type cannot use collations, nonzero (typically

base-commit: 792b2c7e6d926e61e8ff3b33d3e22d7d74e7a437
-- 
2.34.1

#20Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#18)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 05, 2024 at 06:34:31PM -0500, Tom Lane wrote:

You have a mighty optimistic view of what will happen. I predict
that if we do step (1), exactly nothing will happen in applications,
and step (2) will remain just as painful for them. (Assuming that
we remember to do step (2), which is no sure thing given our track
record for following through "in a few years".)

FWIW, my first thought after reading this paragraph is that you sound
too dramatic here, especially after looking at codesearch to note that
the PHP core code stores attndims but does not actually use it.

Now, I've looked at github and noted that there's a large number of
repositories (in the hundreds) that rely on a zero or non-zero value
for *attndims* to check if they're dealing with an array, saving in
what looks like catalog lookups.

The same lookups done for typndims offer an opposite conclusion:
there's no real code in the open that uses this value for similar
checks, most likely because domains are less used in the wild. So I
get the drama for the removal of attndims and I'd agree to keep it
around, but I also see a very good point in removing typndims.
--
Michael

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#20)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Michael Paquier <michael@paquier.xyz> writes:

FWIW, my first thought after reading this paragraph is that you sound
too dramatic here, especially after looking at codesearch to note that
the PHP core code stores attndims but does not actually use it.

It appeared to me that it fetches it in order to return it in an
application inquiry function [1]https://sources.debian.org/src/php8.2/8.2.26-4/ext/pgsql/pgsql.c/?hl=4245#L4245. So to really gauge the damage,
you'd have to find out how many PHP applications pay attention
to the "array dims" field of pg_meta_data(). Maybe it's a trivial
number, or maybe not --- I didn't have the energy to search.

regards, tom lane

[1]: https://sources.debian.org/src/php8.2/8.2.26-4/ext/pgsql/pgsql.c/?hl=4245#L4245

#22Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#21)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Wed, Dec 11, 2024 at 12:15:51AM -0500, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

FWIW, my first thought after reading this paragraph is that you sound
too dramatic here, especially after looking at codesearch to note that
the PHP core code stores attndims but does not actually use it.

It appeared to me that it fetches it in order to return it in an
application inquiry function [1]. So to really gauge the damage,
you'd have to find out how many PHP applications pay attention
to the "array dims" field of pg_meta_data(). Maybe it's a trivial
number, or maybe not --- I didn't have the energy to search.

A large scale of these applications are not in the open, so the answer
to that is probably a large yes for attndims. Funnily enough, I've
worked on a game in PHP that uses Postgres as backend for some hobby
project, and actually used this field now that I look at this code
again. It was from a long time ago, and that's not in the open, but
you get the idea..
--
Michael

#23jian he
jian.universality@gmail.com
In reply to: Michael Paquier (#22)
1 attachment(s)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

hi.
remove pg_type typndims column patch attached.

Attachments:

v3-0001-remove-pg_type-typndims.patchtext/x-patch; charset=US-ASCII; name=v3-0001-remove-pg_type-typndims.patchDownload
From 656fc3bec597f65368a7bd1cdee183865d4070e7 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Thu, 12 Dec 2024 15:27:34 +0800
Subject: [PATCH v3 1/1] remove pg_type typndims

discussion: https://postgr.es/m/CACJufxH0RxsxUQnAT2AVG08JFpA3C60L91_cEMM8JQd8P=12ow@mail.gmail.com
commitfest: https://commitfest.postgresql.org/50/5263/
---
 doc/src/sgml/catalogs.sgml      | 12 ------------
 src/backend/catalog/heap.c      |  4 ++--
 src/backend/catalog/pg_type.c   |  9 ++++++---
 src/backend/commands/typecmds.c | 21 ++++++++++-----------
 src/include/catalog/pg_type.h   |  5 -----
 5 files changed, 18 insertions(+), 33 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bf3cee08a9..7da826bc3a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9505,18 +9505,6 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
-     <row>
-      <entry role="catalog_table_entry"><para role="column_definition">
-       <structfield>typndims</structfield> <type>int4</type>
-      </para>
-      <para>
-       <structfield>typndims</structfield> is the number of array dimensions
-       for a domain over an array (that is, <structfield>typbasetype</structfield> is
-       an array type).
-       Zero for types other than domains over array types.
-      </para></entry>
-     </row>
-
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>typcollation</structfield> <type>oid</type>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d7b88b61dc..8dc30280a7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1062,7 +1062,7 @@ AddNewRelationType(const char *typeName,
 				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
 				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
 				   -1,			/* typmod */
-				   0,			/* array dimensions for typBaseType */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* rowtypes never have a collation */
 }
@@ -1385,7 +1385,7 @@ heap_create_with_catalog(const char *relname,
 				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
 				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
 				   -1,			/* typmod */
-				   0,			/* array dimensions for typBaseType */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* rowtypes never have a collation */
 
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 395dec8ed8..89c6ef79c9 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -118,7 +118,6 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(false);
 	values[Anum_pg_type_typbasetype - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_type_typtypmod - 1] = Int32GetDatum(-1);
-	values[Anum_pg_type_typndims - 1] = Int32GetDatum(0);
 	values[Anum_pg_type_typcollation - 1] = ObjectIdGetDatum(InvalidOid);
 	nulls[Anum_pg_type_typdefaultbin - 1] = true;
 	nulls[Anum_pg_type_typdefault - 1] = true;
@@ -189,6 +188,9 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
  *		Returns the ObjectAddress assigned to the new type.
  *		If newTypeOid is zero (the normal case), a new OID is created;
  *		otherwise we use exactly that OID.
+ *
+ * 		pg_type.typndims column was removed in version 18.
+ * 		so typNDims parameter must pass as 0.
  * ----------------------------------------------------------------
  */
 ObjectAddress
@@ -221,7 +223,7 @@ TypeCreate(Oid newTypeOid,
 		   char alignment,
 		   char storage,
 		   int32 typeMod,
-		   int32 typNDims,		/* Array dimensions for baseType */
+		   int32 typNDims,
 		   bool typeNotNull,
 		   Oid typeCollation)
 {
@@ -238,6 +240,8 @@ TypeCreate(Oid newTypeOid,
 	int			i;
 	ObjectAddress address;
 
+	if (typNDims != 0)
+		elog(ERROR, "typNDims should be 0");
 	/*
 	 * We assume that the caller validated the arguments individually, but did
 	 * not check for bad combinations.
@@ -376,7 +380,6 @@ TypeCreate(Oid newTypeOid,
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(typeNotNull);
 	values[Anum_pg_type_typbasetype - 1] = ObjectIdGetDatum(baseType);
 	values[Anum_pg_type_typtypmod - 1] = Int32GetDatum(typeMod);
-	values[Anum_pg_type_typndims - 1] = Int32GetDatum(typNDims);
 	values[Anum_pg_type_typcollation - 1] = ObjectIdGetDatum(typeCollation);
 
 	/*
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index da591c0922..aa44abfb37 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -599,7 +599,7 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 				   alignment,	/* required alignment */
 				   storage,		/* TOAST strategy */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array Dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   collation);	/* type's collation */
 	Assert(typoid == address.objectId);
@@ -641,7 +641,7 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 			   alignment,		/* see above */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   collation);		/* type's collation */
 
@@ -719,7 +719,6 @@ DefineDomain(CreateDomainStmt *stmt)
 	bool		saw_default = false;
 	bool		typNotNull = false;
 	bool		nullDefined = false;
-	int32		typNDims = list_length(stmt->typeName->arrayBounds);
 	HeapTuple	typeTup;
 	List	   *schema = stmt->constraints;
 	ListCell   *listptr;
@@ -1058,7 +1057,7 @@ DefineDomain(CreateDomainStmt *stmt)
 				   alignment,	/* required alignment */
 				   storage,		/* TOAST strategy */
 				   basetypeMod, /* typeMod value */
-				   typNDims,	/* Array dimensions for base type */
+				   0,			/* no usage, must pass as zero */
 				   typNotNull,	/* Type NOT NULL */
 				   domaincoll); /* type's collation */
 
@@ -1099,7 +1098,7 @@ DefineDomain(CreateDomainStmt *stmt)
 			   alignment,		/* see above */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   domaincoll);		/* type's collation */
 
@@ -1221,7 +1220,7 @@ DefineEnum(CreateEnumStmt *stmt)
 				   TYPALIGN_INT,	/* int alignment */
 				   TYPSTORAGE_PLAIN,	/* TOAST strategy always plain */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation */
 
@@ -1262,7 +1261,7 @@ DefineEnum(CreateEnumStmt *stmt)
 			   TYPALIGN_INT,	/* enums have int align, so do their arrays */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* type's collation */
 
@@ -1563,7 +1562,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   alignment,	/* alignment */
 				   TYPSTORAGE_EXTENDED, /* TOAST strategy (always extended) */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(typoid == InvalidOid || typoid == address.objectId);
@@ -1630,7 +1629,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   alignment,	/* alignment */
 				   'x',			/* TOAST strategy (always extended) */
 				   -1,			/* typMod (Domains only) */
-				   0,			/* Array dimensions of typbasetype */
+				   0,			/* no usage, must pass as zero */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
 	Assert(multirangeOid == mltrngaddress.objectId);
@@ -1673,7 +1672,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   alignment,		/* alignment - same as range's */
 			   TYPSTORAGE_EXTENDED, /* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* typcollation */
 
@@ -1712,7 +1711,7 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   alignment,		/* alignment - same as range's */
 			   'x',				/* ARRAY is always toastable */
 			   -1,				/* typMod (Domains only) */
-			   0,				/* Array dimensions of typbasetype */
+			   0,				/* no usage, must pass as zero */
 			   false,			/* Type NOT NULL */
 			   InvalidOid);		/* typcollation */
 
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e925969732..7b9d02c0e5 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -214,11 +214,6 @@ CATALOG(pg_type,1247,TypeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71,TypeRelati
 	 */
 	int32		typtypmod BKI_DEFAULT(-1);
 
-	/*
-	 * typndims is the declared number of dimensions for an array domain type
-	 * (i.e., typbasetype is an array type).  Otherwise zero.
-	 */
-	int32		typndims BKI_DEFAULT(0);
 
 	/*
 	 * Collation: 0 if type cannot use collations, nonzero (typically
-- 
2.34.1

#24Michael Paquier
michael@paquier.xyz
In reply to: jian he (#23)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 12, 2024 at 03:40:32PM +0800, jian he wrote:

remove pg_type typndims column patch attached.

FWIW, I have been paying more attention to applications that may use
this attribute and bumped into quite a few cases that use quals based
on (t.typndims > 0 AND t.typbasetype > 0) to check that they're
dealing with domains over array types. So even removing this switch
would be unwise, I am afraid..
--
Michael

#25Bruce Momjian
bruce@momjian.us
In reply to: Michael Paquier (#24)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Mon, Dec 23, 2024 at 01:56:24PM +0900, Michael Paquier wrote:

On Thu, Dec 12, 2024 at 03:40:32PM +0800, jian he wrote:

remove pg_type typndims column patch attached.

FWIW, I have been paying more attention to applications that may use
this attribute and bumped into quite a few cases that use quals based
on (t.typndims > 0 AND t.typbasetype > 0) to check that they're
dealing with domains over array types. So even removing this switch
would be unwise, I am afraid..

Well, I would be more excited about keeping the fields if they actually
were reliable in recording information. This email from November 2023
explains the limitations of attndims:

/messages/by-id/20150707072942.1186.98151@wrigleys.postgresql.org

* dimensions not dumped by pg_dump
* dimensions not propagated by CREATE TABLE ... (LIKE)
* dimensions not propagated by CREATE TABLE AS
* dimensions not displayed by psql

So, if users are referencing attndims and the values are not accurate,
how useful are they? I was suggesting removal so people would stop
relying on inaccurate information, or we correct attndims to be
accurate. Allowing people to continue relying on inaccurate information
seems like the worst of all options.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#26Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#25)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Bruce Momjian <bruce@momjian.us> writes:

So, if users are referencing attndims and the values are not accurate,
how useful are they? I was suggesting removal so people would stop
relying on inaccurate information, or we correct attndims to be
accurate. Allowing people to continue relying on inaccurate information
seems like the worst of all options.

I think removal is not happening. The documentation for attndims
already says

Number of dimensions, if the column is an array type; otherwise
0. (Presently, the number of dimensions of an array is not
enforced, so any nonzero value effectively means “it's an array”.)

and as far as I know that is completely correct. (Hence, your gripe
is not that it's not "accurate", rather that it's not "precise".)

There isn't a comparable disclaimer under typndims, but there probably
should be.

regards, tom lane

#27Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#26)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 26, 2024 at 05:08:36PM -0500, Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

So, if users are referencing attndims and the values are not accurate,
how useful are they? I was suggesting removal so people would stop
relying on inaccurate information, or we correct attndims to be
accurate. Allowing people to continue relying on inaccurate information
seems like the worst of all options.

I think removal is not happening. The documentation for attndims
already says

Number of dimensions, if the column is an array type; otherwise
0. (Presently, the number of dimensions of an array is not
enforced, so any nonzero value effectively means “it's an array”.)

and as far as I know that is completely correct. (Hence, your gripe
is not that it's not "accurate", rather that it's not "precise".)

There isn't a comparable disclaimer under typndims, but there probably
should be.

First, my apologies about the URL; it should have been:

/messages/by-id/ZVwI_ozT8z9MCnIZ@momjian.us

Using the queries in that URL, I see:

CREATE TABLE test (data integer, data_array integer[5][5]);

CREATE TABLE test2 (LIKE test);

CREATE TABLE test3 AS SELECT * FROM test;

SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';

relname | attndims
---------+----------
test | 2
--> test2 | 0
--> test3 | 0

Interestingly, if I dump and restore with:

$ createdb test2; pg_dump test | sql test2

and run the query again I get:

SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';

relname | attndims
---------+----------
test | 1
test2 | 1
test3 | 1

There is clearly something not ideal about this behavior.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#28Michael Paquier
michael@paquier.xyz
In reply to: Bruce Momjian (#27)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Thu, Dec 26, 2024 at 06:07:45PM -0500, Bruce Momjian wrote:

On Thu, Dec 26, 2024 at 05:08:36PM -0500, Tom Lane wrote:

I think removal is not happening. The documentation for attndims
already says

Number of dimensions, if the column is an array type; otherwise
0. (Presently, the number of dimensions of an array is not
enforced, so any nonzero value effectively means “it's an array”.)

and as far as I know that is completely correct. (Hence, your gripe
is not that it's not "accurate", rather that it's not "precise".)

There isn't a comparable disclaimer under typndims, but there probably
should be.

FWIW, basically all application I've seen rely on attndims and typndims to
do non-zero comparison to save in joins with other catalogs.

Using the queries in that URL, I see:

CREATE TABLE test (data integer, data_array integer[5][5]);
CREATE TABLE test2 (LIKE test);
CREATE TABLE test3 AS SELECT * FROM test;
SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';

relname | attndims
---------+----------
test | 2
--> test2 | 0
--> test3 | 0

Ugh. LIKE and CTAS are wrong here with what they add in pg_attribute.

and run the query again I get:

SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';

relname | attndims
---------+----------
test | 1
test2 | 1
test3 | 1

There is clearly something not ideal about this behavior.

Still, these are better post-restore.
--
Michael

#29Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#27)
1 attachment(s)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Bruce Momjian <bruce@momjian.us> writes:

Using the queries in that URL, I see:

CREATE TABLE test (data integer, data_array integer[5][5]);
CREATE TABLE test2 (LIKE test);
CREATE TABLE test3 AS SELECT * FROM test;
SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';
relname | attndims
---------+----------
test | 2
--> test2 | 0
--> test3 | 0

Yeah, that's not great. We don't have the ability to extract a
number-of-dimensions from a result column of a SELECT, but we could
at least take care to make attndims be 1 not 0 for an array type.
And CREATE TABLE LIKE can easily do better. See attached draft.
(We could simplify it a little bit if we decide to store only 1 or 0
in all cases.)

Interestingly, if I dump and restore with:
$ createdb test2; pg_dump test | sql test2
and run the query again I get:
relname | attndims
---------+----------
test | 1
test2 | 1
test3 | 1

I looked at getting a better result here and decided that it didn't
look very promising. pg_dump uses format_type() to build the type
name to put in CREATE TABLE, and that doesn't have access to attndims.

regards, tom lane

Attachments:

v1-preserve-attndims-better.patchtext/x-diff; charset=us-ascii; name=v1-preserve-attndims-better.patchDownload
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 23cecd99c9..13fb6acfcf 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -183,7 +183,8 @@ create_ctas_nodata(List *tlist, IntoClause *into)
 			col = makeColumnDef(colname,
 								exprType((Node *) tle->expr),
 								exprTypmod((Node *) tle->expr),
-								exprCollation((Node *) tle->expr));
+								exprCollation((Node *) tle->expr),
+								-1 /* detect array-ness */ );
 
 			/*
 			 * It's possible that the column is of a collatable type but the
@@ -492,10 +493,17 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		else
 			colname = NameStr(attribute->attname);
 
+		/*
+		 * Note: we pass ndims as -1 not attribute->attndims because (a) the
+		 * tupledesc we are given may not have accurate attndims, and (b) it
+		 * seems best for this path to give results matching
+		 * create_ctas_nodata.
+		 */
 		col = makeColumnDef(colname,
 							attribute->atttypid,
 							attribute->atttypmod,
-							attribute->attcollation);
+							attribute->attcollation,
+							-1 /* detect array-ness */ );
 
 		/*
 		 * It's possible that the column is of a collatable type but the
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index b13ee2b745..d5ca4ed5cb 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -178,15 +178,18 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		switch (i)
 		{
 			case SEQ_COL_LASTVAL:
-				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
+				coldef = makeColumnDef("last_value", INT8OID, -1,
+									   InvalidOid, 0);
 				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
 				break;
 			case SEQ_COL_LOG:
-				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
+				coldef = makeColumnDef("log_cnt", INT8OID, -1,
+									   InvalidOid, 0);
 				value[i - 1] = Int64GetDatum((int64) 0);
 				break;
 			case SEQ_COL_CALLED:
-				coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
+				coldef = makeColumnDef("is_called", BOOLOID, -1,
+									   InvalidOid, 0);
 				value[i - 1] = BoolGetDatum(false);
 				break;
 		}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2420a9558..111d96b896 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2741,7 +2741,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			 * Create new column definition
 			 */
 			newdef = makeColumnDef(attributeName, attribute->atttypid,
-								   attribute->atttypmod, attribute->attcollation);
+								   attribute->atttypmod,
+								   attribute->attcollation,
+								   attribute->attndims);
 			newdef->storage = attribute->attstorage;
 			newdef->generated = attribute->attgenerated;
 			if (CompressionMethodIsValid(attribute->attcompression))
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f0301555e..cd0919a935 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -64,7 +64,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 			ColumnDef  *def = makeColumnDef(tle->resname,
 											exprType((Node *) tle->expr),
 											exprTypmod((Node *) tle->expr),
-											exprCollation((Node *) tle->expr));
+											exprCollation((Node *) tle->expr),
+											-1 /* detect array-ness */ );
 
 			/*
 			 * It's possible that the column is of a collatable type but the
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 007612563c..aad0e78e3d 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -512,15 +512,29 @@ makeTypeNameFromOid(Oid typeOid, int32 typmod)
  *	build a ColumnDef node to represent a simple column definition.
  *
  * Type and collation are specified by OID.
+ * Typmod must be given in numeric form.
+ *
+ * ndims can be positive to select that number of array dimensions,
+ * or 0 if it's not an array, or -1 for this function to detect whether
+ * it's an array type.  (In that case we will declare the column as having a
+ * single array dimension.  This might not accurately reproduce the source of,
+ * say, CREATE TABLE AS; but we don't have the information to do better.)
+ *
  * Other properties are all basic to start with.
  */
 ColumnDef *
-makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
+makeColumnDef(const char *colname, Oid typeOid, int32 typmod,
+			  Oid collOid, int ndims)
 {
 	ColumnDef  *n = makeNode(ColumnDef);
 
 	n->colname = pstrdup(colname);
 	n->typeName = makeTypeNameFromOid(typeOid, typmod);
+	if (ndims < 0)
+		ndims = OidIsValid(get_element_type(typeOid)) ? 1 : 0;
+	while (ndims-- > 0)
+		n->typeName->arrayBounds = lappend(n->typeName->arrayBounds,
+										   makeInteger(-1));
 	n->inhcount = 0;
 	n->is_local = true;
 	n->is_not_null = false;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ca028d2a66..d03bc06f3e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1198,7 +1198,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		 * Create a new column definition
 		 */
 		def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid,
-							attribute->atttypmod, attribute->attcollation);
+							attribute->atttypmod, attribute->attcollation,
+							attribute->attndims);
 
 		/*
 		 * Add to column list
@@ -1637,7 +1638,8 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 			continue;
 
 		n = makeColumnDef(NameStr(attr->attname), attr->atttypid,
-						  attr->atttypmod, attr->attcollation);
+						  attr->atttypmod, attr->attcollation,
+						  attr->attndims);
 		n->is_from_type = true;
 
 		cxt->columns = lappend(cxt->columns, n);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a28..3747805f69 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -75,7 +75,8 @@ extern TypeName *makeTypeNameFromNameList(List *names);
 extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
 
 extern ColumnDef *makeColumnDef(const char *colname,
-								Oid typeOid, int32 typmod, Oid collOid);
+								Oid typeOid, int32 typmod, Oid collOid,
+								int ndims);
 
 extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
 							  Oid funccollid, Oid inputcollid, CoercionForm fformat);
#30Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#29)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Sun, Jan 19, 2025 at 06:47:14PM -0500, Tom Lane wrote:

I looked at getting a better result here and decided that it didn't
look very promising. pg_dump uses format_type() to build the type
name to put in CREATE TABLE, and that doesn't have access to attndims.

Small question on this one: would it be worth adding a check in
sanity_check.sql for bumpy values?
--
Michael

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#30)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Michael Paquier <michael@paquier.xyz> writes:

Small question on this one: would it be worth adding a check in
sanity_check.sql for bumpy values?

Yeah, I didn't bother with a regression test in this draft, but
we should likely have one. Or was your point something different?

regards, tom lane

#32Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#31)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Sun, Jan 19, 2025 at 07:07:03PM -0500, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

Small question on this one: would it be worth adding a check in
sanity_check.sql for bumpy values?

Yeah, I didn't bother with a regression test in this draft, but
we should likely have one. Or was your point something different?

Nope, I was surprised to not see a test. Just suggested a location
that seems adapted with the spirit of the fix.
--
Michael

#33Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#29)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Sun, Jan 19, 2025 at 06:47:14PM -0500, Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

Using the queries in that URL, I see:

CREATE TABLE test (data integer, data_array integer[5][5]);
CREATE TABLE test2 (LIKE test);
CREATE TABLE test3 AS SELECT * FROM test;
SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';
relname | attndims
---------+----------
test | 2
--> test2 | 0
--> test3 | 0

Yeah, that's not great. We don't have the ability to extract a
number-of-dimensions from a result column of a SELECT, but we could

I did write a patch in Novemer 2023 to pass the dimension to the layers
that needed it, but it was considered too much code compared to its
value:

/messages/by-id/ZVwI_ozT8z9MCnIZ@momjian.us

at least take care to make attndims be 1 not 0 for an array type.
And CREATE TABLE LIKE can easily do better. See attached draft.
(We could simplify it a little bit if we decide to store only 1 or 0
in all cases.)

Interestingly, if I dump and restore with:
$ createdb test2; pg_dump test | sql test2
and run the query again I get:
relname | attndims
---------+----------
test | 1
test2 | 1
test3 | 1

I looked at getting a better result here and decided that it didn't
look very promising. pg_dump uses format_type() to build the type
name to put in CREATE TABLE, and that doesn't have access to attndims.

I ran your patch with my tests and it was now consistent in a zero/non-zero
test:

CREATE TABLE test (data integer, data_array integer[5][5]);

CREATE TABLE test2 (LIKE test);

CREATE TABLE test3 AS SELECT * FROM test;

SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';
relname | attndims
---------+----------
test | 2
test2 | 2
test3 | 1

$ createdb test2; pg_dump test | sql test2

test2=>
SELECT relname, attndims
FROM pg_class JOIN pg_attribute ON (pg_attribute.attrelid = pg_class.oid)
WHERE attname = 'data_array';
relname | attndims
---------+----------
test | 1
test2 | 1
test3 | 1

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#34Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#33)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

Bruce Momjian <bruce@momjian.us> writes:

I did write a patch in Novemer 2023 to pass the dimension to the layers
that needed it, but it was considered too much code compared to its
value:
/messages/by-id/ZVwI_ozT8z9MCnIZ@momjian.us

Ah, I'd forgotten that discussion. It looks like the main differences
between your patch and mine are

(1) you did nothing about getting a nonzero attndims value for views
and CREATE TABLE AS cases.

(2) you found a way to get pg_dump to preserve attndims, which you
then also applied to psql's \d.

I'm not really thrilled with (2): it's messy and incomplete, and
I'm not entirely sure it won't break some uses of atttypname within
pg_dump. I do think we should try to get to a place where attndims
is guaranteed nonzero for array-typed columns.

I checked the regression database after applying my patch, and see
that this test works:

regression=# select relname, relkind, attname, attndims, typname from pg_attribute a join pg_type t on (a.atttypid = t.oid) join pg_class c on (a.attrelid = c.oid) where typsubscript = 'array_subscript_handler'::regproc and attndims = 0;
relname | relkind | attname | attndims | typname
---------+---------+---------+----------+---------
(0 rows)

but the reverse case not so much:

regression=# select relname, relkind, attname, attndims, typname from pg_attribute a join pg_type t on (a.atttypid = t.oid) join pg_class c on (a.attrelid = c.oid) where typsubscript != 'array_subscript_handler'::regproc and attndims != 0;
relname | relkind | attname | attndims | typname
------------------+---------+---------+----------+---------
gin_test_idx | i | i | 1 | int4
botharrayidx | i | i | 1 | int4
botharrayidx | i | t | 1 | text
gin_relopts_test | i | i | 1 | int4
(4 rows)

I wonder if we should try to fix the GIN AM to avoid that.
The column being indexed is of an array type in these cases, but the
index entries aren't. It seems inconsistent that it sets up the index
column's attndims and atttypid this way.

regards, tom lane

#35Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#34)
1 attachment(s)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

I wrote:

I wonder if we should try to fix the GIN AM to avoid that.
The column being indexed is of an array type in these cases, but the
index entries aren't. It seems inconsistent that it sets up the index
column's attndims and atttypid this way.

Ah, I see the problem: it's not GIN's fault, it's that index.c's
ConstructTupleDescriptor is very sloppy about setting attndims
in some places and not others. Fortunately it's quite cheap to
fix that, since the places where this is missed already have their
hands on the pg_type entry for the index column's type. See v2
attached (now with a regression test).

regards, tom lane

Attachments:

v2-preserve-attndims-better.patchtext/x-diff; charset=us-ascii; name=v2-preserve-attndims-better.patchDownload
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7377912b41..c2efcb68d2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -382,6 +382,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 			to->atttypid = keyType;
 			to->attlen = typeTup->typlen;
 			to->atttypmod = exprTypmod(indexkey);
+			to->attndims = IsTrueArrayType(typeTup) ? 1 : 0;
 			to->attbyval = typeTup->typbyval;
 			to->attalign = typeTup->typalign;
 			to->attstorage = typeTup->typstorage;
@@ -467,6 +468,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 
 			to->atttypid = keyType;
 			to->atttypmod = -1;
+			to->attndims = IsTrueArrayType(typeTup) ? 1 : 0;
 			to->attlen = typeTup->typlen;
 			to->attbyval = typeTup->typbyval;
 			to->attalign = typeTup->typalign;
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 23cecd99c9..13fb6acfcf 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -183,7 +183,8 @@ create_ctas_nodata(List *tlist, IntoClause *into)
 			col = makeColumnDef(colname,
 								exprType((Node *) tle->expr),
 								exprTypmod((Node *) tle->expr),
-								exprCollation((Node *) tle->expr));
+								exprCollation((Node *) tle->expr),
+								-1 /* detect array-ness */ );
 
 			/*
 			 * It's possible that the column is of a collatable type but the
@@ -492,10 +493,17 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		else
 			colname = NameStr(attribute->attname);
 
+		/*
+		 * Note: we pass ndims as -1 not attribute->attndims because (a) the
+		 * tupledesc we are given may not have accurate attndims, and (b) it
+		 * seems best for this path to give results matching
+		 * create_ctas_nodata.
+		 */
 		col = makeColumnDef(colname,
 							attribute->atttypid,
 							attribute->atttypmod,
-							attribute->attcollation);
+							attribute->attcollation,
+							-1 /* detect array-ness */ );
 
 		/*
 		 * It's possible that the column is of a collatable type but the
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index b13ee2b745..d5ca4ed5cb 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -178,15 +178,18 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 		switch (i)
 		{
 			case SEQ_COL_LASTVAL:
-				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
+				coldef = makeColumnDef("last_value", INT8OID, -1,
+									   InvalidOid, 0);
 				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
 				break;
 			case SEQ_COL_LOG:
-				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
+				coldef = makeColumnDef("log_cnt", INT8OID, -1,
+									   InvalidOid, 0);
 				value[i - 1] = Int64GetDatum((int64) 0);
 				break;
 			case SEQ_COL_CALLED:
-				coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
+				coldef = makeColumnDef("is_called", BOOLOID, -1,
+									   InvalidOid, 0);
 				value[i - 1] = BoolGetDatum(false);
 				break;
 		}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d8f0a99ad9..c0b1de326c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2741,7 +2741,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			 * Create new column definition
 			 */
 			newdef = makeColumnDef(attributeName, attribute->atttypid,
-								   attribute->atttypmod, attribute->attcollation);
+								   attribute->atttypmod,
+								   attribute->attcollation,
+								   attribute->attndims);
 			newdef->storage = attribute->attstorage;
 			newdef->generated = attribute->attgenerated;
 			if (CompressionMethodIsValid(attribute->attcompression))
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6f0301555e..cd0919a935 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -64,7 +64,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
 			ColumnDef  *def = makeColumnDef(tle->resname,
 											exprType((Node *) tle->expr),
 											exprTypmod((Node *) tle->expr),
-											exprCollation((Node *) tle->expr));
+											exprCollation((Node *) tle->expr),
+											-1 /* detect array-ness */ );
 
 			/*
 			 * It's possible that the column is of a collatable type but the
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 007612563c..aad0e78e3d 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -512,15 +512,29 @@ makeTypeNameFromOid(Oid typeOid, int32 typmod)
  *	build a ColumnDef node to represent a simple column definition.
  *
  * Type and collation are specified by OID.
+ * Typmod must be given in numeric form.
+ *
+ * ndims can be positive to select that number of array dimensions,
+ * or 0 if it's not an array, or -1 for this function to detect whether
+ * it's an array type.  (In that case we will declare the column as having a
+ * single array dimension.  This might not accurately reproduce the source of,
+ * say, CREATE TABLE AS; but we don't have the information to do better.)
+ *
  * Other properties are all basic to start with.
  */
 ColumnDef *
-makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
+makeColumnDef(const char *colname, Oid typeOid, int32 typmod,
+			  Oid collOid, int ndims)
 {
 	ColumnDef  *n = makeNode(ColumnDef);
 
 	n->colname = pstrdup(colname);
 	n->typeName = makeTypeNameFromOid(typeOid, typmod);
+	if (ndims < 0)
+		ndims = OidIsValid(get_element_type(typeOid)) ? 1 : 0;
+	while (ndims-- > 0)
+		n->typeName->arrayBounds = lappend(n->typeName->arrayBounds,
+										   makeInteger(-1));
 	n->inhcount = 0;
 	n->is_local = true;
 	n->is_not_null = false;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ca028d2a66..d03bc06f3e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1198,7 +1198,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		 * Create a new column definition
 		 */
 		def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid,
-							attribute->atttypmod, attribute->attcollation);
+							attribute->atttypmod, attribute->attcollation,
+							attribute->attndims);
 
 		/*
 		 * Add to column list
@@ -1637,7 +1638,8 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 			continue;
 
 		n = makeColumnDef(NameStr(attr->attname), attr->atttypid,
-						  attr->atttypmod, attr->attcollation);
+						  attr->atttypmod, attr->attcollation,
+						  attr->attndims);
 		n->is_from_type = true;
 
 		cxt->columns = lappend(cxt->columns, n);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a28..3747805f69 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -75,7 +75,8 @@ extern TypeName *makeTypeNameFromNameList(List *names);
 extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
 
 extern ColumnDef *makeColumnDef(const char *colname,
-								Oid typeOid, int32 typmod, Oid collOid);
+								Oid typeOid, int32 typmod, Oid collOid,
+								int ndims);
 
 extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
 							  Oid funccollid, Oid inputcollid, CoercionForm fformat);
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index e061389135..798448c82b 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -558,8 +558,54 @@ CREATE TABLE ctlt11 (LIKE ctlv1);
 CREATE TABLE ctlt11a (LIKE ctlv1 INCLUDING ALL);
 CREATE TYPE ctlty1 AS (a int, b text);
 CREATE TABLE ctlt12 (LIKE ctlty1);
+/* Check that LIKE propagates attndims correctly */
+CREATE TABLE ctlarr1 (a int, b int[], c int[][]);
+CREATE TABLE ctlt13 (LIKE ctlarr1);
+SELECT attname, atttypid::regtype, attndims
+  FROM pg_attribute
+ WHERE attrelid = 'ctlarr1'::regclass AND attnum > 0
+ORDER BY attnum;
+ attname | atttypid  | attndims 
+---------+-----------+----------
+ a       | integer   |        0
+ b       | integer[] |        1
+ c       | integer[] |        2
+(3 rows)
+
+SELECT attname, atttypid::regtype, attndims
+  FROM pg_attribute
+ WHERE attrelid = 'ctlt13'::regclass AND attnum > 0
+ORDER BY attnum;
+ attname | atttypid  | attndims 
+---------+-----------+----------
+ a       | integer   |        0
+ b       | integer[] |        1
+ c       | integer[] |        2
+(3 rows)
+
+/*
+ * While we're here, check attndims consistency globally.
+ * Since this runs relatively late in the regression order, this will
+ * catch problems in system views and many types of indexes.
+ */
+SELECT relname, attname, attndims, typname
+  FROM pg_attribute a JOIN pg_type t ON (a.atttypid = t.oid)
+       JOIN pg_class c ON (a.attrelid = c.oid)
+ WHERE typsubscript = 'array_subscript_handler'::regproc AND attndims = 0;
+ relname | attname | attndims | typname 
+---------+---------+----------+---------
+(0 rows)
+
+SELECT relname, attname, attndims, typname
+  FROM pg_attribute a JOIN pg_type t ON (a.atttypid = t.oid)
+       JOIN pg_class c ON (a.attrelid = c.oid)
+ WHERE typsubscript != 'array_subscript_handler'::regproc AND attndims != 0;
+ relname | attname | attndims | typname 
+---------+---------+----------+---------
+(0 rows)
+
 DROP SEQUENCE ctlseq1;
 DROP TYPE ctlty1;
 DROP VIEW ctlv1;
-DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12;
+DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12, ctlarr1, ctlt13;
 NOTICE:  table "ctlt10" does not exist, skipping
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index a41f8b83d7..6432ebb0e5 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -221,7 +221,37 @@ CREATE TABLE ctlt11a (LIKE ctlv1 INCLUDING ALL);
 CREATE TYPE ctlty1 AS (a int, b text);
 CREATE TABLE ctlt12 (LIKE ctlty1);
 
+
+/* Check that LIKE propagates attndims correctly */
+
+CREATE TABLE ctlarr1 (a int, b int[], c int[][]);
+CREATE TABLE ctlt13 (LIKE ctlarr1);
+SELECT attname, atttypid::regtype, attndims
+  FROM pg_attribute
+ WHERE attrelid = 'ctlarr1'::regclass AND attnum > 0
+ORDER BY attnum;
+SELECT attname, atttypid::regtype, attndims
+  FROM pg_attribute
+ WHERE attrelid = 'ctlt13'::regclass AND attnum > 0
+ORDER BY attnum;
+
+/*
+ * While we're here, check attndims consistency globally.
+ * Since this runs relatively late in the regression order, this will
+ * catch problems in system views and many types of indexes.
+ */
+SELECT relname, attname, attndims, typname
+  FROM pg_attribute a JOIN pg_type t ON (a.atttypid = t.oid)
+       JOIN pg_class c ON (a.attrelid = c.oid)
+ WHERE typsubscript = 'array_subscript_handler'::regproc AND attndims = 0;
+
+SELECT relname, attname, attndims, typname
+  FROM pg_attribute a JOIN pg_type t ON (a.atttypid = t.oid)
+       JOIN pg_class c ON (a.attrelid = c.oid)
+ WHERE typsubscript != 'array_subscript_handler'::regproc AND attndims != 0;
+
+
 DROP SEQUENCE ctlseq1;
 DROP TYPE ctlty1;
 DROP VIEW ctlv1;
-DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12;
+DROP TABLE IF EXISTS ctlt4, ctlt10, ctlt11, ctlt11a, ctlt12, ctlarr1, ctlt13;
#36Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#34)
Re: attndims, typndims still not enforced, but make the value within a sane threshold

On Tue, Jan 21, 2025 at 03:23:31PM -0500, Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

I did write a patch in Novemer 2023 to pass the dimension to the layers
that needed it, but it was considered too much code compared to its
value:
/messages/by-id/ZVwI_ozT8z9MCnIZ@momjian.us

Ah, I'd forgotten that discussion. It looks like the main differences
between your patch and mine are

(1) you did nothing about getting a nonzero attndims value for views
and CREATE TABLE AS cases.

(2) you found a way to get pg_dump to preserve attndims, which you
then also applied to psql's \d.

Yes, I was more focused on perserving the value, rather than computing
zero/non-zero from the data type.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.