Обсуждение: [PATCH] Add pg_get_tablespace_ddl() function to reconstruct CREATE TABLESPACE statement
Hello!
I am submitting a patch as a part of a larger Retail DDL functions project described by Andrew Dunstan here: https://www.postgresql.org/message-id/945db7c5-be75-45bf-b55b-cb1e56f2e3e9%40dunslane.net
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);
This patch includes documentation, comments, and regression tests, all of which pass successfully.
--
Best,
			
		I am submitting a patch as a part of a larger Retail DDL functions project described by Andrew Dunstan here: https://www.postgresql.org/message-id/945db7c5-be75-45bf-b55b-cb1e56f2e3e9%40dunslane.net
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);
This patch includes documentation, comments, and regression tests, all of which pass successfully.
--
Best,
Manni Wood
EnterpriseDB 
Вложения
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
			
		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
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)
┌──────────────┐
│ 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)
┌───────┬────────────┬──────────┬────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 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
Вложения
On 04/11/2025 00:49, Manni Wood wrote:
> == 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.
You're right. The source of my confusion is that I was approaching the
tablespace name as if it were a relation:
postgres=# CREATE TABLE "T"();
CREATE TABLE
postgres=# SELECT '"T"'::regclass::oid;
  oid
-------
 47766
(1 row)
postgres=# SELECT 'T'::regclass::oid;
ERROR:  relation "t" does not exist
LINE 1: SELECT 'T'::regclass::oid;
But I see that other functions behave similarly, e.g. pg_tablespace_size:
postgres=# SELECT pg_tablespace_size('My TS');
 pg_tablespace_size
--------------------
                  0
(1 row)
postgres=# SELECT pg_tablespace_size('"My TS"');
ERROR:  tablespace ""My TS"" does not exist
postgres=#
Sorry for the noise.
Do you think that an overload in pg_proc.dat with oid as parameter would
make sense here? e.g.
{ oid => '2322',
  descr => 'total disk space usage for the specified tablespace',
  proname => 'pg_tablespace_size', provolatile => 'v', prorettype => 'int8',
  proargtypes => 'oid', prosrc => 'pg_tablespace_size_oid' },
{ oid => '2323',
  descr => 'total disk space usage for the specified tablespace',
  proname => 'pg_tablespace_size', provolatile => 'v', prorettype => 'int8',
  proargtypes => 'name', prosrc => 'pg_tablespace_size_name' },
> 
> == 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.
Nice. It now shows the options without precision loss:
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
(seq_page_cost=1.12345678910, random_page_cost=1.12345678910,
effective_io_concurrency=17, maintenance_io_concurrency=18);
(1 row)
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,maintenance_io_concurrency=18}
| 0 bytes |
(1 row)
> == permissions ==
> 
> I'm not sure what to think of this. psql's "\db+" does not let me show
> the tablespace.
> 
Right. I guess the difference here is that \db+ also shows the
tablespace's size, which requires the user to actually read it.
postgres=# CREATE TABLESPACE ts OWNER jim LOCATION '/tmp/ts';
CREATE TABLESPACE
postgres=# SELECT pg_tablespace_size('ts');
 pg_tablespace_size
--------------------
                  0
(1 row)
postgres=# SET ROLE u1;
SET
postgres=> SELECT pg_tablespace_size('ts');
ERROR:  permission denied for tablespace ts
Since pg_get_tablespace_ddl doesn't display size, I believe it's fine as-is.
Thanks.
Best, Jim
			
		Re: [PATCH] Add pg_get_tablespace_ddl() function to reconstruct CREATE TABLESPACE statement
От
 
		    	Nishant Sharma
		    Дата:
		        On Tue, Nov 4, 2025 at 1:58 PM Jim Jones <jim.jones@uni-muenster.de> wrote:
Do you think that an overload in pg_proc.dat with oid as parameter would
make sense here? e.g.
{ oid => '2322',
descr => 'total disk space usage for the specified tablespace',
proname => 'pg_tablespace_size', provolatile => 'v', prorettype => 'int8',
proargtypes => 'oid', prosrc => 'pg_tablespace_size_oid' },
{ oid => '2323',
descr => 'total disk space usage for the specified tablespace',
proname => 'pg_tablespace_size', provolatile => 'v', prorettype => 'int8',
proargtypes => 'name', prosrc => 'pg_tablespace_size_name' },
Using name as parameter is more user friendly than OID.
Because users usually do not know the oids. Constructing
the DDL from the name appears better as it contains a name
in it. So, no gain in having an OID version of
pg_get_tablespace_ddl.
PFA, v3 patch set. It has some cosmetic changes and few
improvements in the new code added by Manni in v2. Also, the
new test case added did not have a DROP statement for the
tablespace created, which caused make-world failure. So, I
corrected that too.
Regards,
Nishant Sharma.
EDB, Pune.
Вложения
Hi Nishant On 04/11/2025 11:37, Nishant Sharma wrote: > Using name as parameter is more user friendly than OID. > Because users usually do not know the oids. Constructing > the DDL from the name appears better as it contains a name > in it. So, no gain in having an OID version of > pg_get_tablespace_ddl. Would you also say that having a pg_tablespace_size(oid) has no benefit? I took a look at similar functions, and the only pattern I could identify is that all of them take an oid parameter. pg_tablespace_size: oid and name pg_tablespace_location: oid has_tablespace_privilege: oid, name, and text pg_tablespace_databases: oid ... pg_get_tablespace_ddl: name I'm definitely not opposed to having just a name parameter, but I thought it would be worth mentioning. Best, Jim
On Tue, Nov 4, 2025 at 5:25 AM Jim Jones <jim.jones@uni-muenster.de> wrote:
Hi Nishant
On 04/11/2025 11:37, Nishant Sharma wrote:
> Using name as parameter is more user friendly than OID.
> Because users usually do not know the oids. Constructing
> the DDL from the name appears better as it contains a name
> in it. So, no gain in having an OID version of
> pg_get_tablespace_ddl.
Would you also say that having a pg_tablespace_size(oid) has no benefit?
I took a look at similar functions, and the only pattern I could
identify is that all of them take an oid parameter.
pg_tablespace_size: oid and name
pg_tablespace_location: oid
has_tablespace_privilege: oid, name, and text
pg_tablespace_databases: oid
...
pg_get_tablespace_ddl: name
I'm definitely not opposed to having just a name parameter, but I
thought it would be worth mentioning.
Best, Jim
Hello,  Jim and Nishant!
About having an OID variant:
I definitely want to keep the current name-based parameter, and it looks like we all agree on that.
The question is if we should additionally have an OID-based variant.
I personally see no harm in additionally having an OID-based variant, seeing as it looks like a lot of functions do seem to take an OID. If I understand correctly, many functions take an OID, and Postgres users are supposed to have read the docs (https://www.postgresql.org/docs/current/datatype-oid.html) to know to cast names to OIDs. So, in terms of following established practice / patterns, an OID-based variant is defensible.
Thankfully for people like me (for whom the "just cast the OID to a name" pattern never sunk in after 25 years of using Postgres), I'm glad text/name variants of functions are also a thing in Postgres, as I suspect every time a user has a choice between the two, a user will choose to just provide the name.
Let me know what you think!
Thanks, Jim,
Thanks Nishant for fixing/improving my v2 patch to v3!
-Manni