agora inbox for pgsql-bugs@postgresql.org  
help / color / mirror / Atom feed
BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
6+ messages / 3 participants
[nested] [flat]

* BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
@ 2026-08-19 03:51 PG Bug reporting form <noreply@postgresql.org>
  2026-08-19 10:46 ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  0 siblings, 1 reply; 6+ messages in thread

From: PG Bug reporting form @ 2026-08-19 03:51 UTC (permalink / raw)
  To: pgsql-bugs@lists.postgresql.org; +Cc: hackerzheng666@gmail.com

The following bug has been logged on the website:

Bug reference:      19631
Logged by:          Zheng Hacker
Email address:      hackerzheng666@gmail.com
PostgreSQL version: 19beta3
Operating system:   Linux x86_64
Description:        

PostgreSQL version: 20devel (commit bdbf662, 2026-08-19)
  OS: Linux x86_64
  
  Calling currtid2() on a view whose SELECT includes ctid in a GROUP BY
  hits elog(ERROR) without errcode() in tid.c, producing SQLSTATE XX000.
  
  Reproducer:
  
  CREATE TABLE tid_tab (a int);
  INSERT INTO tid_tab VALUES (1);
  CREATE VIEW tid_view_with_ctid AS
    SELECT ctid, a FROM tid_tab GROUP BY ctid, a;
  SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid);
  -- ERROR: XX000: currtid cannot handle this view
  -- LOCATION: currtid_for_view, tid.c:435
  
  Note: the view has a ctid column (so it passes the tididx check at
  line 389 which does have a proper errcode), but the GROUP BY prevents
  the code from resolving the TLE to a simple base-table Var, so it
  falls through to the elog(ERROR) at line 435 which lacks errcode().
  
  Expected: a proper SQLSTATE (e.g. 0A000 feature_not_supported).

  Found by automated SQL fuzzing.
  Credit: Zheng Wang, Yanjie Zhao, Yiyang Liu








^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
  2026-08-19 03:51 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 PG Bug reporting form <noreply@postgresql.org>
@ 2026-08-19 10:46 ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-08-21 10:15   ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  0 siblings, 1 reply; 6+ messages in thread

From: Ayush Tiwari @ 2026-08-19 10:46 UTC (permalink / raw)
  To: hackerzheng666@gmail.com; pgsql-bugs@lists.postgresql.org

Hi,

On Wed, 19 Aug 2026 at 14:54, PG Bug reporting form <noreply@postgresql.org>
wrote:

> The following bug has been logged on the website:
>
> Bug reference:      19631
> Logged by:          Zheng Hacker
> Email address:      hackerzheng666@gmail.com
> PostgreSQL version: 19beta3
> Operating system:   Linux x86_64
> Description:
>
> PostgreSQL version: 20devel (commit bdbf662, 2026-08-19)
>   OS: Linux x86_64
>
>   Calling currtid2() on a view whose SELECT includes ctid in a GROUP BY
>   hits elog(ERROR) without errcode() in tid.c, producing SQLSTATE XX000.
>
>   Reproducer:
>
>   CREATE TABLE tid_tab (a int);
>   INSERT INTO tid_tab VALUES (1);
>   CREATE VIEW tid_view_with_ctid AS
>     SELECT ctid, a FROM tid_tab GROUP BY ctid, a;
>   SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid);
>   -- ERROR: XX000: currtid cannot handle this view
>   -- LOCATION: currtid_for_view, tid.c:435
>
>   Note: the view has a ctid column (so it passes the tididx check at
>   line 389 which does have a proper errcode), but the GROUP BY prevents
>   the code from resolving the TLE to a simple base-table Var, so it
>   falls through to the elog(ERROR) at line 435 which lacks errcode().
>
>   Expected: a proper SQLSTATE (e.g. 0A000 feature_not_supported).
>
>   Found by automated SQL fuzzing.
>   Credit: Zheng Wang, Yanjie Zhao, Yiyang Liu
>

Thanks for the reports.  #19629, #19630 and #19631 share one cause: an
error ordinary SQL can reach is raised without an errcode(), so the user
sees XX000.

I scanned the backend for the same pattern, and the attached patch fixes
those three plus four more sites with the same problem: unicode_assigned()
on a non-UTF8 encoding, the pg_control_*() CRC check, a GiST tuple marked
invalid, and ALTER COLLATION ... REFRESH VERSION on "default".

Regards,
Ayush

Attachments:

  [application/octet-stream] v1-0001-Add-missing-SQLSTATEs-to-user-reachable-errors.patch (8.9K, ../../CAJTYsWWpwHsvzKp0JPvGbWttXrOafMCtmGBBQs6bhCKn88fgug@mail.gmail.com/3-v1-0001-Add-missing-SQLSTATEs-to-user-reachable-errors.patch)
  download | inline diff:
From e97a004834be6f83626f5c51003ad2b0f715ce76 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Wed, 19 Aug 2026 15:52:17 +0530
Subject: [PATCH v1] Add missing SQLSTATEs to user-reachable errors

Several errors that ordinary SQL can reach were raised without an error
code, so they were reported as XX000 (internal_error) even though they
signal bad user input, an unsupported request, or corrupted data.
Assign error codes to the ones reachable from SQL, following what
comparable errors already use:

* stat_utils.c: the variadic name/value checks validate user-supplied
  arguments, so use ERRCODE_INVALID_PARAMETER_VALUE, as the rest of the
  file does; the pg_statistic slot limit is
  ERRCODE_PROGRAM_LIMIT_EXCEEDED.  (bug #19629)

* nodeModifyTable.c: a null FOR PORTION OF target is invalid input, not
  an internal error, so use ERRCODE_NULL_VALUE_NOT_ALLOWED.  (bug
  #19630)

* tid.c: currtid_for_view() cannot always resolve the view's ctid to a
  base relation.  The other unsupported-view checks beside it report
  ERRCODE_FEATURE_NOT_SUPPORTED, so match them.  (bug #19631)

* varlena.c: unicode_assigned() rejects non-UTF8 server encodings;
  unicode_normalize() and formatting.c use ERRCODE_SYNTAX_ERROR for the
  same restriction, so match them.

* pg_controldata.c: the pg_control_*() functions raise a control file
  CRC mismatch, which is ERRCODE_DATA_CORRUPTED.

* gist.c: an inner tuple left invalid by a pre-9.1 crash is a corrupt
  index, so use ERRCODE_INDEX_CORRUPTED, like the checks in gistutil.c.

* collationcmds.c: refusing ALTER COLLATION ... REFRESH VERSION for the
  default collation is ERRCODE_WRONG_OBJECT_TYPE.

The messages are unchanged, so no expected output changes.

Bug: #19629
Bug: #19630
Bug: #19631
Reported-by: Zheng Hacker <hackerzheng666@gmail.com>
Reported-by: Zheng Wang
Reported-by: Yanjie Zhao
Reported-by: Yiyang Liu
Discussion: https://postgr.es/m/19629-76babc04b683594d@postgresql.org
Discussion: https://postgr.es/m/19630-9f10ca28426295fa@postgresql.org
Discussion: https://postgr.es/m/19631-b443dd6cd8d4e40b@postgresql.org
---
 src/backend/access/gist/gist.c          |  3 ++-
 src/backend/commands/collationcmds.c    |  3 ++-
 src/backend/executor/nodeModifyTable.c  |  3 ++-
 src/backend/statistics/stat_utils.c     | 10 +++++++---
 src/backend/utils/adt/tid.c             |  4 +++-
 src/backend/utils/adt/varlena.c         |  3 ++-
 src/backend/utils/misc/pg_controldata.c | 12 ++++++++----
 7 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8565e225be7..44597793433 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -762,7 +762,8 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 			 */
 			if (GistTupleIsInvalid(idxtuple))
 				ereport(ERROR,
-						(errmsg("index \"%s\" contains an inner tuple marked as invalid",
+						(errcode(ERRCODE_INDEX_CORRUPTED),
+						 errmsg("index \"%s\" contains an inner tuple marked as invalid",
 								RelationGetRelationName(r)),
 						 errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
 						 errhint("Please REINDEX it.")));
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index cfa0e4610d9..92faa60a750 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -443,7 +443,8 @@ AlterCollation(AlterCollationStmt *stmt)
 
 	if (collOid == DEFAULT_COLLATION_OID)
 		ereport(ERROR,
-				(errmsg("cannot refresh version of default collation"),
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot refresh version of default collation"),
 		/* translator: %s is an SQL command */
 				 errhint("Use %s instead.",
 						 "ALTER DATABASE ... REFRESH COLLATION VERSION")));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ca954729f1e..b33715484ba 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -5645,7 +5645,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		 */
 		if (isNull)
 			ereport(ERROR,
-					(errmsg("FOR PORTION OF target was null")),
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+					 errmsg("FOR PORTION OF target was null")),
 					executor_errposition(estate, forPortionOf->targetLocation));
 
 		/* Create state for FOR PORTION OF operation */
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 5ff37ef4cf8..f911a46bddb 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -370,6 +370,7 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 
 	if (nargs % 2 != 0)
 		ereport(ERROR,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("variadic arguments must be name/value pairs"),
 				errhint("Provide an even number of variadic arguments that can be divided into pairs."));
 
@@ -385,11 +386,13 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 
 		if (argnulls[i])
 			ereport(ERROR,
-					(errmsg("name at variadic position %d is null", i + 1)));
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("name at variadic position %d is null", i + 1)));
 
 		if (types[i] != TEXTOID)
 			ereport(ERROR,
-					(errmsg("name at variadic position %d has type %s, expected type %s",
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("name at variadic position %d has type %s, expected type %s",
 							i + 1, format_type_be(types[i]),
 							format_type_be(TEXTOID))));
 
@@ -654,7 +657,8 @@ statatt_set_slot(Datum *values, bool *nulls, bool *replaces,
 
 	if (slotidx >= STATISTIC_NUM_SLOTS)
 		ereport(ERROR,
-				(errmsg("maximum number of statistics slots exceeded: %d",
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("maximum number of statistics slots exceeded: %d",
 						slotidx + 1)));
 
 	stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index a97873f91ba..9b2cca0db4d 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -432,7 +432,9 @@ currtid_for_view(Relation viewrel, const ItemPointerData *tid)
 			break;
 		}
 	}
-	elog(ERROR, "currtid cannot handle this view");
+	ereport(ERROR,
+			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("currtid cannot handle this view"));
 	return NULL;
 }
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index a09a9e5d5bb..823381073b8 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -5482,7 +5482,8 @@ unicode_assigned(PG_FUNCTION_ARGS)
 
 	if (GetDatabaseEncoding() != PG_UTF8)
 		ereport(ERROR,
-				(errmsg("Unicode categorization can only be performed if server encoding is UTF8")));
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("Unicode categorization can only be performed if server encoding is UTF8")));
 
 	/* convert to char32_t */
 	size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input));
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index ab74d169c96..4fb56aa413c 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -47,7 +47,8 @@ pg_control_system(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = Int32GetDatum(ControlFile->pg_control_version);
 	nulls[0] = false;
@@ -87,7 +88,8 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	/*
 	 * Calculate name of the WAL file containing the latest checkpoint's REDO
@@ -184,7 +186,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = LSNGetDatum(ControlFile->minRecoveryPoint);
 	nulls[0] = false;
@@ -225,7 +228,8 @@ pg_control_init(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = Int32GetDatum(ControlFile->maxAlign);
 	nulls[0] = false;

base-commit: 170c9344defaefb351a9dbd0134f4c37a9847358
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
  2026-08-19 03:51 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 PG Bug reporting form <noreply@postgresql.org>
  2026-08-19 10:46 ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
@ 2026-08-21 10:15   ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-09-12 09:15     ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Pierre Forstmann <pierre.forstmann@gmail.com>
  0 siblings, 1 reply; 6+ messages in thread

From: Ayush Tiwari @ 2026-08-21 10:15 UTC (permalink / raw)
  To: hackerzheng666@gmail.com; pgsql-bugs@lists.postgresql.org

Hi,

On Wed, 19 Aug 2026 at 16:16, Ayush Tiwari <ayushtiwari.slg01@gmail.com>
wrote:

> Hi,
>
> On Wed, 19 Aug 2026 at 14:54, PG Bug reporting form <
> noreply@postgresql.org> wrote:
>
>> The following bug has been logged on the website:
>>
>> Bug reference:      19631
>> Logged by:          Zheng Hacker
>> Email address:      hackerzheng666@gmail.com
>> PostgreSQL version: 19beta3
>> Operating system:   Linux x86_64
>> Description:
>>
>> PostgreSQL version: 20devel (commit bdbf662, 2026-08-19)
>>   OS: Linux x86_64
>>
>>   Calling currtid2() on a view whose SELECT includes ctid in a GROUP BY
>>   hits elog(ERROR) without errcode() in tid.c, producing SQLSTATE XX000.
>>
>>   Reproducer:
>>
>>   CREATE TABLE tid_tab (a int);
>>   INSERT INTO tid_tab VALUES (1);
>>   CREATE VIEW tid_view_with_ctid AS
>>     SELECT ctid, a FROM tid_tab GROUP BY ctid, a;
>>   SELECT currtid2('tid_view_with_ctid'::text, '(0,1)'::tid);
>>   -- ERROR: XX000: currtid cannot handle this view
>>   -- LOCATION: currtid_for_view, tid.c:435
>>
>>   Note: the view has a ctid column (so it passes the tididx check at
>>   line 389 which does have a proper errcode), but the GROUP BY prevents
>>   the code from resolving the TLE to a simple base-table Var, so it
>>   falls through to the elog(ERROR) at line 435 which lacks errcode().
>>
>>   Expected: a proper SQLSTATE (e.g. 0A000 feature_not_supported).
>>
>>   Found by automated SQL fuzzing.
>>   Credit: Zheng Wang, Yanjie Zhao, Yiyang Liu
>>
>
> Thanks for the reports.  #19629, #19630 and #19631 share one cause: an
> error ordinary SQL can reach is raised without an errcode(), so the user
> sees XX000.
>
> I scanned the backend for the same pattern, and the attached patch fixes
> those three plus four more sites with the same problem: unicode_assigned()
> on a non-UTF8 encoding, the pg_control_*() CRC check, a GiST tuple marked
> invalid, and ALTER COLLATION ... REFRESH VERSION on "default".
>

Attaching new patch addressing just #19631 along with
rest files mentioned upthread.

Regards,
Ayush

Attachments:

  [application/octet-stream] v1-0001-Fix-internal-errors-reachable-from-SQL.patch (6.1K, ../../CAJTYsWVYYpxOx29jxrR4SzYMGzJtENr4iQ53NvVE3uzCFtVkrQ@mail.gmail.com/3-v1-0001-Fix-internal-errors-reachable-from-SQL.patch)
  download | inline diff:
From fd0d0d731392bcb2fc154cb18a292909ca301713 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Fri, 21 Aug 2026 15:37:01 +0530
Subject: [PATCH v1] Fix internal errors reachable from SQL

currtid_for_view() ended with an elog() when it could not resolve the
view's ctid column to a base relation, which happens for example when
the view groups by ctid.  Ordinary SQL can reach that, so it should not
be reported as XX000 (internal_error).  The other unsupported-view
checks in the same function report ERRCODE_FEATURE_NOT_SUPPORTED, so
match them.  (bug #19631)

While looking for other instances of the same problem, a few more
errors reachable from SQL turned out to have no error code:

* varlena.c: unicode_assigned() rejects non-UTF8 server encodings;
  unicode_normalize() and formatting.c use ERRCODE_SYNTAX_ERROR for the
  same restriction, so match them.

* pg_controldata.c: the pg_control_*() functions raise a control file
  CRC mismatch, which is ERRCODE_DATA_CORRUPTED.

* gist.c: an inner tuple left invalid by a pre-9.1 crash is a corrupt
  index, so use ERRCODE_INDEX_CORRUPTED, like the checks in gistutil.c.

* collationcmds.c: refusing ALTER COLLATION ... REFRESH VERSION for the
  default collation is ERRCODE_WRONG_OBJECT_TYPE.

No messages change, so there is no expected output churn.

Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Bug: #19631
Reported-by: Zheng Wang <hackerzheng666@gmail.com>
Reported-by: Yanjie Zhao
Reported-by: Yiyang Liu
Discussion: https://postgr.es/m/19631-b443dd6cd8d4e40b@postgresql.org
---
 src/backend/access/gist/gist.c          |  3 ++-
 src/backend/commands/collationcmds.c    |  3 ++-
 src/backend/utils/adt/tid.c             |  4 +++-
 src/backend/utils/adt/varlena.c         |  3 ++-
 src/backend/utils/misc/pg_controldata.c | 12 ++++++++----
 5 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8565e225be7..44597793433 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -762,7 +762,8 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 			 */
 			if (GistTupleIsInvalid(idxtuple))
 				ereport(ERROR,
-						(errmsg("index \"%s\" contains an inner tuple marked as invalid",
+						(errcode(ERRCODE_INDEX_CORRUPTED),
+						 errmsg("index \"%s\" contains an inner tuple marked as invalid",
 								RelationGetRelationName(r)),
 						 errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
 						 errhint("Please REINDEX it.")));
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index cfa0e4610d9..92faa60a750 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -443,7 +443,8 @@ AlterCollation(AlterCollationStmt *stmt)
 
 	if (collOid == DEFAULT_COLLATION_OID)
 		ereport(ERROR,
-				(errmsg("cannot refresh version of default collation"),
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot refresh version of default collation"),
 		/* translator: %s is an SQL command */
 				 errhint("Use %s instead.",
 						 "ALTER DATABASE ... REFRESH COLLATION VERSION")));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index a97873f91ba..9b2cca0db4d 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -432,7 +432,9 @@ currtid_for_view(Relation viewrel, const ItemPointerData *tid)
 			break;
 		}
 	}
-	elog(ERROR, "currtid cannot handle this view");
+	ereport(ERROR,
+			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("currtid cannot handle this view"));
 	return NULL;
 }
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index a09a9e5d5bb..823381073b8 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -5482,7 +5482,8 @@ unicode_assigned(PG_FUNCTION_ARGS)
 
 	if (GetDatabaseEncoding() != PG_UTF8)
 		ereport(ERROR,
-				(errmsg("Unicode categorization can only be performed if server encoding is UTF8")));
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("Unicode categorization can only be performed if server encoding is UTF8")));
 
 	/* convert to char32_t */
 	size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input));
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index ab74d169c96..4fb56aa413c 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -47,7 +47,8 @@ pg_control_system(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = Int32GetDatum(ControlFile->pg_control_version);
 	nulls[0] = false;
@@ -87,7 +88,8 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	/*
 	 * Calculate name of the WAL file containing the latest checkpoint's REDO
@@ -184,7 +186,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = LSNGetDatum(ControlFile->minRecoveryPoint);
 	nulls[0] = false;
@@ -225,7 +228,8 @@ pg_control_init(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = Int32GetDatum(ControlFile->maxAlign);
 	nulls[0] = false;

base-commit: a1bb92fb250a7ff94a13a0e7ac784f88ccc0c402
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
  2026-08-19 03:51 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 PG Bug reporting form <noreply@postgresql.org>
  2026-08-19 10:46 ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-08-21 10:15   ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
@ 2026-09-12 09:15     ` Pierre Forstmann <pierre.forstmann@gmail.com>
  2026-09-12 15:50       ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  0 siblings, 1 reply; 6+ messages in thread

From: Pierre Forstmann @ 2026-09-12 09:15 UTC (permalink / raw)
  To: pgsql-hackers@lists.postgresql.org; +Cc: Ayush Tiwari <ayushtiwari.slg01@gmail.com>

Hello,

I think that the that SQLSTATE chosen in unicode_assigned() is wrong: this not a SQL statement syntax error but more a feature not supported for the current database encoding;
ERRCODE_FEATURE_NOT_SUPPORTED is more appropriate.

Maybe you could add a regression test for tid.c ?

Other modifications look  good to me and more difficult to test automatically.

Regards,
PF

^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
  2026-08-19 03:51 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 PG Bug reporting form <noreply@postgresql.org>
  2026-08-19 10:46 ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-08-21 10:15   ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-09-12 09:15     ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Pierre Forstmann <pierre.forstmann@gmail.com>
@ 2026-09-12 15:50       ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-09-14 16:44         ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Pierre Forstmann <pierre.forstmann@gmail.com>
  0 siblings, 1 reply; 6+ messages in thread

From: Ayush Tiwari @ 2026-09-12 15:50 UTC (permalink / raw)
  To: Pierre Forstmann <pierre.forstmann@gmail.com>; +Cc: pgsql-hackers@lists.postgresql.org

Hi,

Thanks for the review!

On Sat, 12 Sept 2026 at 14:46, Pierre Forstmann
<pierre.forstmann@gmail.com> wrote:
>
> I think that the that SQLSTATE chosen in unicode_assigned() is wrong: this not a SQL statement syntax error but more a feature not supported for the current database encoding;
> ERRCODE_FEATURE_NOT_SUPPORTED is more appropriate.

Hmm, you are right, added that instead.

> Maybe you could add a regression test for tid.c ?

I've added it for now, but personally I don't think it is required.

(PS. This patch is intended for master only)

Attached v2.

Regards,
Ayush

Attachments:

  [application/octet-stream] v2-0001-Fix-internal-errors-reachable-from-SQL.patch (8.1K, ../../CAJTYsWVoc0Gx0m_ocJKhvcJ2=6V_q=JHXwF9-dJvnGBvg402nA@mail.gmail.com/2-v2-0001-Fix-internal-errors-reachable-from-SQL.patch)
  download | inline diff:
From f2c741e5a52cd71268891c3d7877878a2a2ce260 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Fri, 21 Aug 2026 15:37:01 +0530
Subject: [PATCH v2] Fix internal errors reachable from SQL

currtid_for_view() ended with an elog() when it could not resolve the
view's ctid column to a base relation, which happens for example when
the view groups by ctid.  Ordinary SQL can reach that, so it should not
be reported as XX000 (internal_error).  The other unsupported-view
checks in the same function report ERRCODE_FEATURE_NOT_SUPPORTED, so
match them.  (bug #19631)

While looking for other instances of the same problem, a few more
errors reachable from SQL turned out to have no error code:

* varlena.c: unicode_assigned() rejects non-UTF8 server encodings;
  report that as ERRCODE_FEATURE_NOT_SUPPORTED, since the operation is
  not supported for the current database encoding.

* pg_controldata.c: the pg_control_*() functions raise a control file
  CRC mismatch, which is ERRCODE_DATA_CORRUPTED.

* gist.c: an inner tuple left invalid by a pre-9.1 crash is a corrupt
  index, so use ERRCODE_INDEX_CORRUPTED, like the checks in gistutil.c.

* collationcmds.c: refusing ALTER COLLATION ... REFRESH VERSION for the
  default collation is ERRCODE_WRONG_OBJECT_TYPE.

Add regression coverage for the currtid_for_view() fallback, using
SQLSTATE-only error verbosity to verify that it reports 0A000 rather
than XX000.  No error messages change.

Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Bug: #19631
Reported-by: Zheng Wang <hackerzheng666@gmail.com>
Reported-by: Yanjie Zhao
Reported-by: Yiyang Liu
Discussion: https://postgr.es/m/19631-b443dd6cd8d4e40b@postgresql.org
---
Changes in v2:
* Use ERRCODE_FEATURE_NOT_SUPPORTED for unicode_assigned().
* Add a regression test for the currtid_for_view() error code.

 src/backend/access/gist/gist.c          |  3 ++-
 src/backend/commands/collationcmds.c    |  3 ++-
 src/backend/utils/adt/tid.c             |  4 +++-
 src/backend/utils/adt/varlena.c         |  3 ++-
 src/backend/utils/misc/pg_controldata.c | 12 ++++++++----
 src/test/regress/expected/tid.out       |  8 ++++++++
 src/test/regress/sql/tid.sql            |  7 +++++++
 7 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8565e225be7..44597793433 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -762,7 +762,8 @@ gistdoinsert(Relation r, IndexTuple itup, Size freespace,
 			 */
 			if (GistTupleIsInvalid(idxtuple))
 				ereport(ERROR,
-						(errmsg("index \"%s\" contains an inner tuple marked as invalid",
+						(errcode(ERRCODE_INDEX_CORRUPTED),
+						 errmsg("index \"%s\" contains an inner tuple marked as invalid",
 								RelationGetRelationName(r)),
 						 errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
 						 errhint("Please REINDEX it.")));
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index cfa0e4610d9..92faa60a750 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -443,7 +443,8 @@ AlterCollation(AlterCollationStmt *stmt)
 
 	if (collOid == DEFAULT_COLLATION_OID)
 		ereport(ERROR,
-				(errmsg("cannot refresh version of default collation"),
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot refresh version of default collation"),
 		/* translator: %s is an SQL command */
 				 errhint("Use %s instead.",
 						 "ALTER DATABASE ... REFRESH COLLATION VERSION")));
diff --git a/src/backend/utils/adt/tid.c b/src/backend/utils/adt/tid.c
index a97873f91ba..9b2cca0db4d 100644
--- a/src/backend/utils/adt/tid.c
+++ b/src/backend/utils/adt/tid.c
@@ -432,7 +432,9 @@ currtid_for_view(Relation viewrel, const ItemPointerData *tid)
 			break;
 		}
 	}
-	elog(ERROR, "currtid cannot handle this view");
+	ereport(ERROR,
+			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("currtid cannot handle this view"));
 	return NULL;
 }
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index f6a41e709ae..1288705baeb 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -5482,7 +5482,8 @@ unicode_assigned(PG_FUNCTION_ARGS)
 
 	if (GetDatabaseEncoding() != PG_UTF8)
 		ereport(ERROR,
-				(errmsg("Unicode categorization can only be performed if server encoding is UTF8")));
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Unicode categorization can only be performed if server encoding is UTF8")));
 
 	/* convert to char32_t */
 	size = pg_mbstrlen_with_len(VARDATA_ANY(input), VARSIZE_ANY_EXHDR(input));
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index 1f47302fe2a..4eac0b33cd1 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -47,7 +47,8 @@ pg_control_system(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = Int32GetDatum(ControlFile->pg_control_version);
 	nulls[0] = false;
@@ -87,7 +88,8 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	/*
 	 * Calculate name of the WAL file containing the latest checkpoint's REDO
@@ -184,7 +186,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = LSNGetDatum(ControlFile->minRecoveryPoint);
 	nulls[0] = false;
@@ -225,7 +228,8 @@ pg_control_init(PG_FUNCTION_ARGS)
 	LWLockRelease(ControlFileLock);
 	if (!crc_ok)
 		ereport(ERROR,
-				(errmsg("calculated CRC checksum does not match value stored in file")));
+				(errcode(ERRCODE_DATA_CORRUPTED),
+				 errmsg("calculated CRC checksum does not match value stored in file")));
 
 	values[0] = Int32GetDatum(ControlFile->maxAlign);
 	nulls[0] = false;
diff --git a/src/test/regress/expected/tid.out b/src/test/regress/expected/tid.out
index 45cc61fb1ca..d2dc23fbdeb 100644
--- a/src/test/regress/expected/tid.out
+++ b/src/test/regress/expected/tid.out
@@ -192,4 +192,12 @@ CREATE VIEW tid_view_fake_ctid AS SELECT 1 AS ctid, 2 AS a;
 SELECT currtid2('tid_view_fake_ctid'::text, '(0,1)'::tid); -- fails
 ERROR:  ctid isn't of type TID
 DROP VIEW tid_view_fake_ctid;
+-- ctid cannot be traced to a base relation
+CREATE VIEW tid_view_grouped_ctid AS
+  SELECT ctid, a FROM tid_tab GROUP BY ctid, a;
+\set VERBOSITY sqlstate
+SELECT currtid2('tid_view_grouped_ctid'::text, '(0,1)'::tid); -- fails
+ERROR:  0A000
+\set VERBOSITY default
+DROP VIEW tid_view_grouped_ctid;
 DROP TABLE tid_tab CASCADE;
diff --git a/src/test/regress/sql/tid.sql b/src/test/regress/sql/tid.sql
index 51d00b92074..5176503951b 100644
--- a/src/test/regress/sql/tid.sql
+++ b/src/test/regress/sql/tid.sql
@@ -91,5 +91,12 @@ TRUNCATE tid_tab;
 CREATE VIEW tid_view_fake_ctid AS SELECT 1 AS ctid, 2 AS a;
 SELECT currtid2('tid_view_fake_ctid'::text, '(0,1)'::tid); -- fails
 DROP VIEW tid_view_fake_ctid;
+-- ctid cannot be traced to a base relation
+CREATE VIEW tid_view_grouped_ctid AS
+  SELECT ctid, a FROM tid_tab GROUP BY ctid, a;
+\set VERBOSITY sqlstate
+SELECT currtid2('tid_view_grouped_ctid'::text, '(0,1)'::tid); -- fails
+\set VERBOSITY default
+DROP VIEW tid_view_grouped_ctid;
 
 DROP TABLE tid_tab CASCADE;

base-commit: 5624de9904c29224737574f039ba073bc02fba82
-- 
2.34.1


^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000
  2026-08-19 03:51 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 PG Bug reporting form <noreply@postgresql.org>
  2026-08-19 10:46 ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-08-21 10:15   ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
  2026-09-12 09:15     ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Pierre Forstmann <pierre.forstmann@gmail.com>
  2026-09-12 15:50       ` Re: BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 Ayush Tiwari <ayushtiwari.slg01@gmail.com>
@ 2026-09-14 16:44         ` Pierre Forstmann <pierre.forstmann@gmail.com>
  0 siblings, 0 replies; 6+ messages in thread

From: Pierre Forstmann @ 2026-09-14 16:44 UTC (permalink / raw)
  To: pgsql-hackers@lists.postgresql.org; +Cc: Ayush Tiwari <ayushtiwari.slg01@gmail.com>

The following review has been posted through the commitfest application:
make installcheck-world:  tested, passed
Implements feature:       tested, passed
Spec compliant:           not tested
Documentation:            not tested

Hello,

I have reviewed v2 of patch and it looks good to me.

There is no SQL change: no need to test SQL compliance.

I don't think that documentation should be updated.

Regards,
PF.

The new status of this patch is: Ready for Committer


^ permalink  raw  reply  [nested|flat] 6+ messages in thread


end of thread, other threads:[~2026-09-14 16:44 UTC | newest]

Thread overview: 6+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-08-19 03:51 BUG #19631: currtid2() on a view with GROUP BY ctid crashes with XX000 PG Bug reporting form <noreply@postgresql.org>
2026-08-19 10:46 ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
2026-08-21 10:15   ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
2026-09-12 09:15     ` Pierre Forstmann <pierre.forstmann@gmail.com>
2026-09-12 15:50       ` Ayush Tiwari <ayushtiwari.slg01@gmail.com>
2026-09-14 16:44         ` Pierre Forstmann <pierre.forstmann@gmail.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox