From 4b802e6760d31a477be285b3af71dbabb417469a Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Wed, 4 Aug 2021 17:20:48 +1200
Subject: [PATCH v6 2/4] CREATE TABLESPACE ... NEW LOCATION.

Allow tablespace directories to be created on demand.  This allows the
regression test suite to be run without knowing the path of, or having
access to, the server's data directory.

Discussion: https://postgr.es/m/CA%2BhUKGKpRWQ9SxdxxDmTBCJoR0YnFpMBe7kyzY8SUQk%2BHeskxg%40mail.gmail.com
---
 doc/src/sgml/ref/create_tablespace.sgml | 14 ++++++----
 src/backend/commands/tablespace.c       | 36 ++++++++++++++++++-------
 src/backend/parser/gram.y               | 13 ++++++---
 src/include/commands/tablespace.h       |  1 +
 src/include/nodes/parsenodes.h          |  1 +
 5 files changed, 47 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/ref/create_tablespace.sgml b/doc/src/sgml/ref/create_tablespace.sgml
index 84fa7ee5e2..497b2568fb 100644
--- a/doc/src/sgml/ref/create_tablespace.sgml
+++ b/doc/src/sgml/ref/create_tablespace.sgml
@@ -23,7 +23,7 @@ PostgreSQL documentation
 <synopsis>
 CREATE TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
     [ OWNER { <replaceable>new_owner</replaceable> | CURRENT_ROLE | CURRENT_USER | SESSION_USER } ]
-    LOCATION '<replaceable class="parameter">directory</replaceable>'
+    [ NEW ] LOCATION '<replaceable class="parameter">directory</replaceable>'
     [ WITH ( <replaceable class="parameter">tablespace_option</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] ) ]
 </synopsis>
  </refsynopsisdiv>
@@ -92,10 +92,14 @@ CREATE TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
       <listitem>
        <para>
         The directory that will be used for the tablespace. The directory
-        must exist (<command>CREATE TABLESPACE</command> will not create it),
-        should be empty, and must be owned by the
-        <productname>PostgreSQL</productname> system user.  The directory must be
-        specified by an absolute path name.
+        must exist, be empty, and be owned by the
+        <productname>PostgreSQL</productname> system user unless
+        <command>NEW</command> is specified.  If
+        <command>NEW</command> is specified it will be created, and the parent
+        directory must exist.
+        The directory must be specified by an absolute path name, or a path
+        name that is relative to the data directory and begins with
+        <literal>pg_user_files/</literal>.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index adfbde6b29..b2a1a12fbc 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -91,7 +91,8 @@ char	   *temp_tablespaces = NULL;
 
 
 static void create_tablespace_directories(const char *location,
-										  const Oid tablespaceoid);
+										  const Oid tablespaceoid,
+										  bool missing_ok);
 static bool destroy_tablespace_directories(Oid tablespaceoid, bool redo);
 
 
@@ -369,13 +370,14 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
 	/* Post creation hook for new tablespace */
 	InvokeObjectPostCreateHook(TableSpaceRelationId, tablespaceoid, 0);
 
-	create_tablespace_directories(location, tablespaceoid);
+	create_tablespace_directories(location, tablespaceoid, stmt->missing_ok);
 
 	/* Record the filesystem change in XLOG */
 	{
 		xl_tblspc_create_rec xlrec;
 
 		xlrec.ts_id = tablespaceoid;
+		xlrec.ts_missing_ok = stmt->missing_ok;
 
 		XLogBeginInsert();
 		XLogRegisterData((char *) &xlrec,
@@ -588,7 +590,8 @@ DropTableSpace(DropTableSpaceStmt *stmt)
  *	to the specified directory
  */
 static void
-create_tablespace_directories(const char *location, const Oid tablespaceoid)
+create_tablespace_directories(const char *location, const Oid tablespaceoid,
+							  bool missing_ok)
 {
 	char		buffer[MAXPGPATH];
 	char	   *linkloc;
@@ -601,16 +604,28 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
 
 	/*
 	 * Attempt to coerce target directory to safe permissions.  If this fails,
-	 * it doesn't exist or has the wrong owner.
+	 * it doesn't exist or has the wrong owner.  If NEW LOCATION, we'll create
+	 * the directory.
 	 */
 	if (chmod(location, pg_dir_create_mode) != 0)
 	{
 		if (errno == ENOENT)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FILE),
-					 errmsg("directory \"%s\" does not exist", location),
-					 InRecovery ? errhint("Create this directory for the tablespace before "
-										  "restarting the server.") : 0));
+		{
+			if (missing_ok)
+			{
+				if (mkdir(location, pg_dir_create_mode) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_FILE),
+							 errmsg("could not create directory \"%s\"",
+									location)));
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_FILE),
+						 errmsg("directory \"%s\" does not exist", location),
+						 InRecovery ? errhint("Create this directory for the tablespace before "
+											  "restarting the server.") : 0));
+		}
 		else
 			ereport(ERROR,
 					(errcode_for_file_access(),
@@ -1537,8 +1552,9 @@ tblspc_redo(XLogReaderState *record)
 	{
 		xl_tblspc_create_rec *xlrec = (xl_tblspc_create_rec *) XLogRecGetData(record);
 		char	   *location = xlrec->ts_path;
+		bool		missing_ok = xlrec->ts_missing_ok;
 
-		create_tablespace_directories(location, xlrec->ts_id);
+		create_tablespace_directories(location, xlrec->ts_id, missing_ok);
 	}
 	else if (info == XLOG_TBLSPC_DROP)
 	{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a6d0cefa6b..591b6f437f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -578,6 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean> constraints_set_mode
 %type <str>		OptTableSpace OptConsTableSpace
 %type <rolespec> OptTableSpaceOwner
+%type <boolean> OptTableSpaceNew
 %type <ival>	opt_check_option
 
 %type <str>		opt_provider security_label
@@ -4559,13 +4560,14 @@ opt_procedural:
  *
  *****************************************************************************/
 
-CreateTableSpaceStmt: CREATE TABLESPACE name OptTableSpaceOwner LOCATION Sconst opt_reloptions
+CreateTableSpaceStmt: CREATE TABLESPACE name OptTableSpaceOwner OptTableSpaceNew LOCATION Sconst opt_reloptions
 				{
 					CreateTableSpaceStmt *n = makeNode(CreateTableSpaceStmt);
 					n->tablespacename = $3;
 					n->owner = $4;
-					n->location = $6;
-					n->options = $7;
+					n->missing_ok = $5;
+					n->location = $7;
+					n->options = $8;
 					$$ = (Node *) n;
 				}
 		;
@@ -4574,6 +4576,11 @@ OptTableSpaceOwner: OWNER RoleSpec		{ $$ = $2; }
 			| /*EMPTY */				{ $$ = NULL; }
 		;
 
+OptTableSpaceNew:
+			NEW							{ $$ = true; }
+			| /*EMPTY*/					{ $$ = false; }
+		;
+
 /*****************************************************************************
  *
  *		QUERY :
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index 49cfc8479a..7a53c886c0 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -26,6 +26,7 @@
 typedef struct xl_tblspc_create_rec
 {
 	Oid			ts_id;
+	bool		ts_missing_ok;
 	char		ts_path[FLEXIBLE_ARRAY_MEMBER]; /* null-terminated string */
 } xl_tblspc_create_rec;
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 067138e6b5..fc3893f965 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2322,6 +2322,7 @@ typedef struct CreateTableSpaceStmt
 	RoleSpec   *owner;
 	char	   *location;
 	List	   *options;
+	bool		missing_ok;
 } CreateTableSpaceStmt;
 
 typedef struct DropTableSpaceStmt
-- 
2.33.1

