From 9da56d39290b56f8d1b92ddac411dc26c70787e7 Mon Sep 17 00:00:00 2001
From: Maxim Orlov <orlovmg@gmail.com>
Date: Mon, 14 Nov 2022 18:24:58 +0300
Subject: [PATCH v1] Add "out of disk space" elog level.

---
 doc/src/sgml/config.sgml                      | 19 +++++++
 doc/src/sgml/ref/alter_tablespace.sgml        | 23 +++++---
 doc/src/sgml/ref/create_tablespace.sgml       | 23 +++++---
 src/backend/access/common/reloptions.c        | 23 +++++++-
 src/backend/access/heap/hio.c                 |  6 +++
 src/backend/storage/file/buffile.c            | 54 ++++++++++++++-----
 src/backend/storage/file/fd.c                 |  2 +-
 src/backend/storage/file/fileset.c            |  4 +-
 src/backend/storage/smgr/md.c                 |  7 ++-
 src/backend/utils/cache/spccache.c            | 36 +++++++++++++
 src/backend/utils/misc/guc_tables.c           | 15 ++++++
 src/backend/utils/misc/postgresql.conf.sample |  1 +
 src/bin/psql/tab-complete.c                   |  3 +-
 src/include/commands/tablespace.h             |  1 +
 src/include/storage/fd.h                      |  2 +-
 src/include/storage/fileset.h                 |  4 +-
 src/include/utils/spccache.h                  |  3 ++
 src/test/regress/expected/tablespace.out      | 23 +++++++-
 src/test/regress/sql/tablespace.sql           | 19 ++++++-
 19 files changed, 230 insertions(+), 38 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 559eb898a9..85e5906faa 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2526,6 +2526,25 @@ include_dir 'conf.d'
        </listitem>
       </varlistentry>
 
+      <varlistentry id="guc-on_no_space" xreflabel="on_no_space">
+       <term><varname>on_no_space</varname> (<type>integer</type>)
+       <indexterm>
+        <primary><varname>on_no_space</varname> configuration parameter</primary>
+       </indexterm>
+       </term>
+       <listitem>
+        <para>
+         Specifies the log level used for reporting of an insufficient disk
+         resources from the tablespace.
+        </para>
+        <para>
+         The default is ERROR.  This value can be overridden for tables in a
+         particular tablespace by setting the tablespace parameter of the same
+         name (see <xref linkend="sql-altertablespace"/>).
+        </para>
+       </listitem>
+      </varlistentry>
+
       <varlistentry id="guc-max-worker-processes" xreflabel="max_worker_processes">
        <term><varname>max_worker_processes</varname> (<type>integer</type>)
        <indexterm>
diff --git a/doc/src/sgml/ref/alter_tablespace.sgml b/doc/src/sgml/ref/alter_tablespace.sgml
index 6de80746d5..a37e417f7c 100644
--- a/doc/src/sgml/ref/alter_tablespace.sgml
+++ b/doc/src/sgml/ref/alter_tablespace.sgml
@@ -84,12 +84,18 @@ ALTER TABLESPACE <replaceable>name</replaceable> RESET ( <replaceable class="par
      <para>
       A tablespace parameter to be set or reset.  Currently, the only
       available parameters are <varname>seq_page_cost</varname>,
-      <varname>random_page_cost</varname>, <varname>effective_io_concurrency</varname>
-      and <varname>maintenance_io_concurrency</varname>.
-      Setting these values for a particular tablespace will override the
-      planner's usual estimate of the cost of reading pages from tables in
-      that tablespace, and the executor's prefetching behavior, as established
-      by the configuration parameters of the
+      <varname>random_page_cost</varname>, <varname>effective_io_concurrency</varname>,
+      <varname>maintenance_io_concurrency</varname> and <varname>on_no_space</varname>.
+      </para>
+
+     <para>
+      Setting <varname>seq_page_cost</varname>,
+      <varname>random_page_cost</varname>,
+      <varname>effective_io_concurrency</varname> and
+      <varname>maintenance_io_concurrency</varname>
+      values for a particular tablespace will override the planner's usual estimate
+      of the cost of reading pages from tables in that tablespace, and the executor's
+      prefetching behavior, as established by the configuration parameters of the
       same name (see <xref linkend="guc-seq-page-cost"/>,
       <xref linkend="guc-random-page-cost"/>,
       <xref linkend="guc-effective-io-concurrency"/>,
@@ -97,6 +103,11 @@ ALTER TABLESPACE <replaceable>name</replaceable> RESET ( <replaceable class="par
       one tablespace is located on a disk which is faster or slower than the
       remainder of the I/O subsystem.
      </para>
+
+     <para>
+      Setting <varname>on_no_space</varname> value for a particular tablespace
+      will override the <xref linkend="guc-on-no-space"/> value.
+     </para>
     </listitem>
    </varlistentry>
 
diff --git a/doc/src/sgml/ref/create_tablespace.sgml b/doc/src/sgml/ref/create_tablespace.sgml
index 9d5ab02526..bb075aa332 100644
--- a/doc/src/sgml/ref/create_tablespace.sgml
+++ b/doc/src/sgml/ref/create_tablespace.sgml
@@ -106,12 +106,18 @@ CREATE TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
        <para>
         A tablespace parameter to be set or reset.  Currently, the only
         available parameters are <varname>seq_page_cost</varname>,
-        <varname>random_page_cost</varname>, <varname>effective_io_concurrency</varname>
-        and <varname>maintenance_io_concurrency</varname>.
-        Setting these values for a particular tablespace will override the
-        planner's usual estimate of the cost of reading pages from tables in
-        that tablespace, and the executor's prefetching behavior, as established
-        by the configuration parameters of the
+        <varname>random_page_cost</varname>, <varname>effective_io_concurrency</varname>,
+        <varname>maintenance_io_concurrency</varname> and <varname>on_no_space</varname>.
+        </para>
+
+       <para>
+        Setting <varname>seq_page_cost</varname>,
+        <varname>random_page_cost</varname>,
+        <varname>effective_io_concurrency</varname> and
+        <varname>maintenance_io_concurrency</varname>
+        values for a particular tablespace will override the planner's usual estimate
+        of the cost of reading pages from tables in that tablespace, and the executor's
+        prefetching behavior, as established by the configuration parameters of the
         same name (see <xref linkend="guc-seq-page-cost"/>,
         <xref linkend="guc-random-page-cost"/>,
         <xref linkend="guc-effective-io-concurrency"/>,
@@ -119,6 +125,11 @@ CREATE TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
         one tablespace is located on a disk which is faster or slower than the
         remainder of the I/O subsystem.
        </para>
+
+       <para>
+        Setting <varname>on_no_space</varname> value for a particular tablespace
+        will override the <xref linkend="guc-on-no-space"/> value.
+       </para>
       </listitem>
      </varlistentry>
   </variablelist>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 75b7344891..105eaad862 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -507,6 +507,15 @@ static relopt_enum_elt_def viewCheckOptValues[] =
 	{(const char *) NULL}		/* list terminator */
 };
 
+/* some values from elog level */
+static relopt_enum_elt_def onNoSpaceOptValues[] =
+{
+	{"error", ERROR},
+	{"fatal", FATAL},
+	{"panic", PANIC},
+	{(const char *) NULL}		/* list terminator */
+};
+
 static relopt_enum enumRelOpts[] =
 {
 	{
@@ -542,6 +551,17 @@ static relopt_enum enumRelOpts[] =
 		VIEW_OPTION_CHECK_OPTION_NOT_SET,
 		gettext_noop("Valid values are \"local\" and \"cascaded\".")
 	},
+	{
+		{
+			"on_no_space",
+			"Specifies the log level used for reporting of an insufficient disk.",
+			RELOPT_KIND_TABLESPACE,
+			ShareUpdateExclusiveLock
+		},
+		onNoSpaceOptValues,
+		ERROR,
+		gettext_noop("Valid values are \"error\", \"fatal\" and \"panic\".")
+	},
 	/* list terminator */
 	{{NULL}}
 };
@@ -2090,7 +2110,8 @@ tablespace_reloptions(Datum reloptions, bool validate)
 		{"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)},
 		{"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)},
 		{"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)},
-		{"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)}
+		{"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)},
+		{"on_no_space", RELOPT_TYPE_ENUM, offsetof(TableSpaceOpts, on_no_space)}
 	};
 
 	return (bytea *) build_reloptions(reloptions, validate,
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index b0ece66629..dbe75f3ef0 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -23,6 +23,7 @@
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
+#include "utils/spccache.h"
 
 
 /*
@@ -422,6 +423,11 @@ RelationGetBufferForTuple(Relation relation, Size len,
 			targetBlock = nblocks - 1;
 	}
 
+	/*
+	 * Put tablespace into cache.
+	 */
+	get_tablespace_elevel(relation->rd_locator.spcOid);
+
 loop:
 	while (targetBlock != InvalidBlockNumber)
 	{
diff --git a/src/backend/storage/file/buffile.c b/src/backend/storage/file/buffile.c
index b0b4eeb3bd..57f146ecea 100644
--- a/src/backend/storage/file/buffile.c
+++ b/src/backend/storage/file/buffile.c
@@ -53,6 +53,7 @@
 #include "storage/buffile.h"
 #include "storage/fd.h"
 #include "utils/resowner.h"
+#include "utils/spccache.h"
 
 /*
  * We break BufFiles into gigabyte-sized segments, regardless of RELSEG_SIZE.
@@ -72,6 +73,7 @@ struct BufFile
 	int			numFiles;		/* number of physical files in set */
 	/* all files except the last have length exactly MAX_PHYSICAL_FILESIZE */
 	File	   *files;			/* palloc'd array with numFiles entries */
+	Oid		   *tablespaces;	/* palloc'd array with numFiles entries */
 
 	bool		isInterXact;	/* keep open over transactions? */
 	bool		dirty;			/* does buffer need to be written? */
@@ -99,12 +101,12 @@ struct BufFile
 };
 
 static BufFile *makeBufFileCommon(int nfiles);
-static BufFile *makeBufFile(File firstfile);
+static BufFile *makeBufFile(File firstfile, Oid tablespace);
 static void extendBufFile(BufFile *file);
 static void BufFileLoadBuffer(BufFile *file);
 static void BufFileDumpBuffer(BufFile *file);
 static void BufFileFlush(BufFile *file);
-static File MakeNewFileSetSegment(BufFile *buffile, int segment);
+static File MakeNewFileSetSegment(BufFile *file, int segment, Oid *tablespace);
 
 /*
  * Create BufFile and perform the common initialization.
@@ -131,12 +133,14 @@ makeBufFileCommon(int nfiles)
  * NOTE: caller must set isInterXact if appropriate.
  */
 static BufFile *
-makeBufFile(File firstfile)
+makeBufFile(File firstfile, Oid tablespace)
 {
 	BufFile    *file = makeBufFileCommon(1);
 
 	file->files = (File *) palloc(sizeof(File));
 	file->files[0] = firstfile;
+	file->tablespaces = (Oid *) palloc0(sizeof(Oid));
+	file->tablespaces[0] = tablespace;
 	file->readOnly = false;
 	file->fileset = NULL;
 	file->name = NULL;
@@ -151,6 +155,7 @@ static void
 extendBufFile(BufFile *file)
 {
 	File		pfile;
+	Oid			tablespace;
 	ResourceOwner oldowner;
 
 	/* Be sure to associate the file with the BufFile's resource owner */
@@ -158,9 +163,9 @@ extendBufFile(BufFile *file)
 	CurrentResourceOwner = file->resowner;
 
 	if (file->fileset == NULL)
-		pfile = OpenTemporaryFile(file->isInterXact);
+		pfile = OpenTemporaryFile(file->isInterXact, &tablespace);
 	else
-		pfile = MakeNewFileSetSegment(file, file->numFiles);
+		pfile = MakeNewFileSetSegment(file, file->numFiles, &tablespace);
 
 	Assert(pfile >= 0);
 
@@ -168,7 +173,10 @@ extendBufFile(BufFile *file)
 
 	file->files = (File *) repalloc(file->files,
 									(file->numFiles + 1) * sizeof(File));
+	file->tablespaces = (Oid *) repalloc(file->tablespaces,
+										 (file->numFiles + 1) * sizeof(Oid));
 	file->files[file->numFiles] = pfile;
+	file->tablespaces[file->numFiles] = tablespace;
 	file->numFiles++;
 }
 
@@ -189,6 +197,7 @@ BufFileCreateTemp(bool interXact)
 {
 	BufFile    *file;
 	File		pfile;
+	Oid			tablespace;
 
 	/*
 	 * Ensure that temp tablespaces are set up for OpenTemporaryFile to use.
@@ -201,10 +210,10 @@ BufFileCreateTemp(bool interXact)
 	 */
 	PrepareTempTablespaces();
 
-	pfile = OpenTemporaryFile(interXact);
+	pfile = OpenTemporaryFile(interXact, &tablespace);
 	Assert(pfile >= 0);
 
-	file = makeBufFile(pfile);
+	file = makeBufFile(pfile, tablespace);
 	file->isInterXact = interXact;
 
 	return file;
@@ -223,7 +232,7 @@ FileSetSegmentName(char *name, const char *buffile_name, int segment)
  * Create a new segment file backing a fileset based BufFile.
  */
 static File
-MakeNewFileSetSegment(BufFile *buffile, int segment)
+MakeNewFileSetSegment(BufFile *buffile, int segment, Oid *tablespace)
 {
 	char		name[MAXPGPATH];
 	File		file;
@@ -239,7 +248,7 @@ MakeNewFileSetSegment(BufFile *buffile, int segment)
 
 	/* Create the new segment. */
 	FileSetSegmentName(name, buffile->name, segment);
-	file = FileSetCreate(buffile->fileset, name);
+	file = FileSetCreate(buffile->fileset, name, tablespace);
 
 	/* FileSetCreate would've errored out */
 	Assert(file > 0);
@@ -262,12 +271,15 @@ BufFile *
 BufFileCreateFileSet(FileSet *fileset, const char *name)
 {
 	BufFile    *file;
+	Oid			tablespace;
 
 	file = makeBufFileCommon(1);
 	file->fileset = fileset;
 	file->name = pstrdup(name);
 	file->files = (File *) palloc(sizeof(File));
-	file->files[0] = MakeNewFileSetSegment(file, 0);
+	file->files[0] = MakeNewFileSetSegment(file, 0, &tablespace);
+	file->tablespaces = (Oid *) palloc(sizeof(Oid));
+	file->tablespaces[0] = tablespace;
 	file->readOnly = false;
 
 	return file;
@@ -290,9 +302,12 @@ BufFileOpenFileSet(FileSet *fileset, const char *name, int mode,
 	char		segment_name[MAXPGPATH];
 	Size		capacity = 16;
 	File	   *files;
+	Oid		   *tablespaces,
+				tablespace;
 	int			nfiles = 0;
 
 	files = palloc(sizeof(File) * capacity);
+	tablespaces = palloc(sizeof(Oid) * capacity);
 
 	/*
 	 * We don't know how many segments there are, so we'll probe the
@@ -305,10 +320,12 @@ BufFileOpenFileSet(FileSet *fileset, const char *name, int mode,
 		{
 			capacity *= 2;
 			files = repalloc(files, sizeof(File) * capacity);
+			tablespaces = repalloc(tablespaces, sizeof(Oid) * capacity);
 		}
 		/* Try to load a segment. */
 		FileSetSegmentName(segment_name, name, nfiles);
-		files[nfiles] = FileSetOpen(fileset, segment_name, mode);
+		files[nfiles] = FileSetOpen(fileset, segment_name, mode, &tablespace);
+		tablespaces[nfiles] = tablespace;
 		if (files[nfiles] <= 0)
 			break;
 		++nfiles;
@@ -324,6 +341,7 @@ BufFileOpenFileSet(FileSet *fileset, const char *name, int mode,
 	{
 		/* free the memory */
 		pfree(files);
+		pfree(tablespaces);
 
 		if (missing_ok)
 			return NULL;
@@ -336,6 +354,7 @@ BufFileOpenFileSet(FileSet *fileset, const char *name, int mode,
 
 	file = makeBufFileCommon(nfiles);
 	file->files = files;
+	file->tablespaces = tablespaces;
 	file->readOnly = (mode == O_RDONLY);
 	file->fileset = fileset;
 	file->name = pstrdup(name);
@@ -415,6 +434,7 @@ BufFileClose(BufFile *file)
 		FileClose(file->files[i]);
 	/* release the buffer space */
 	pfree(file->files);
+	pfree(file->tablespaces);
 	pfree(file);
 }
 
@@ -532,10 +552,14 @@ BufFileDumpBuffer(BufFile *file)
 								 file->curOffset,
 								 WAIT_EVENT_BUFFILE_WRITE);
 		if (bytestowrite <= 0)
-			ereport(ERROR,
+		{
+			Oid		tablespace = file->tablespaces[file->curFile];
+
+			ereport(get_tablespace_elevel(tablespace),
 					(errcode_for_file_access(),
 					 errmsg("could not write to file \"%s\": %m",
 							FilePathName(thisfile))));
+		}
 
 		if (track_io_timing)
 		{
@@ -885,8 +909,14 @@ BufFileAppend(BufFile *target, BufFile *source)
 
 	target->files = (File *)
 		repalloc(target->files, sizeof(File) * newNumFiles);
+	target->tablespaces = (Oid *)
+		repalloc(target->tablespaces, sizeof(Oid) * newNumFiles);
+
 	for (i = target->numFiles; i < newNumFiles; i++)
+	{
 		target->files[i] = source->files[i - target->numFiles];
+		target->tablespaces[i] = source->tablespaces[i - target->numFiles];
+	}
 	target->numFiles = newNumFiles;
 
 	return startBlock;
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 4151cafec5..61ae827cdf 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -1626,7 +1626,7 @@ PathNameDeleteTemporaryDir(const char *dirname)
  * case, the file is removed when the File is explicitly closed.
  */
 File
-OpenTemporaryFile(bool interXact)
+OpenTemporaryFile(bool interXact, Oid *tablespace)
 {
 	File		file = 0;
 
diff --git a/src/backend/storage/file/fileset.c b/src/backend/storage/file/fileset.c
index 9c63f2b267..10f57b95ac 100644
--- a/src/backend/storage/file/fileset.c
+++ b/src/backend/storage/file/fileset.c
@@ -91,7 +91,7 @@ FileSetInit(FileSet *fileset)
  * Create a new file in the given set.
  */
 File
-FileSetCreate(FileSet *fileset, const char *name)
+FileSetCreate(FileSet *fileset, const char *name, Oid *tablespace)
 {
 	char		path[MAXPGPATH];
 	File		file;
@@ -118,7 +118,7 @@ FileSetCreate(FileSet *fileset, const char *name)
 /*
  * Open a file that was created with FileSetCreate() */
 File
-FileSetOpen(FileSet *fileset, const char *name, int mode)
+FileSetOpen(FileSet *fileset, const char *name, int mode, Oid *tablespace)
 {
 	char		path[MAXPGPATH];
 	File		file;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 14b6fa0fd9..36b8b39f82 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -40,6 +40,7 @@
 #include "storage/sync.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
+#include "utils/spccache.h"
 
 /*
  *	The magnetic disk storage manager keeps track of open file
@@ -479,14 +480,16 @@ mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 
 	if ((nbytes = FileWrite(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_EXTEND)) != BLCKSZ)
 	{
+		int elevel = get_tablespace_elevel(reln->smgr_rlocator.locator.spcOid);
+
 		if (nbytes < 0)
-			ereport(ERROR,
+			ereport(elevel,
 					(errcode_for_file_access(),
 					 errmsg("could not extend file \"%s\": %m",
 							FilePathName(v->mdfd_vfd)),
 					 errhint("Check free disk space.")));
 		/* short write: complain appropriately */
-		ereport(ERROR,
+		ereport(elevel,
 				(errcode(ERRCODE_DISK_FULL),
 				 errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u",
 						FilePathName(v->mdfd_vfd),
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index 5609246c2f..d0f929c865 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -25,11 +25,24 @@
 #include "optimizer/optimizer.h"
 #include "storage/bufmgr.h"
 #include "utils/catcache.h"
+#include "utils/guc.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 
+/*
+ * GUC support
+ */
+const struct config_enum_entry on_no_space_options[] = {
+	{"error", ERROR, false},
+	{"fatal", FATAL, false},
+	{"panic", PANIC, false},
+	{NULL, 0, false}
+};
+
+/* GUC variable */
+int		on_no_space = ERROR;
 
 /* Hash table for information about each tablespace */
 static HTAB *TableSpaceCacheHash = NULL;
@@ -234,3 +247,26 @@ get_tablespace_maintenance_io_concurrency(Oid spcid)
 	else
 		return spc->opts->maintenance_io_concurrency;
 }
+
+/*
+ * get_tablespace_elevel
+ *
+ *		Return the error level for the namespace.
+ */
+int
+get_tablespace_elevel(Oid spcid)
+{
+	TableSpaceCacheEntry *spc;
+
+	/*
+	 * Use GUC level only in normal mode.
+	 */
+	if (!IsNormalProcessingMode())
+		return ERROR;
+
+	spc = get_tablespace(spcid);
+	if (!spc->opts)
+		return on_no_space;
+	else
+		return spc->opts->on_no_space;
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 836b49484a..a2f3461499 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -77,6 +77,7 @@
 #include "utils/pg_locale.h"
 #include "utils/portal.h"
 #include "utils/ps_status.h"
+#include "utils/spccache.h"
 #include "utils/queryjumble.h"
 #include "utils/inval.h"
 #include "utils/xml.h"
@@ -454,6 +455,7 @@ extern const struct config_enum_entry archive_mode_options[];
 extern const struct config_enum_entry recovery_target_action_options[];
 extern const struct config_enum_entry sync_method_options[];
 extern const struct config_enum_entry dynamic_shared_memory_options[];
+extern const struct config_enum_entry on_no_space_options[];
 
 /*
  * GUC option variables that are exported from this module
@@ -2914,6 +2916,19 @@ struct config_int ConfigureNamesInt[] =
 		NULL
 	},
 
+	{
+		{"on_no_space",
+			PGC_POSTMASTER,
+			RESOURCES_DISK,
+			gettext_noop("Error level on an out of disk memory."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&on_no_space,
+		ERROR, ERROR, PANIC,
+		NULL, NULL, NULL
+	},
+
 	{
 		{"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Number of pages after which previously performed writes are flushed to disk."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 868d21c351..b947ed928d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -160,6 +160,7 @@
 
 #temp_file_limit = -1			# limits per-process temp file space
 					# in kilobytes, or -1 for no limit
+#on_no_space = ERROR
 
 # - Kernel Resources -
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7b73886ce1..94a78c5b6e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2514,7 +2514,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLESPACE <foo> SET|RESET ( */
 	else if (Matches("ALTER", "TABLESPACE", MatchAny, "SET|RESET", "("))
 		COMPLETE_WITH("seq_page_cost", "random_page_cost",
-					  "effective_io_concurrency", "maintenance_io_concurrency");
+					  "effective_io_concurrency", "maintenance_io_concurrency",
+					  "on_no_space");
 
 	/* ALTER TEXT SEARCH */
 	else if (Matches("ALTER", "TEXT", "SEARCH"))
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index a11c9e9473..75d3a3b061 100644
--- a/src/include/commands/tablespace.h
+++ b/src/include/commands/tablespace.h
@@ -43,6 +43,7 @@ typedef struct TableSpaceOpts
 	float8		seq_page_cost;
 	int			effective_io_concurrency;
 	int			maintenance_io_concurrency;
+	int			on_no_space;
 } TableSpaceOpts;
 
 extern Oid	CreateTableSpace(CreateTableSpaceStmt *stmt);
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index c0a212487d..5c0073f28c 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -100,7 +100,7 @@ extern PGDLLIMPORT int max_safe_fds;
 /* Operations on virtual Files --- equivalent to Unix kernel file ops */
 extern File PathNameOpenFile(const char *fileName, int fileFlags);
 extern File PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
-extern File OpenTemporaryFile(bool interXact);
+extern File OpenTemporaryFile(bool interXact, Oid *tablespace);
 extern void FileClose(File file);
 extern int	FilePrefetch(File file, off_t offset, int amount, uint32 wait_event_info);
 extern int	FileRead(File file, char *buffer, int amount, off_t offset, uint32 wait_event_info);
diff --git a/src/include/storage/fileset.h b/src/include/storage/fileset.h
index ad37884717..e50da55d95 100644
--- a/src/include/storage/fileset.h
+++ b/src/include/storage/fileset.h
@@ -30,9 +30,9 @@ typedef struct FileSet
 } FileSet;
 
 extern void FileSetInit(FileSet *fileset);
-extern File FileSetCreate(FileSet *fileset, const char *name);
+extern File FileSetCreate(FileSet *fileset, const char *name, Oid *tablespace);
 extern File FileSetOpen(FileSet *fileset, const char *name,
-						int mode);
+						int mode, Oid *tablespace);
 extern bool FileSetDelete(FileSet *fileset, const char *name,
 						  bool error_on_failure);
 extern void FileSetDeleteAll(FileSet *fileset);
diff --git a/src/include/utils/spccache.h b/src/include/utils/spccache.h
index 5163eeedbd..a31304c0fb 100644
--- a/src/include/utils/spccache.h
+++ b/src/include/utils/spccache.h
@@ -13,9 +13,12 @@
 #ifndef SPCCACHE_H
 #define SPCCACHE_H
 
+extern PGDLLIMPORT int on_no_space;
+
 extern void get_tablespace_page_costs(Oid spcid, float8 *spc_random_page_cost,
 									  float8 *spc_seq_page_cost);
 extern int	get_tablespace_io_concurrency(Oid spcid);
 extern int	get_tablespace_maintenance_io_concurrency(Oid spcid);
+extern int	get_tablespace_elevel(Oid spcid);
 
 #endif							/* SPCCACHE_H */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf..5b92008e1e 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -22,6 +22,22 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 
 -- drop the tablespace so we can re-use the location
 DROP TABLESPACE regress_tblspacewith;
+-- check "on_no_space" tablespace opt
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = 1); -- fail
+ERROR:  invalid value for enum option "on_no_space": 1
+DETAIL:  Valid values are "error", "fatal" and "panic".
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = true); -- fail
+ERROR:  invalid value for enum option "on_no_space": true
+DETAIL:  Valid values are "error", "fatal" and "panic".
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = WARNING); -- fail
+ERROR:  invalid value for enum option "on_no_space": warning
+DETAIL:  Valid values are "error", "fatal" and "panic".
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = ERROR); -- ok
+DROP TABLESPACE regress_tblspacewith;
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = FATAL); -- ok
+DROP TABLESPACE regress_tblspacewith;
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = PANIC); -- ok
+DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '';
 -- This returns a relative path as of an effect of allow_in_place_tablespaces,
@@ -34,12 +50,15 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 (1 row)
 
 -- try setting and resetting some properties for the new tablespace
-ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
+ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1, on_no_space = PANIC);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
 ERROR:  unrecognized parameter "some_nonexistent_parameter"
+ALTER TABLESPACE regress_tblspace SET (on_no_space = WARNING);  -- fail
+ERROR:  invalid value for enum option "on_no_space": warning
+DETAIL:  Valid values are "error", "fatal" and "panic".
 ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
 ERROR:  RESET must not include values for parameters
-ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
+ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency, on_no_space); -- ok
 -- REINDEX (TABLESPACE)
 -- catalogs and system tablespaces
 -- system catalog, fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a..aa1100b641 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -20,6 +20,20 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 -- drop the tablespace so we can re-use the location
 DROP TABLESPACE regress_tblspacewith;
 
+-- check "on_no_space" tablespace opt
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = 1); -- fail
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = true); -- fail
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = WARNING); -- fail
+
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = ERROR); -- ok
+DROP TABLESPACE regress_tblspacewith;
+
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = FATAL); -- ok
+DROP TABLESPACE regress_tblspacewith;
+
+CREATE TABLESPACE regress_tblspacewith LOCATION '' WITH (on_no_space = PANIC); -- ok
+DROP TABLESPACE regress_tblspacewith;
+
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '';
 -- This returns a relative path as of an effect of allow_in_place_tablespaces,
@@ -28,10 +42,11 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
   FROM pg_tablespace  WHERE spcname = 'regress_tblspace';
 
 -- try setting and resetting some properties for the new tablespace
-ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
+ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1, on_no_space = PANIC);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
+ALTER TABLESPACE regress_tblspace SET (on_no_space = WARNING);  -- fail
 ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail
-ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok
+ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency, on_no_space); -- ok
 
 -- REINDEX (TABLESPACE)
 -- catalogs and system tablespaces
-- 
2.38.1

