pg_upgrade: Add --set-char-signedness to set the default char signedness of new cluster.
authorMasahiko Sawada <msawada@postgresql.org>
Fri, 21 Feb 2025 18:23:39 +0000 (10:23 -0800)
committerMasahiko Sawada <msawada@postgresql.org>
Fri, 21 Feb 2025 18:23:39 +0000 (10:23 -0800)
This change adds a new option --set-char-signedness to pg_upgrade. It
enables user to set arbitrary signedness during pg_upgrade. This helps
cases where user who knew they copied the v17 source cluster from
x86 (signedness=true) to ARM (signedness=false) can pg_upgrade
properly without the prerequisite of acquiring an x86 VM.

Reviewed-by: Noah Misch <noah@leadboat.com>
Discussion: https://postgr.es/m/CB11ADBC-0C3F-4FE0-A678-666EE80CBB07%40amazon.com

doc/src/sgml/ref/pgupgrade.sgml
src/bin/pg_upgrade/check.c
src/bin/pg_upgrade/option.c
src/bin/pg_upgrade/pg_upgrade.c
src/bin/pg_upgrade/pg_upgrade.h
src/bin/pg_upgrade/t/005_char_signedness.pl

index 4d9ca2a561667c746f9bee17b144f02f2621794f..6f29ffad76baa3a274b37b2ff9948dfbe4e58aee 100644 (file)
@@ -285,6 +285,59 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--set-char-signedness=</option><replaceable>option</replaceable></term>
+      <listitem>
+       <para>
+        Manually set the default char signedness of new clusters. Possible values
+        are <literal>signed</literal> and <literal>unsigned</literal>.
+       </para>
+       <para>
+        In the C language, the default signedness of the <type>char</type> type
+        (when not explicitly specified) varies across platforms. For example,
+        <type>char</type> defaults to <type>signed char</type> on x86 CPUs but
+        to <type>unsigned char</type> on ARM CPUs.
+       </para>
+       <para>
+        Starting from <productname>PostgreSQL</productname> 18, database clusters
+        maintain their own default char signedness setting, which can be used to
+        ensure consistent behavior across platforms with different default char
+        signedness. By default, <application>pg_upgrade</application> preserves
+        the char signedness setting when upgrading from an existing cluster.
+        However, when upgrading from <productname>PostgreSQL</productname> 17 or
+        earlier, <application>pg_upgrade</application> adopts the char signedness
+        of the platform on which it was built.
+       </para>
+       <para>
+        This option allows you to explicitly set the default char signedness for
+        the new cluster, overriding any inherited values. There are two specific
+        scenarios where this option is relevant:
+        <itemizedlist>
+         <listitem>
+          <para>
+           If you are planning to migrate to a different platform after the upgrade,
+           you should not use this option. The default behavior is right in this case.
+           Instead, perform the upgrade on the original platform without this flag,
+           and then migrate the cluster afterward. This is the recommended and safest
+           approach.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           If you have already migrated the cluster to a platform with different
+           char signedness (for example, from an x86-based system to an ARM-based
+           system), you should use this option to specify the signedness matching
+           the original platform's default char signedness. Additionally, it's
+           essential not to modify any data files between migrating data files and
+           running <command>pg_upgrade</command>. <command>pg_upgrade</command>
+           should be the first operation that starts the cluster on the new platform.
+          </para>
+         </listitem>
+        </itemizedlist>
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-?</option></term>
       <term><option>--help</option></term>
index 7ca1d8fffc9f7b1ea9ddc58b9b238693b6413c90..d6f629dd3a2bd411cad9e3477275006ba87c8dfb 100644 (file)
@@ -838,6 +838,18 @@ check_cluster_versions(void)
        GET_MAJOR_VERSION(new_cluster.bin_version))
        pg_fatal("New cluster data and binary directories are from different major versions.");
 
+   /*
+    * Since from version 18, newly created database clusters always have
+    * 'signed' default char-signedness, it makes less sense to use
+    * --set-char-signedness option for upgrading from version 18 or later.
+    * Users who want to change the default char signedness of the new
+    * cluster, they can use pg_resetwal manually before the upgrade.
+    */
+   if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1800 &&
+       user_opts.char_signedness != -1)
+       pg_fatal("%s option cannot be used to upgrade from PostgreSQL %s and later.",
+                "--set-char-signedness", "18");
+
    check_ok();
 }
 
index 3fd487086a5c70b05ada1766f92d5e088231335a..fe716c4c80517fd64d2442c2c788f80daaf69ca7 100644 (file)
@@ -61,6 +61,7 @@ parseCommandLine(int argc, char *argv[])
        {"copy-file-range", no_argument, NULL, 3},
        {"sync-method", required_argument, NULL, 4},
        {"no-statistics", no_argument, NULL, 5},
+       {"set-char-signedness", required_argument, NULL, 6},
 
        {NULL, 0, NULL, 0}
    };
@@ -72,6 +73,7 @@ parseCommandLine(int argc, char *argv[])
    user_opts.do_sync = true;
    user_opts.transfer_mode = TRANSFER_MODE_COPY;
    user_opts.do_statistics = true;
+   user_opts.char_signedness = -1;
 
    os_info.progname = get_progname(argv[0]);
 
@@ -218,6 +220,14 @@ parseCommandLine(int argc, char *argv[])
                user_opts.do_statistics = false;
                break;
 
+           case 6:
+               if (pg_strcasecmp(optarg, "signed") == 0)
+                   user_opts.char_signedness = 1;
+               else if (pg_strcasecmp(optarg, "unsigned") == 0)
+                   user_opts.char_signedness = 0;
+               else
+                   pg_fatal("invalid argument for option %s", "--set-char-signedness");
+               break;
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
                        os_info.progname);
@@ -313,6 +323,8 @@ usage(void)
    printf(_("  --copy                        copy files to new cluster (default)\n"));
    printf(_("  --copy-file-range             copy files to new cluster with copy_file_range\n"));
    printf(_("  --no-statistics               do not import statistics from old cluster\n"));
+   printf(_("  --set-char-signedness=OPTION  set new cluster char signedness to \"signed\" or\n"));
+   printf(_("                                \"unsigned\"\n"));
    printf(_("  --sync-method=METHOD          set method for syncing files to disk\n"));
    printf(_("  -?, --help                    show this help, then exit\n"));
    printf(_("\n"
index cc7357b55996ea10f9e3995592580d55c6077b03..d95c491fb57e705b40521c9f615f0acfd5a15f42 100644 (file)
@@ -399,8 +399,14 @@ set_new_cluster_char_signedness(void)
 {
    bool        new_char_signedness;
 
-   /* Inherit the source database's signedness */
-   new_char_signedness = old_cluster.controldata.default_char_signedness;
+   /*
+    * Use the specified char signedness if specified. Otherwise we inherit
+    * the source database's signedness.
+    */
+   if (user_opts.char_signedness != -1)
+       new_char_signedness = (user_opts.char_signedness == 1);
+   else
+       new_char_signedness = old_cluster.controldata.default_char_signedness;
 
    /* Change the char signedness of the new cluster, if necessary */
    if (new_cluster.controldata.default_char_signedness != new_char_signedness)
index 6937d49b5e76b90222496a3369eef82bdd71d9a3..f4e375d27c78225f87544a439cffd72c9d47b3d3 100644 (file)
@@ -334,6 +334,9 @@ typedef struct
    char       *socketdir;      /* directory to use for Unix sockets */
    char       *sync_method;
    bool        do_statistics;  /* carry over statistics from old cluster */
+   int         char_signedness;    /* default char signedness: -1 for initial
+                                    * value, 1 for "signed" and 0 for
+                                    * "unsigned" */
 } UserOpts;
 
 typedef struct
index 05c3014a27d66589c12c4f98ebeecd145a2ecd3a..c024106863ebc1b51180b36e6a9c63c86341e724 100644 (file)
@@ -40,6 +40,23 @@ command_like(
    qr/Default char data signedness:\s+unsigned/,
    'updated default char signedness is unsigned in control file');
 
+# Cannot use --set-char-signedness option for upgrading from v18+
+command_fails(
+   [
+       'pg_upgrade', '--no-sync',
+       '-d', $old->data_dir,
+       '-D', $new->data_dir,
+       '-b', $old->config_data('--bindir'),
+       '-B', $new->config_data('--bindir'),
+       '-s', $new->host,
+       '-p', $old->port,
+       '-P', $new->port,
+       '-set-char-signedness', 'signed',
+       $mode
+   ],
+   '--set-char-signedness option cannot be used for upgrading from v18 or later'
+);
+
 # pg_upgrade should be successful.
 command_ok(
    [