role memberships and their options.
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_get_tablespace_ddl</primary>
+ </indexterm>
+ <function>pg_get_tablespace_ddl</function>
+ ( <parameter>tablespace</parameter> <type>oid</type>
+ <optional>, <literal>VARIADIC</literal> <parameter>options</parameter>
+ <type>text</type> </optional> )
+ <returnvalue>setof text</returnvalue>
+ </para>
+ <para>
+ <function>pg_get_tablespace_ddl</function>
+ ( <parameter>tablespace</parameter> <type>name</type>
+ <optional>, <literal>VARIADIC</literal> <parameter>options</parameter>
+ <type>text</type> </optional> )
+ <returnvalue>setof text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE TABLESPACE</command> statement for
+ the specified tablespace (by OID or name). If the tablespace has
+ options set, an <command>ALTER TABLESPACE ... SET</command> statement
+ is also returned. Each statement is returned as a separate row.
+ The following options are supported: <literal>pretty</literal> (boolean)
+ for formatted output and <literal>owner</literal> (boolean) to include
+ <literal>OWNER</literal>.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
#include "access/genam.h"
#include "access/htup_details.h"
-#include "access/relation.h"
#include "access/table.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_tablespace.h"
+#include "commands/tablespace.h"
+#include "common/relpath.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/varlena.h"
const char *value);
static List *pg_get_role_ddl_internal(Oid roleid, bool pretty,
bool memberships);
+static List *pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner);
+static Datum pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull);
/*
SRF_RETURN_DONE(funcctx);
}
}
+
+/*
+ * pg_get_tablespace_ddl_internal
+ * Generate DDL statements to recreate a tablespace.
+ *
+ * Returns a List of palloc'd strings. The first element is the
+ * CREATE TABLESPACE statement; if the tablespace has reloptions,
+ * a second element with ALTER TABLESPACE SET (...) is appended.
+ */
+static List *
+pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner)
+{
+ HeapTuple tuple;
+ Form_pg_tablespace tspForm;
+ StringInfoData buf;
+ char *spcname;
+ char *spcowner;
+ char *path;
+ bool isNull;
+ Datum datum;
+ List *statements = NIL;
+
+ tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tsid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tablespace with OID %u does not exist",
+ tsid)));
+
+ tspForm = (Form_pg_tablespace) GETSTRUCT(tuple);
+ spcname = pstrdup(NameStr(tspForm->spcname));
+
+ /* User must have SELECT privilege on pg_tablespace. */
+ if (pg_class_aclcheck(TableSpaceRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ {
+ ReleaseSysCache(tuple);
+ aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE, spcname);
+ }
+
+ /*
+ * We don't support generating DDL for system tablespaces. The primary
+ * reason for this is that users shouldn't be recreating them.
+ */
+ if (IsReservedName(spcname))
+ ereport(ERROR,
+ (errcode(ERRCODE_RESERVED_NAME),
+ errmsg("tablespace name \"%s\" is reserved", spcname),
+ errdetail("Tablespace names starting with \"pg_\" are reserved for system tablespaces.")));
+
+ initStringInfo(&buf);
+
+ /* Start building the CREATE TABLESPACE statement */
+ appendStringInfo(&buf, "CREATE TABLESPACE %s", quote_identifier(spcname));
+
+ /* Add OWNER clause */
+ if (!no_owner)
+ {
+ spcowner = GetUserNameFromId(tspForm->spcowner, false);
+ append_ddl_option(&buf, pretty, 4, "OWNER %s",
+ quote_identifier(spcowner));
+ pfree(spcowner);
+ }
+
+ /* Find tablespace directory path */
+ path = get_tablespace_location(tsid);
+
+ /* Add directory LOCATION (path), if it exists */
+ if (path[0] != '\0')
+ {
+ /*
+ * Special case: if the tablespace was created with GUC
+ * "allow_in_place_tablespaces = true" and "LOCATION ''", path will
+ * begin with "pg_tblspc/". In that case, show "LOCATION ''" as the
+ * user originally specified.
+ */
+ if (strncmp(PG_TBLSPC_DIR_SLASH, path, strlen(PG_TBLSPC_DIR_SLASH)) == 0)
+ append_ddl_option(&buf, pretty, 4, "LOCATION ''");
+ else
+ append_ddl_option(&buf, pretty, 4, "LOCATION %s",
+ quote_literal_cstr(path));
+ }
+ pfree(path);
+
+ appendStringInfoChar(&buf, ';');
+ statements = lappend(statements, pstrdup(buf.data));
+
+ /* Check for tablespace options */
+ datum = SysCacheGetAttr(TABLESPACEOID, tuple,
+ Anum_pg_tablespace_spcoptions, &isNull);
+ if (!isNull)
+ {
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER TABLESPACE %s SET (",
+ quote_identifier(spcname));
+ get_reloptions(&buf, datum);
+ appendStringInfoString(&buf, ");");
+ statements = lappend(statements, pstrdup(buf.data));
+ }
+
+ ReleaseSysCache(tuple);
+ pfree(spcname);
+ pfree(buf.data);
+
+ return statements;
+}
+
+/*
+ * pg_get_tablespace_ddl_srf - common SRF logic for tablespace DDL
+ */
+static Datum
+pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull)
+{
+ FuncCallContext *funcctx;
+ List *statements;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+ DdlOption opts[] = {
+ {"pretty", DDL_OPT_BOOL},
+ {"owner", DDL_OPT_BOOL},
+ };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ if (isnull)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ parse_ddl_options(fcinfo, 1, opts, lengthof(opts));
+
+ statements = pg_get_tablespace_ddl_internal(tsid,
+ opts[0].isset && opts[0].boolval,
+ opts[1].isset && !opts[1].boolval);
+ funcctx->user_fctx = statements;
+ funcctx->max_calls = list_length(statements);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ statements = (List *) funcctx->user_fctx;
+
+ if (funcctx->call_cntr < funcctx->max_calls)
+ {
+ char *stmt;
+
+ stmt = (char *) list_nth(statements, funcctx->call_cntr);
+
+ SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
+ }
+ else
+ {
+ list_free_deep(statements);
+ SRF_RETURN_DONE(funcctx);
+ }
+}
+
+/*
+ * pg_get_tablespace_ddl_oid
+ * Return DDL to recreate a tablespace, taking OID.
+ */
+Datum
+pg_get_tablespace_ddl_oid(PG_FUNCTION_ARGS)
+{
+ Oid tsid = InvalidOid;
+ bool isnull;
+
+ isnull = PG_ARGISNULL(0);
+ if (!isnull)
+ tsid = PG_GETARG_OID(0);
+
+ return pg_get_tablespace_ddl_srf(fcinfo, tsid, isnull);
+}
+
+/*
+ * pg_get_tablespace_ddl_name
+ * Return DDL to recreate a tablespace, taking name.
+ */
+Datum
+pg_get_tablespace_ddl_name(PG_FUNCTION_ARGS)
+{
+ Oid tsid = InvalidOid;
+ Name tspname;
+ bool isnull;
+
+ isnull = PG_ARGISNULL(0);
+
+ if (!isnull)
+ {
+ tspname = PG_GETARG_NAME(0);
+ tsid = get_tablespace_oid(NameStr(*tspname), false);
+ }
+
+ return pg_get_tablespace_ddl_srf(fcinfo, tsid, isnull);
+}
static char *generate_qualified_type_name(Oid typid);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
-static void get_reloptions(StringInfo buf, Datum reloptions);
+void get_reloptions(StringInfo buf, Datum reloptions);
static void get_json_path_spec(Node *path_spec, deparse_context *context,
bool showimplicit);
static void get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
/*
* Generate a C string representing a relation options from text[] datum.
*/
-static void
+void
get_reloptions(StringInfo buf, Datum reloptions)
{
Datum *options;
proallargtypes => '{regrole,text}',
pronargdefaults => '1', proargdefaults => '{NULL}',
prosrc => 'pg_get_role_ddl' },
+{ oid => '8758', descr => 'get DDL to recreate a tablespace',
+ proname => 'pg_get_tablespace_ddl', provariadic => 'text', proisstrict => 'f',
+ provolatile => 's', proretset => 't', prorows => '10', prorettype => 'text',
+ proargtypes => 'oid text',
+ proargmodes => '{i,v}',
+ proallargtypes => '{oid,text}',
+ pronargdefaults => '1', proargdefaults => '{NULL}',
+ prosrc => 'pg_get_tablespace_ddl_oid' },
+{ oid => '8759', descr => 'get DDL to recreate a tablespace',
+ proname => 'pg_get_tablespace_ddl', provariadic => 'text', proisstrict => 'f',
+ provolatile => 's', proretset => 't', prorows => '10', prorettype => 'text',
+ proargtypes => 'name text',
+ proargmodes => '{i,v}',
+ proallargtypes => '{name,text}',
+ pronargdefaults => '1', proargdefaults => '{NULL}',
+ prosrc => 'pg_get_tablespace_ddl_name' },
{ oid => '2509',
descr => 'deparse an encoded expression with pretty-print option',
proname => 'pg_get_expr', provolatile => 's', prorettype => 'text',
extern char *generate_collation_name(Oid collid);
extern char *generate_opclass_name(Oid opclass);
extern char *get_range_partbound_string(List *bound_datums);
+extern void get_reloptions(StringInfo buf, Datum reloptions);
extern char *pg_get_statisticsobjdef_string(Oid statextid);
--- /dev/null
+--
+-- Tests for pg_get_tablespace_ddl()
+--
+SET allow_in_place_tablespaces = true;
+CREATE ROLE regress_tblspc_ddl_user;
+-- error: non-existent tablespace by name
+SELECT * FROM pg_get_tablespace_ddl('regress_nonexistent_tblsp');
+ERROR: tablespace "regress_nonexistent_tblsp" does not exist
+-- error: non-existent tablespace by OID
+SELECT * FROM pg_get_tablespace_ddl(0::oid);
+ERROR: tablespace with OID 0 does not exist
+-- NULL input returns no rows (name variant)
+SELECT * FROM pg_get_tablespace_ddl(NULL::name);
+ pg_get_tablespace_ddl
+-----------------------
+(0 rows)
+
+-- NULL input returns no rows (OID variant)
+SELECT * FROM pg_get_tablespace_ddl(NULL::oid);
+ pg_get_tablespace_ddl
+-----------------------
+(0 rows)
+
+-- tablespace name requiring quoting
+CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION '';
+SELECT * FROM pg_get_tablespace_ddl('regress_ tblsp');
+ pg_get_tablespace_ddl
+-------------------------------------------------------------------------------
+ CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION '';
+(1 row)
+
+DROP TABLESPACE "regress_ tblsp";
+-- tablespace with multiple options
+CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION ''
+ WITH (seq_page_cost = '1.5', random_page_cost = '1.1234567890',
+ effective_io_concurrency = '17', maintenance_io_concurrency = '18');
+SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp');
+ pg_get_tablespace_ddl
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
+ ALTER TABLESPACE regress_allopt_tblsp SET (seq_page_cost='1.5', random_page_cost='1.1234567890', effective_io_concurrency='17', maintenance_io_concurrency='18');
+(2 rows)
+
+-- pretty-printed output
+\pset format unaligned
+SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'pretty', 'true');
+pg_get_tablespace_ddl
+CREATE TABLESPACE regress_allopt_tblsp
+ OWNER regress_tblspc_ddl_user
+ LOCATION '';
+ALTER TABLESPACE regress_allopt_tblsp SET (seq_page_cost='1.5', random_page_cost='1.1234567890', effective_io_concurrency='17', maintenance_io_concurrency='18');
+(2 rows)
+\pset format aligned
+-- tablespace with owner suppressed
+SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'owner', 'false');
+ pg_get_tablespace_ddl
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE TABLESPACE regress_allopt_tblsp LOCATION '';
+ ALTER TABLESPACE regress_allopt_tblsp SET (seq_page_cost='1.5', random_page_cost='1.1234567890', effective_io_concurrency='17', maintenance_io_concurrency='18');
+(2 rows)
+
+DROP TABLESPACE regress_allopt_tblsp;
+-- test by OID
+CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
+SELECT oid AS tsid FROM pg_tablespace WHERE spcname = 'regress_oid_tblsp' \gset
+SELECT * FROM pg_get_tablespace_ddl(:tsid);
+ pg_get_tablespace_ddl
+--------------------------------------------------------------------------------
+ CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
+(1 row)
+
+DROP TABLESPACE regress_oid_tblsp;
+-- Permission check: revoke SELECT on pg_tablespace
+CREATE TABLESPACE regress_acl_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
+CREATE ROLE regress_tblspc_ddl_noaccess;
+REVOKE SELECT ON pg_tablespace FROM PUBLIC;
+SET ROLE regress_tblspc_ddl_noaccess;
+SELECT * FROM pg_get_tablespace_ddl('regress_acl_tblsp'); -- should fail
+ERROR: permission denied for tablespace regress_acl_tblsp
+RESET ROLE;
+GRANT SELECT ON pg_tablespace TO PUBLIC;
+DROP TABLESPACE regress_acl_tblsp;
+DROP ROLE regress_tblspc_ddl_noaccess;
+DROP ROLE regress_tblspc_ddl_user;
# oidjoins is read-only, though, and should run late for best coverage
test: oidjoins event_trigger
-test: role_ddl
+test: role_ddl tablespace_ddl
# event_trigger_login cannot run concurrently with any other tests because
# on-login event handling could catch connection of a concurrent test.
--- /dev/null
+--
+-- Tests for pg_get_tablespace_ddl()
+--
+
+SET allow_in_place_tablespaces = true;
+CREATE ROLE regress_tblspc_ddl_user;
+
+-- error: non-existent tablespace by name
+SELECT * FROM pg_get_tablespace_ddl('regress_nonexistent_tblsp');
+
+-- error: non-existent tablespace by OID
+SELECT * FROM pg_get_tablespace_ddl(0::oid);
+
+-- NULL input returns no rows (name variant)
+SELECT * FROM pg_get_tablespace_ddl(NULL::name);
+
+-- NULL input returns no rows (OID variant)
+SELECT * FROM pg_get_tablespace_ddl(NULL::oid);
+
+-- tablespace name requiring quoting
+CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION '';
+SELECT * FROM pg_get_tablespace_ddl('regress_ tblsp');
+DROP TABLESPACE "regress_ tblsp";
+
+-- tablespace with multiple options
+CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION ''
+ WITH (seq_page_cost = '1.5', random_page_cost = '1.1234567890',
+ effective_io_concurrency = '17', maintenance_io_concurrency = '18');
+SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp');
+
+-- pretty-printed output
+\pset format unaligned
+SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'pretty', 'true');
+\pset format aligned
+
+-- tablespace with owner suppressed
+SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'owner', 'false');
+
+DROP TABLESPACE regress_allopt_tblsp;
+
+-- test by OID
+CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
+SELECT oid AS tsid FROM pg_tablespace WHERE spcname = 'regress_oid_tblsp' \gset
+SELECT * FROM pg_get_tablespace_ddl(:tsid);
+DROP TABLESPACE regress_oid_tblsp;
+
+-- Permission check: revoke SELECT on pg_tablespace
+CREATE TABLESPACE regress_acl_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
+CREATE ROLE regress_tblspc_ddl_noaccess;
+REVOKE SELECT ON pg_tablespace FROM PUBLIC;
+SET ROLE regress_tblspc_ddl_noaccess;
+SELECT * FROM pg_get_tablespace_ddl('regress_acl_tblsp'); -- should fail
+RESET ROLE;
+GRANT SELECT ON pg_tablespace TO PUBLIC;
+DROP TABLESPACE regress_acl_tblsp;
+DROP ROLE regress_tblspc_ddl_noaccess;
+
+DROP ROLE regress_tblspc_ddl_user;