Add default_char_signedness field to ControlFileData.
authorMasahiko Sawada <[email protected]>
Fri, 21 Feb 2025 18:12:08 +0000 (10:12 -0800)
committerMasahiko Sawada <[email protected]>
Fri, 21 Feb 2025 18:12:08 +0000 (10:12 -0800)
The signedness of the 'char' type in C is
implementation-dependent. For instance, 'signed char' is used by
default on x86 CPUs, while 'unsigned char' is used on aarch
CPUs. Previously, we accidentally let C implementation signedness
affect persistent data. This led to inconsistent results when
comparing char data across different platforms.

This commit introduces a new 'default_char_signedness' field in
ControlFileData to store the signedness of the 'char' type. While this
change does not encourage the use of 'char' without explicitly
specifying its signedness, this field can be used as a hint to ensure
consistent behavior for pre-v18 data files that store data sorted by
the 'char' type on disk (e.g., GIN and GiST indexes), especially in
cross-platform replication scenarios.

Newly created database clusters unconditionally set the default char
signedness to true. pg_upgrade (with an upcoming commit) changes this
flag for clusters if the source database cluster has
signedness=false. As a result, signedness=false setting will become
rare over time. If we had known about the problem during the last
development cycle that forced initdb (v8.3), we would have made all
clusters signed or all clusters unsigned. Making pg_upgrade the only
source of signedness=false will cause the population of database
clusters to converge toward that retrospective ideal.

Bump catalog version (for the catalog changes) and PG_CONTROL_VERSION
(for the additions in ControlFileData).

Reviewed-by: Noah Misch <[email protected]>
Discussion: https://postgr.es/m/CB11ADBC-0C3F-4FE0-A678-666EE80CBB07%40amazon.com

doc/src/sgml/func.sgml
src/backend/access/transam/xlog.c
src/backend/utils/misc/pg_controldata.c
src/bin/pg_controldata/pg_controldata.c
src/include/access/xlog.h
src/include/catalog/catversion.h
src/include/catalog/pg_control.h
src/include/catalog/pg_proc.dat

index df32ee0bf5bd9de471519d3dddbfc50dbe6a5783..9f60a476eb30ca65ef13cdf4b7cc5f854dfaf2a3 100644 (file)
@@ -27991,6 +27991,11 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
        <entry><type>integer</type></entry>
       </row>
 
+      <row>
+       <entry><structfield>default_char_signedness</structfield></entry>
+       <entry><type>boolean</type></entry>
+      </row>
+
      </tbody>
     </tgroup>
    </table>
index d10704360a6c842998c109aa5f089c8374bf8dd1..ea1f2d2993c041b1cff39f0606d0cfec81a27ff6 100644 (file)
@@ -4284,6 +4284,33 @@ WriteControlFile(void)
 
    ControlFile->float8ByVal = FLOAT8PASSBYVAL;
 
+   /*
+    * Initialize the default 'char' signedness.
+    *
+    * The signedness of the char type is implementation-defined. For instance
+    * on x86 architecture CPUs, the char data type is typically treated as
+    * signed by default, whereas on aarch architecture CPUs, it is typically
+    * treated as unsigned by default. In v17 or earlier, we accidentally let
+    * C implementation signedness affect persistent data. This led to
+    * inconsistent results when comparing char data across different
+    * platforms.
+    *
+    * This flag can be used as a hint to ensure consistent behavior for
+    * pre-v18 data files that store data sorted by the 'char' type on disk,
+    * especially in cross-platform replication scenarios.
+    *
+    * Newly created database clusters unconditionally set the default char
+    * signedness to true. pg_upgrade changes this flag for clusters that were
+    * initialized on signedness=false platforms. As a result,
+    * signedness=false setting will become rare over time. If we had known
+    * about this problem during the last development cycle that forced initdb
+    * (v8.3), we would have made all clusters signed or all clusters
+    * unsigned. Making pg_upgrade the only source of signedness=false will
+    * cause the population of database clusters to converge toward that
+    * retrospective ideal.
+    */
+   ControlFile->default_char_signedness = true;
+
    /* Contents are protected with a CRC */
    INIT_CRC32C(ControlFile->crc);
    COMP_CRC32C(ControlFile->crc,
@@ -4612,6 +4639,19 @@ DataChecksumsEnabled(void)
    return (ControlFile->data_checksum_version > 0);
 }
 
+/*
+ * Return true if the cluster was initialized on a platform where the
+ * default signedness of char is "signed". This function exists for code
+ * that deals with pre-v18 data files that store data sorted by the 'char'
+ * type on disk (e.g., GIN and GiST indexes). See the comments in
+ * WriteControlFile() for details.
+ */
+bool
+GetDefaultCharSignedness(void)
+{
+   return ControlFile->default_char_signedness;
+}
+
 /*
  * Returns a fake LSN for unlogged relations.
  *
index 9dfba499c13487707b6e0ba00af86b00f7852202..6d036e3bf3280eecf0d9005ca52557f1d964a5d8 100644 (file)
@@ -203,8 +203,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
 Datum
 pg_control_init(PG_FUNCTION_ARGS)
 {
-   Datum       values[11];
-   bool        nulls[11];
+   Datum       values[12];
+   bool        nulls[12];
    TupleDesc   tupdesc;
    HeapTuple   htup;
    ControlFileData *ControlFile;
@@ -254,6 +254,9 @@ pg_control_init(PG_FUNCTION_ARGS)
    values[10] = Int32GetDatum(ControlFile->data_checksum_version);
    nulls[10] = false;
 
+   values[11] = BoolGetDatum(ControlFile->default_char_signedness);
+   nulls[11] = false;
+
    htup = heap_form_tuple(tupdesc, values, nulls);
 
    PG_RETURN_DATUM(HeapTupleGetDatum(htup));
index cf11ab3f2ee3e1a8a7336aaf172ff5d633c11641..bea779eef940db47c02df6165e1bcbe2b7f246b4 100644 (file)
@@ -336,6 +336,8 @@ main(int argc, char *argv[])
           (ControlFile->float8ByVal ? _("by value") : _("by reference")));
    printf(_("Data page checksum version:           %u\n"),
           ControlFile->data_checksum_version);
+   printf(_("Default char data signedness:         %s\n"),
+          (ControlFile->default_char_signedness ? _("signed") : _("unsigned")));
    printf(_("Mock authentication nonce:            %s\n"),
           mock_auth_nonce_str);
    return 0;
index 4411c1468aca0a1bcff626e7a29b527cd5d93119..d313099c027f0af3e76e2a4eb97b706e80702700 100644 (file)
@@ -231,6 +231,7 @@ extern XLogRecPtr GetXLogWriteRecPtr(void);
 extern uint64 GetSystemIdentifier(void);
 extern char *GetMockAuthenticationNonce(void);
 extern bool DataChecksumsEnabled(void);
+extern bool GetDefaultCharSignedness(void);
 extern XLogRecPtr GetFakeLSNForUnloggedRel(void);
 extern Size XLOGShmemSize(void);
 extern void XLOGShmemInit(void);
index 1d609f1af47220a44c61bf38feae239ec39a028c..d179b512be9736406b85140ac43188a8223d294c 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202502211
+#define CATALOG_VERSION_NO 202502212
 
 #endif
index 3797f25b306bd24dd08e6bb2e918dc0487619eea..63e834a6ce4771a38d6ca98c3d8ab454e4fca8da 100644 (file)
@@ -22,7 +22,7 @@
 
 
 /* Version identifier for this pg_control format */
-#define PG_CONTROL_VERSION 1700
+#define PG_CONTROL_VERSION 1800
 
 /* Nonce key length, see below */
 #define MOCK_AUTH_NONCE_LEN        32
@@ -221,6 +221,12 @@ typedef struct ControlFileData
    /* Are data pages protected by checksums? Zero if no checksum version */
    uint32      data_checksum_version;
 
+   /*
+    * True if the default signedness of char is "signed" on a platform where
+    * the cluster is initialized.
+    */
+   bool        default_char_signedness;
+
    /*
     * Random nonce, used in authentication requests that need to proceed
     * based on values that are cluster-unique, like a SASL exchange that
index 9e803d610d7bc7c53a1276c8a1a9c0a5d1aea771..e2d5c0d0886016d9bbc74568d7e40bfa8313627a 100644 (file)
   descr => 'pg_controldata init state information as a function',
   proname => 'pg_control_init', provolatile => 'v', prorettype => 'record',
   proargtypes => '',
-  proallargtypes => '{int4,int4,int4,int4,int4,int4,int4,int4,int4,bool,int4}',
-  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,float8_pass_by_value,data_page_checksum_version}',
+  proallargtypes => '{int4,int4,int4,int4,int4,int4,int4,int4,int4,bool,int4,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,float8_pass_by_value,data_page_checksum_version,default_char_signedness}',
   prosrc => 'pg_control_init' },
 
 # subscripting support for built-in types