mirror of
https://github.com/postgres/postgres.git
synced 2025-12-16 00:04:05 -05:00
Presently, pg_upgrade assumes that all non-default tablespaces don't move to different directories during upgrade. Unfortunately, this isn't true for in-place tablespaces, which move to the new cluster's pg_tblspc directory. This commit teaches pg_upgrade to handle in-place tablespaces by retrieving the tablespace directories for both the old and new clusters. In turn, we can relax the prohibition on non-default tablespaces for same-version upgrades, i.e., if all non-default tablespaces are in-place, pg_upgrade may proceed. This change is primarily intended to enable additional pg_upgrade testing with non-default tablespaces, as is done in 006_transfer_modes.pl. Reviewed-by: Corey Huinker <corey.huinker@gmail.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/aA_uBLYMUs5D66Nb%40nathan
151 lines
4.2 KiB
C
151 lines
4.2 KiB
C
/*
|
|
* tablespace.c
|
|
*
|
|
* tablespace functions
|
|
*
|
|
* Copyright (c) 2010-2025, PostgreSQL Global Development Group
|
|
* src/bin/pg_upgrade/tablespace.c
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
#include "pg_upgrade.h"
|
|
|
|
static void get_tablespace_paths(void);
|
|
static void set_tablespace_directory_suffix(ClusterInfo *cluster);
|
|
|
|
|
|
void
|
|
init_tablespaces(void)
|
|
{
|
|
get_tablespace_paths();
|
|
|
|
set_tablespace_directory_suffix(&old_cluster);
|
|
set_tablespace_directory_suffix(&new_cluster);
|
|
|
|
if (old_cluster.num_tablespaces > 0 &&
|
|
strcmp(old_cluster.tablespace_suffix, new_cluster.tablespace_suffix) == 0)
|
|
{
|
|
for (int i = 0; i < old_cluster.num_tablespaces; i++)
|
|
{
|
|
/*
|
|
* In-place tablespaces are okay for same-version upgrades because
|
|
* their paths will differ between clusters.
|
|
*/
|
|
if (strcmp(old_cluster.tablespaces[i], new_cluster.tablespaces[i]) == 0)
|
|
pg_fatal("Cannot upgrade to/from the same system catalog version when\n"
|
|
"using tablespaces.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* get_tablespace_paths()
|
|
*
|
|
* Scans pg_tablespace and returns a malloc'ed array of all tablespace
|
|
* paths. It's the caller's responsibility to free the array.
|
|
*/
|
|
static void
|
|
get_tablespace_paths(void)
|
|
{
|
|
PGconn *conn = connectToServer(&old_cluster, "template1");
|
|
PGresult *res;
|
|
int tblnum;
|
|
int i_spclocation;
|
|
char query[QUERY_ALLOC];
|
|
|
|
snprintf(query, sizeof(query),
|
|
"SELECT pg_catalog.pg_tablespace_location(oid) AS spclocation "
|
|
"FROM pg_catalog.pg_tablespace "
|
|
"WHERE spcname != 'pg_default' AND "
|
|
" spcname != 'pg_global'");
|
|
|
|
res = executeQueryOrDie(conn, "%s", query);
|
|
|
|
old_cluster.num_tablespaces = PQntuples(res);
|
|
new_cluster.num_tablespaces = PQntuples(res);
|
|
|
|
if (PQntuples(res) != 0)
|
|
{
|
|
old_cluster.tablespaces =
|
|
(char **) pg_malloc(old_cluster.num_tablespaces * sizeof(char *));
|
|
new_cluster.tablespaces =
|
|
(char **) pg_malloc(new_cluster.num_tablespaces * sizeof(char *));
|
|
}
|
|
else
|
|
{
|
|
old_cluster.tablespaces = NULL;
|
|
new_cluster.tablespaces = NULL;
|
|
}
|
|
|
|
i_spclocation = PQfnumber(res, "spclocation");
|
|
|
|
for (tblnum = 0; tblnum < old_cluster.num_tablespaces; tblnum++)
|
|
{
|
|
struct stat statBuf;
|
|
char *spcloc = PQgetvalue(res, tblnum, i_spclocation);
|
|
|
|
/*
|
|
* For now, we do not expect non-in-place tablespaces to move during
|
|
* upgrade. If that changes, it will likely become necessary to run
|
|
* the above query on the new cluster, too.
|
|
*
|
|
* pg_tablespace_location() returns absolute paths for non-in-place
|
|
* tablespaces and relative paths for in-place ones, so we use
|
|
* is_absolute_path() to distinguish between them.
|
|
*/
|
|
if (is_absolute_path(PQgetvalue(res, tblnum, i_spclocation)))
|
|
{
|
|
old_cluster.tablespaces[tblnum] = pg_strdup(spcloc);
|
|
new_cluster.tablespaces[tblnum] = old_cluster.tablespaces[tblnum];
|
|
}
|
|
else
|
|
{
|
|
old_cluster.tablespaces[tblnum] = psprintf("%s/%s", old_cluster.pgdata, spcloc);
|
|
new_cluster.tablespaces[tblnum] = psprintf("%s/%s", new_cluster.pgdata, spcloc);
|
|
}
|
|
|
|
/*
|
|
* Check that the tablespace path exists and is a directory.
|
|
* Effectively, this is checking only for tables/indexes in
|
|
* non-existent tablespace directories. Databases located in
|
|
* non-existent tablespaces already throw a backend error.
|
|
* Non-existent tablespace directories can occur when a data directory
|
|
* that contains user tablespaces is moved as part of pg_upgrade
|
|
* preparation and the symbolic links are not updated.
|
|
*/
|
|
if (stat(old_cluster.tablespaces[tblnum], &statBuf) != 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
report_status(PG_FATAL,
|
|
"tablespace directory \"%s\" does not exist",
|
|
old_cluster.tablespaces[tblnum]);
|
|
else
|
|
report_status(PG_FATAL,
|
|
"could not stat tablespace directory \"%s\": %m",
|
|
old_cluster.tablespaces[tblnum]);
|
|
}
|
|
if (!S_ISDIR(statBuf.st_mode))
|
|
report_status(PG_FATAL,
|
|
"tablespace path \"%s\" is not a directory",
|
|
old_cluster.tablespaces[tblnum]);
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
PQfinish(conn);
|
|
}
|
|
|
|
|
|
static void
|
|
set_tablespace_directory_suffix(ClusterInfo *cluster)
|
|
{
|
|
/* This cluster has a version-specific subdirectory */
|
|
|
|
/* The leading slash is needed to start a new directory. */
|
|
cluster->tablespace_suffix = psprintf("/PG_%s_%d",
|
|
cluster->major_version_str,
|
|
cluster->controldata.cat_ver);
|
|
}
|