Re: [PATCH] Add pg_get_tablespace_ddl() function to reconstruct CREATE TABLESPACE statement

Manni Wood <manni.wood@enterprisedb.com>

From: Manni Wood <manni.wood@enterprisedb.com>
To: Jim Jones <jim.jones@uni-muenster.de>
Cc: pgsql-hackers@postgresql.org
Date: 2025-11-03T23:49:23Z
Lists: pgsql-hackers

Attachments

On Fri, Oct 31, 2025 at 10:36โ€ฏAM Jim Jones <jim.jones@uni-muenster.de>
wrote:

> Hi Manni,
>
> Thanks for the patch!
>
> On 29/10/2025 02:23, Manni Wood wrote:
> > This patch creates a function pg_get_tablespace_ddl, designed to
> > retrieve the full DDL statement for a tablespace. Users can obtain the
> > DDL by providing the tablespace name, like so:
> >
> >     SELECT pg_get_tablespace_ddl('regress_owner_tblsp');
> >                                            pg_get_tablespace_ddl
> >
> >
> ---------------------------------------------------------------------------------------------------
> >      CREATE TABLESPACE regress_owner_tblsp OWNER regress_user LOCATION
> > '' WITH (random_page_cost = 3);
>
>
> Here my first comments regarding usability:
>
> == quoted identifier ==
>
> Tablespace names containing quoted identifiers cannot be parsed:
>
> postgres=# CREATE TABLESPACE "My TS" LOCATION '/tmp/ts';
> CREATE TABLESPACE
> postgres=# SELECT pg_get_tablespace_ddl('"My TS"');
> ERROR:  tablespace ""My TS"" does not exist
>
> The following works, but I guess it shouldn't:
>
> postgres=# SELECT pg_get_tablespace_ddl('My TS');
>              pg_get_tablespace_ddl
> -----------------------------------------------
>  CREATE TABLESPACE "My TS" LOCATION '/tmp/ts';
> (1 row)
>
> The same applies for unicode characters:
>
> postgres=# CREATE TABLESPACE "๐Ÿ˜" LOCATION '/tmp/ts';
> CREATE TABLESPACE
> postgres=# SELECT pg_get_tablespace_ddl('"๐Ÿ˜"');
> ERROR:  tablespace ""๐Ÿ˜"" does not exist
> postgres=# SELECT pg_get_tablespace_ddl('๐Ÿ˜');
>            pg_get_tablespace_ddl
> --------------------------------------------
>  CREATE TABLESPACE "๐Ÿ˜" LOCATION '/tmp/ts';
> (1 row)
>
>
> == option precision ==
>
> There is a precision loss in the options:
>
> postgres=# CREATE TABLESPACE ts OWNER u1 LOCATION '/tmp/ts' WITH
> (seq_page_cost = 1.12345678910, random_page_cost = 1.12345678910,
> effective_io_concurrency = 17, maintenance_io_concurrency = 18);
> CREATE TABLESPACE
> postgres=# SELECT pg_get_tablespace_ddl('ts');
>
>    pg_get_tablespace_ddl
>
>
> ---------------------------------------------------------------------------------------------------------------------------------------------
> ---------------------------------
>  CREATE TABLESPACE ts OWNER u1 LOCATION '/tmp/ts' WITH (random_page_cost
> = 1.12346, seq_page_cost = 1.12346, effective_io_concurrency = 17, m
> aintenance_io_concurrency = 18);
> (1 row)
>
> \db shows it as in the CREATE TABLESPACE statement:
>
> postgres=# \db+ ts
>
>             List of tablespaces
>  Name | Owner | Location | Access privileges |
>                              Options
>                          |  Size   | Description
>
> ------+-------+----------+-------------------+-----------------------------------------------------------------------------------------------
> -------------------------+---------+-------------
>  ts   | u1    | /tmp/ts  |                   |
>
> {seq_page_cost=1.12345678910,random_page_cost=1.12345678910,effective_io_concurrency=17,mainte
> nance_io_concurrency=18} | 0 bytes |
> (1 row)
>
>
> == permissions ==
>
> Is it supposed to be visible to all users?
>
> postgres=# CREATE USER u1;
> CREATE ROLE
> postgres=# CREATE TABLESPACE ts LOCATION '/tmp/ts';
> CREATE TABLESPACE
> postgres=# SET ROLE u1;
> SET
> postgres=> SELECT pg_get_tablespace_ddl('ts');
>                pg_get_tablespace_ddl
> ----------------------------------------------------
>  CREATE TABLESPACE ts OWNER jim LOCATION '/tmp/ts';
> (1 row)
>
> Note that \db does not allow it:
>
> postgres=> SELECT CURRENT_USER;
>  current_user
> --------------
>  u1
> (1 row)
>
> postgres=> \db+ ts
> ERROR:  permission denied for tablespace ts
>
>
> Best, Jim
>
>
> Hi, Jim

Thanks for reviewing my very first patch!

== quoted identifier ==

 I see that Postgres already has the SQL function has_tablespace_privilege
that behaves the same way as this patch's pg_get_tablespace_ddl.

# create tablespace "My TS" location '/tmp/has_space';
CREATE TABLESPACE

# select has_tablespace_privilege('My TS', 'create'); rollback;

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ has_tablespace_privilege โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ t                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
(1 row)

# select has_tablespace_privilege('"My TS"', 'create'); rollback;
ERROR:  42704: tablespace ""My TS"" does not exist


# create tablespace "๐Ÿ˜" location '/tmp/has_elephant';
CREATE TABLESPACE

# select has_tablespace_privilege('๐Ÿ˜', 'create'); rollback;
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ has_tablespace_privilege โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ t                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
(1 row)

# select has_tablespace_privilege('"๐Ÿ˜"', 'create'); rollback;
ERROR:  42704: tablespace ""๐Ÿ˜"" does not exist

Does the existence of this behavior in an existing function make the same
behavior less surprising for this patch's function?

== option precision ==

Thanks for pointing this out.

I have attached a v2 of the patch that just uses the original text the user
entered for the spcoptions.

This is much better, and it made the code smaller.

I have added "1.1234567890" to one of the tests to show that this works.

== permissions ==

I'm not sure what to think of this. psql's "\db+" does not let me show the
tablespace.

But if, as user 'u1', I select from pg_tablespace directly, I have the
permissions to do so:

postgres> select current_user; rollback;
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ current_user โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ u1           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
(1 row)

postgres> select * from pg_catalog.pg_tablespace; rollback;
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  oid  โ”‚  spcname   โ”‚ spcowner โ”‚ spcacl โ”‚
                      spcoptions
            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  1663 โ”‚ pg_default โ”‚       10 โ”‚ [NULL] โ”‚ [NULL]

          โ”‚
โ”‚  1664 โ”‚ pg_global  โ”‚       10 โ”‚ [NULL] โ”‚ [NULL]

          โ”‚
โ”‚ 19971 โ”‚ ts         โ”‚       10 โ”‚ [NULL] โ”‚
{seq_page_cost=1.12345678910,random_page_cost=1.12345678910,effective_io_concurrency=17,maintenance_io_concurrency=18}
โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
(3 rows)

So if the information is obtainable by selecting from
pg_catalog.pg_tablespace, it seems defensible to make the same data
available via pg_get_tablespace_ddl.

Thoughts?

Thanks again for reviewing my patch,

-Manni