From 28591acf281de5c11e5910fc12f6699ac1a96452 Mon Sep 17 00:00:00 2001 From: Filip Janus Date: Mon, 26 Jan 2026 11:13:57 +0100 Subject: [PATCH 1/2] Auto-detect locale from old cluster during upgrade Automatically detect and preserve locale settings (lc_collate, lc_ctype) from the old PostgreSQL cluster during upgrade operations. This fixes upgrade failures when system locale has changed between initialization and upgrade. The implementation queries locale from the old cluster's template1 database, temporarily starting the server if needed. Detected locale values are automatically passed to initdb via PGSETUP_INITDB_OPTIONS. Fixes: Bug 1152556 --- bin/postgresql-setup.in | 139 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/bin/postgresql-setup.in b/bin/postgresql-setup.in index 9626dd9..a99046e 100644 --- a/bin/postgresql-setup.in +++ b/bin/postgresql-setup.in @@ -213,6 +213,112 @@ old_data_in_use() } +# Detect locale settings from old cluster datadir +# Sets: old_cluster_lc_collate, old_cluster_lc_ctype, old_cluster_locale +detect_old_cluster_locale() +{ + local old_datadir="$1" + local old_bindir="$2" + local old_port="$3" + + old_cluster_lc_collate="" + old_cluster_lc_ctype="" + old_cluster_locale="" + + debug "detecting locale from old cluster at $old_datadir" + + # Try to get locale from running database using psql + # First check if server is running + local pidfile="$old_datadir/postmaster.pid" + local server_was_running=false + local temp_server_started=false + + if [ -f "$pidfile" ]; then + # Check if the PID in the file is actually running + local pid=$(head -n 1 "$pidfile" 2>/dev/null) + if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then + server_was_running=true + debug "old cluster server is running (PID: $pid), querying locale via psql" + fi + fi + + # If server is not running, try to start it temporarily + if ! $server_was_running; then + debug "old cluster server is not running, attempting temporary start to query locale" + + # Create a temporary socket directory + local temp_sockdir=$(mktemp -d 2>/dev/null) + if [ $? -ne 0 ] || [ -z "$temp_sockdir" ]; then + debug "failed to create temporary socket directory" + return 1 + fi + + local temp_log="$old_datadir/temp_locale_query.log" + + # Try to start server with minimal configuration + # Use a different port to avoid conflicts + local temp_port=$((old_port + 10000)) + + # Start server in background with minimal settings + # Use -m fast to speed up startup + if "$old_bindir/pg_ctl" -D "$old_datadir" -o "-p $temp_port -c listen_addresses='' -c unix_socket_directories='$temp_sockdir' -c max_connections=5 -c shared_buffers=128kB" start -w -l "$temp_log" -m fast >/dev/null 2>&1; then + temp_server_started=true + debug "temporarily started old cluster server on port $temp_port" + # Give it a moment to be ready + sleep 2 + else + debug "failed to start old cluster server temporarily (see $temp_log for details)" + rm -rf "$temp_sockdir" + rm -f "$temp_log" + return 1 + fi + fi + + # Query locale from template1 database + local query="SELECT datcollate, datctype FROM pg_catalog.pg_database WHERE datname = 'template1'" + local result + local query_port="$old_port" + if $temp_server_started; then + query_port="$temp_port" + fi + + # Use appropriate connection method + local psql_exit_code=0 + if $temp_server_started; then + result=$("$old_bindir/psql" -t -A -h "$temp_sockdir" -p "$query_port" -d template1 -c "$query" 2>/dev/null) + psql_exit_code=$? + else + result=$("$old_bindir/psql" -t -A -p "$query_port" -d template1 -c "$query" 2>/dev/null) + psql_exit_code=$? + fi + + # Stop temporary server if we started it + if $temp_server_started; then + "$old_bindir/pg_ctl" -D "$old_datadir" stop >/dev/null 2>&1 + rm -rf "$temp_sockdir" + rm -f "$temp_log" + fi + + # Parse result + if [ $psql_exit_code -eq 0 ] && [ -n "$result" ]; then + # Parse result: datcollate|datctype + old_cluster_lc_collate=$(echo "$result" | cut -d'|' -f1 | tr -d ' ') + old_cluster_lc_ctype=$(echo "$result" | cut -d'|' -f2 | tr -d ' ') + old_cluster_locale="$old_cluster_lc_collate" + + if [ -n "$old_cluster_lc_collate" ] && [ -n "$old_cluster_lc_ctype" ]; then + debug "detected locale from old cluster: collate=$old_cluster_lc_collate, ctype=$old_cluster_lc_ctype" + return 0 + fi + fi + + # If we couldn't detect it, return failure + warn $"Could not automatically detect locale settings from old cluster." + warn_q $"You may need to set PGSETUP_INITDB_OPTIONS manually with --lc-collate, --lc-ctype, and --locale options." + return 1 +} + + upgrade() { local inplace=false @@ -275,6 +381,39 @@ upgrade() info $"Upgrading database." + # Automatically detect locale settings from old cluster if not already set + # This addresses Bug 1152556: detect --locale from old datadir automatically + local locale_already_set=false + if [ -n "$PGSETUP_INITDB_OPTIONS" ]; then + if echo "$PGSETUP_INITDB_OPTIONS" | grep -qE -- "--lc-collate|--lc-ctype|--locale"; then + locale_already_set=true + fi + fi + + if [ "$locale_already_set" != "true" ]; then + debug "attempting to auto-detect locale from old cluster" + if detect_old_cluster_locale "$pgdataold" "$upgradefrom_engine" "$PGPORT"; then + # Build initdb options with detected locale + local detected_opts="--lc-collate=$old_cluster_lc_collate --lc-ctype=$old_cluster_lc_ctype" + if [ -n "$old_cluster_locale" ]; then + detected_opts="$detected_opts --locale=$old_cluster_locale" + fi + + # Append to existing PGSETUP_INITDB_OPTIONS if set, otherwise set it + if [ -n "$PGSETUP_INITDB_OPTIONS" ]; then + export PGSETUP_INITDB_OPTIONS="$PGSETUP_INITDB_OPTIONS $detected_opts" + else + export PGSETUP_INITDB_OPTIONS="$detected_opts" + fi + + info $"Auto-detected locale settings from old cluster: $detected_opts" + else + debug "locale auto-detection failed, proceeding with system default locale" + fi + else + debug "locale options already set in PGSETUP_INITDB_OPTIONS, skipping auto-detection" + fi + scls_upgrade_hacks= test -n "$upgradefrom_scls" && { debug "scls [$upgradefrom_scls] will be enabled" From 0bea766c5ee6d21a5d2b556c8079949772be0c6b Mon Sep 17 00:00:00 2001 From: Petr Khartskhaev Date: Wed, 4 Feb 2026 17:18:40 +0100 Subject: [PATCH 2/2] Test and fixups Replaced check for running postgres with failure in `detect_old_locale`, since the script would fail later anyway, so there's no reason to have extra code that does nothing. Fixed `psql` being run from the old pg bin directory. Wrote a test for the script. --- bin/postgresql-setup.in | 105 +++++++----------- .../virtual/sanity/locale-change/locale.sh | 100 +++++++++++++++++ .../virtual/sanity/locale-change/main.fmf | 19 ++++ 3 files changed, 159 insertions(+), 65 deletions(-) create mode 100755 tmt/tests/virtual/sanity/locale-change/locale.sh create mode 100644 tmt/tests/virtual/sanity/locale-change/main.fmf diff --git a/bin/postgresql-setup.in b/bin/postgresql-setup.in index a99046e..ca3e613 100644 --- a/bin/postgresql-setup.in +++ b/bin/postgresql-setup.in @@ -225,80 +225,55 @@ detect_old_cluster_locale() old_cluster_lc_ctype="" old_cluster_locale="" - debug "detecting locale from old cluster at $old_datadir" - - # Try to get locale from running database using psql - # First check if server is running - local pidfile="$old_datadir/postmaster.pid" - local server_was_running=false - local temp_server_started=false - - if [ -f "$pidfile" ]; then - # Check if the PID in the file is actually running - local pid=$(head -n 1 "$pidfile" 2>/dev/null) - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - server_was_running=true - debug "old cluster server is running (PID: $pid), querying locale via psql" - fi + if old_data_in_use; then + return 1 fi # If server is not running, try to start it temporarily - if ! $server_was_running; then - debug "old cluster server is not running, attempting temporary start to query locale" - - # Create a temporary socket directory - local temp_sockdir=$(mktemp -d 2>/dev/null) - if [ $? -ne 0 ] || [ -z "$temp_sockdir" ]; then - debug "failed to create temporary socket directory" - return 1 - fi - - local temp_log="$old_datadir/temp_locale_query.log" - - # Try to start server with minimal configuration - # Use a different port to avoid conflicts - local temp_port=$((old_port + 10000)) - - # Start server in background with minimal settings - # Use -m fast to speed up startup - if "$old_bindir/pg_ctl" -D "$old_datadir" -o "-p $temp_port -c listen_addresses='' -c unix_socket_directories='$temp_sockdir' -c max_connections=5 -c shared_buffers=128kB" start -w -l "$temp_log" -m fast >/dev/null 2>&1; then - temp_server_started=true - debug "temporarily started old cluster server on port $temp_port" - # Give it a moment to be ready - sleep 2 - else - debug "failed to start old cluster server temporarily (see $temp_log for details)" - rm -rf "$temp_sockdir" - rm -f "$temp_log" - return 1 - fi - fi - - # Query locale from template1 database - local query="SELECT datcollate, datctype FROM pg_catalog.pg_database WHERE datname = 'template1'" - local result - local query_port="$old_port" - if $temp_server_started; then - query_port="$temp_port" + debug "old cluster server is not running, attempting temporary start to query locale" + + # Create a temporary socket directory + local temp_sockdir=$(mktemp -d 2>/dev/null) + if [ $? -ne 0 ] || [ -z "$temp_sockdir" ]; then + debug "failed to create temporary socket directory" + return 1 fi - - # Use appropriate connection method - local psql_exit_code=0 - if $temp_server_started; then - result=$("$old_bindir/psql" -t -A -h "$temp_sockdir" -p "$query_port" -d template1 -c "$query" 2>/dev/null) - psql_exit_code=$? + + local temp_log="$old_datadir/temp_locale_query.log" + + # Try to start server with minimal configuration + # Use a different port to avoid conflicts + local temp_port=$((old_port + 10000)) + + # Start server in background with minimal settings + # Use -m fast to speed up startup + if "$old_bindir/pg_ctl" -D "$old_datadir" -o "-p $temp_port -c listen_addresses='' -c unix_socket_directories='$temp_sockdir' -c max_connections=5 -c shared_buffers=128kB" start -w -l "$temp_log" -m fast >/dev/null 2>&1; then + debug "temporarily started old cluster server on port $temp_port" + # Give it a moment to be ready + sleep 2 else - result=$("$old_bindir/psql" -t -A -p "$query_port" -d template1 -c "$query" 2>/dev/null) - psql_exit_code=$? - fi - - # Stop temporary server if we started it - if $temp_server_started; then - "$old_bindir/pg_ctl" -D "$old_datadir" stop >/dev/null 2>&1 + debug "failed to start old cluster server temporarily (see $temp_log for details)" rm -rf "$temp_sockdir" rm -f "$temp_log" + return 1 fi + + # Query locale from template1 database + local query="SELECT datcollate, datctype FROM pg_catalog.pg_database WHERE datname = 'template1'" + local result + local query_port="$temp_port" + + # Perform query + local psql_exit_code=0 + result=$(psql -t -A -h "$temp_sockdir" -p "$query_port" -d template1 -c "$query" 2>/dev/null) + psql_exit_code=$? + + # Stop temporary server + "$old_bindir/pg_ctl" -D "$old_datadir" stop >/dev/null 2>&1 + rm -rf "$temp_sockdir" + rm -f "$temp_log" + # Parse result if [ $psql_exit_code -eq 0 ] && [ -n "$result" ]; then # Parse result: datcollate|datctype diff --git a/tmt/tests/virtual/sanity/locale-change/locale.sh b/tmt/tests/virtual/sanity/locale-change/locale.sh new file mode 100755 index 0000000..0a36183 --- /dev/null +++ b/tmt/tests/virtual/sanity/locale-change/locale.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# vim: dict+=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k + +. /usr/share/beakerlib/beakerlib.sh || exit 1 + +rlJournalStart + rlPhaseStartSetup + rlRun "git clone \"$REPO_URL\" repo" + rlRun "pushd repo" + rlRun "git fetch origin \"$PR_HEAD\"" + rlRun "git checkout FETCH_HEAD" + rlRun "cat /etc/fedora-release" + rlRun "dnf -y install postgresql16-server" + + rlRun "autoreconf -vfi" 0 "Building and installing postgresql-setup" + rlRun "./configure --prefix=/usr" + rlRun "make" + rlRun "./bin/postgresql-setup --init" 0 "Initializing database dir" + rlRun "systemctl start postgresql" 0 "Starting service" + rlRun "systemctl is-active postgresql" 0 "Verifying service running" + rlPhaseEnd + + rlPhaseStartTest + rlRun "su - postgres -c \" + createdb testdb; + psql -U postgres -d testdb -c \\\"create table users (id serial primary key, name text)\\\"; + psql -U postgres -d testdb -c \\\"insert into users (name) values ('Alice'), ('Bob'), ('Celine')\\\" + \"" + rlRun "su - postgres -c ' + psql -U postgres -d testdb -c \"select * from users\" + ' > expected.txt" + + echo "Expected:" + cat expected.txt + + # change locale (https://wiki.archlinux.org/title/Locale#Make_locale_changes_immediate) + rlRun "localectl set-locale LANG=en_GB.UTF-8" 0 "Changing locale" + rlRun "unset LANG" + rlRun "source /etc/profile.d/lang.sh" + rlRun "locale" + + rlRun "dnf -y remove postgresql16*" 0 "Removing postgresql 16" + rlRun "dnf -y install postgresql17-upgrade" 0 "Installing postgresql 17" + rlRun "./bin/postgresql-setup --upgrade" 0 "Running upgrade" + + rlRun "systemctl start postgresql" 0 "Starting service again" + rlRun "systemctl is-active postgresql" 0 "Verifying service running" + + rlRun "su - postgres -c ' + psql -U postgres -d testdb -c \"select * from users\" + ' > actual17.txt" + + echo "Actual:" + cat actual17.txt + + rlAssertNotDiffer expected.txt actual17.txt + + rlRun "localectl set-locale LC_COLLATE=en_US.UTF-8" 0 "Changing LC_COLLATE" + rlRun "localectl set-locale LC_CTYPE=en_AU.UTF-8" 0 "Changing LC_CTYPE" + rlRun "unset LANG" + rlRun "source /etc/profile.d/lang.sh" + rlRun "locale" + + rlRun "dnf -y remove postgresql17*" 0 "Removing postgresql 17" + rlRun "dnf -y install postgresql18-upgrade" 0 "Installing postgresql 18" + # TODO: remove if functionality added to the script + rlRun "/usr/lib64/pgsql/postgresql-17/bin/pg_checksums -e /var/lib/pgsql/data" 0 "Enabling data checksums for pg18" + + rlRun "su - postgres -c '/usr/lib64/pgsql/postgresql-17/bin/pg_ctl -D /var/lib/pgsql/data -l /var/lib/pgsql/logfile start'" 0 "Starting postgres pre-upgrade" + rlRun "su - postgres -c '/usr/lib64/pgsql/postgresql-17/bin/pg_ctl -D /var/lib/pgsql/data status'" 0 "Verifying postgres is running" + + rlRun "./bin/postgresql-setup --upgrade --debug" 1 "Trying to run upgrade" + rlRun "su - postgres -c '/usr/lib64/pgsql/postgresql-17/bin/pg_ctl -D /var/lib/pgsql/data stop'" 0 "Stopping old postgres" + rlRun "./bin/postgresql-setup --upgrade --debug" 0 "Upgrading" + + rlRun "systemctl start postgresql" 0 "Starting service again" + rlRun "systemctl is-active postgresql" 0 "Verifying service running" + + rlRun "su - postgres -c ' + psql -U postgres -d testdb -c \"select * from users\" + ' > actual18.txt" + + echo "Actual:" + cat actual18.txt + + rlAssertNotDiffer actual17.txt actual18.txt + rlPhaseEnd + + rlPhaseStartCleanup + rlRun "popd" + rlRun "rm -rf repo" + rlRun "echo 'LANG=en_US.UTF-8' > /etc/locale.conf" 0 "Resetting locale" + rlRun "unset LANG" + rlRun "source /etc/profile.d/lang.sh" + rlRun "systemctl stop postgresql" 0 "Stopping postgresql service" + rlRun "rm -rf /var/lib/pgsql" 0 "Removing database folder" + rlRun "dnf -y remove postgresql*" 0 "Uninstalling postgresql" + rlPhaseEnd +rlJournalPrintText +rlJournalEnd diff --git a/tmt/tests/virtual/sanity/locale-change/main.fmf b/tmt/tests/virtual/sanity/locale-change/main.fmf new file mode 100644 index 0000000..08e14a0 --- /dev/null +++ b/tmt/tests/virtual/sanity/locale-change/main.fmf @@ -0,0 +1,19 @@ +summary: Check if upgrading a database works when using a different locale +name: locale-change +require: + - make + - m4 + - docbook-utils + - help2man + - elinks + - coreutils + - autoconf + - automake + - autoconf-archive + - git +framework: beakerlib +contact: pkhartsk@redhat.com +tag: + - fedora-upgrade +test: ./locale.sh +