From 6687650ce6c1a1c688db96df82e8c9173ccbbc57 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 25 Apr 2003 02:28:22 +0000
Subject: [PATCH] COPY and pg_dump failed to cope with zero-column tables.  Fix
 'em.

---
 src/backend/commands/copy.c | 50 +++++++++++++++++-------
 src/bin/pg_dump/pg_dump.c   | 78 ++++++++++++++++++-------------------
 2 files changed, 75 insertions(+), 53 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7cb530a3cda..ec6e771aedc 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.196 2003/04/24 21:16:42 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.197 2003/04/25 02:28:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -808,10 +808,11 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
 	 * Get info about the columns we need to process.
 	 *
 	 * For binary copy we really only need isvarlena, but compute it all...
+	 * +1's here are to avoid palloc(0) in a zero-column table.
 	 */
-	out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
-	elements = (Oid *) palloc(num_phys_attrs * sizeof(Oid));
-	isvarlena = (bool *) palloc(num_phys_attrs * sizeof(bool));
+	out_functions = (FmgrInfo *) palloc((num_phys_attrs + 1) * sizeof(FmgrInfo));
+	elements = (Oid *) palloc((num_phys_attrs + 1) * sizeof(Oid));
+	isvarlena = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
 	foreach(cur, attnumlist)
 	{
 		int			attnum = lfirsti(cur);
@@ -1078,12 +1079,13 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	 * relation, including the input function, the element type (to pass
 	 * to the input function), and info about defaults and constraints.
 	 * (We don't actually use the input function if it's a binary copy.)
+	 * +1's here are to avoid palloc(0) in a zero-column table.
 	 */
-	in_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
-	elements = (Oid *) palloc(num_phys_attrs * sizeof(Oid));
-	defmap = (int *) palloc(num_phys_attrs * sizeof(int));
-	defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
-	constraintexprs = (ExprState **) palloc0(num_phys_attrs * sizeof(ExprState *));
+	in_functions = (FmgrInfo *) palloc((num_phys_attrs + 1) * sizeof(FmgrInfo));
+	elements = (Oid *) palloc((num_phys_attrs + 1) * sizeof(Oid));
+	defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int));
+	defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *));
+	constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *));
 
 	for (i = 0; i < num_phys_attrs; i++)
 	{
@@ -1196,8 +1198,8 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 		}
 	}
 
-	values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
-	nulls = (char *) palloc(num_phys_attrs * sizeof(char));
+	values = (Datum *) palloc((num_phys_attrs + 1) * sizeof(Datum));
+	nulls = (char *) palloc((num_phys_attrs + 1) * sizeof(char));
 
 	/* Make room for a PARAM_EXEC value for domain constraint checks */
 	if (hasConstraints)
@@ -1304,9 +1306,31 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 			if (done)
 				break;			/* out of per-row loop */
 
-			/* Complain if there are more fields on the input line */
+			/*
+			 * Complain if there are more fields on the input line.
+			 *
+			 * Special case: if we're reading a zero-column table, we
+			 * won't yet have called CopyReadAttribute() at all; so do that
+			 * and check we have an empty line.  Fortunately we can keep that
+			 * silly corner case out of the main line of execution.
+			 */
 			if (result == NORMAL_ATTR)
-				elog(ERROR, "Extra data after last expected column");
+			{
+				if (attnumlist == NIL && !file_has_oids)
+				{
+					string = CopyReadAttribute(delim, &result);
+					if (result == NORMAL_ATTR || *string != '\0')
+						elog(ERROR, "Extra data after last expected column");
+					if (result == END_OF_FILE)
+					{
+						/* EOF at start of line: all is well */
+						done = true;
+						break;
+					}
+				}
+				else
+					elog(ERROR, "Extra data after last expected column");
+			}
 
 			/*
 			 * If we got some data on the line, but it was ended by EOF,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 409a961701a..84cf3950ec1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.326 2003/04/04 20:42:12 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.327 2003/04/25 02:28:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -838,9 +838,6 @@ dumpClasses_nodumpData(Archive *fout, char *oid, void *dctxv)
 	 * possibility of retrieving data in the wrong column order.  (The
 	 * default column ordering of COPY will not be what we want in certain
 	 * corner cases involving ADD COLUMN and inheritance.)
-	 *
-	 * NB: caller should have already determined that there are dumpable
-	 * columns, so that fmtCopyColumnList will return something.
 	 */
 	if (g_fout->remoteVersion >= 70300)
 		column_list = fmtCopyColumnList(tbinfo);
@@ -975,6 +972,7 @@ dumpClasses_dumpData(Archive *fout, char *oid, void *dctxv)
 	PQExpBuffer q = createPQExpBuffer();
 	PGresult   *res;
 	int			tuple;
+	int			nfields;
 	int			field;
 
 	/*
@@ -1024,14 +1022,21 @@ dumpClasses_dumpData(Archive *fout, char *oid, void *dctxv)
 			exit_nicely();
 		}
 
+		nfields = PQnfields(res);
 		for (tuple = 0; tuple < PQntuples(res); tuple++)
 		{
 			archprintf(fout, "INSERT INTO %s ", fmtId(classname));
+			if (nfields == 0)
+			{
+				/* corner case for zero-column table */
+				archprintf(fout, "DEFAULT VALUES;\n");
+				continue;
+			}
 			if (attrNames == true)
 			{
 				resetPQExpBuffer(q);
 				appendPQExpBuffer(q, "(");
-				for (field = 0; field < PQnfields(res); field++)
+				for (field = 0; field < nfields; field++)
 				{
 					if (field > 0)
 						appendPQExpBuffer(q, ", ");
@@ -1041,7 +1046,7 @@ dumpClasses_dumpData(Archive *fout, char *oid, void *dctxv)
 				archprintf(fout, "%s", q->data);
 			}
 			archprintf(fout, "VALUES (");
-			for (field = 0; field < PQnfields(res); field++)
+			for (field = 0; field < nfields; field++)
 			{
 				if (field > 0)
 					archprintf(fout, ", ");
@@ -1154,45 +1159,38 @@ dumpClasses(const TableInfo *tblinfo, const int numTables, Archive *fout,
 
 		if (tblinfo[i].dump)
 		{
-			const char *column_list;
-
 			if (g_verbose)
 				write_msg(NULL, "preparing to dump the contents of table %s\n",
 						  classname);
 
-			/* Get column list first to check for zero-column table */
-			column_list = fmtCopyColumnList(&(tblinfo[i]));
-			if (column_list)
-			{
-				dumpCtx = (DumpContext *) malloc(sizeof(DumpContext));
-				dumpCtx->tblinfo = (TableInfo *) tblinfo;
-				dumpCtx->tblidx = i;
-				dumpCtx->oids = oids;
+			dumpCtx = (DumpContext *) malloc(sizeof(DumpContext));
+			dumpCtx->tblinfo = (TableInfo *) tblinfo;
+			dumpCtx->tblidx = i;
+			dumpCtx->oids = oids;
 
-				if (!dumpData)
-				{
-					/* Dump/restore using COPY */
-					dumpFn = dumpClasses_nodumpData;
-					resetPQExpBuffer(copyBuf);
-					appendPQExpBuffer(copyBuf, "COPY %s %s %sFROM stdin;\n",
-									  fmtId(tblinfo[i].relname),
-									  column_list,
+			if (!dumpData)
+			{
+				/* Dump/restore using COPY */
+				dumpFn = dumpClasses_nodumpData;
+				resetPQExpBuffer(copyBuf);
+				appendPQExpBuffer(copyBuf, "COPY %s %s %sFROM stdin;\n",
+								  fmtId(tblinfo[i].relname),
+								  fmtCopyColumnList(&(tblinfo[i])),
 					   (oids && tblinfo[i].hasoids) ? "WITH OIDS " : "");
-					copyStmt = copyBuf->data;
-				}
-				else
-				{
-					/* Restore using INSERT */
-					dumpFn = dumpClasses_dumpData;
-					copyStmt = NULL;
-				}
-
-				ArchiveEntry(fout, tblinfo[i].oid, tblinfo[i].relname,
-							 tblinfo[i].relnamespace->nspname,
-							 tblinfo[i].usename,
-							 "TABLE DATA", NULL, "", "", copyStmt,
-							 dumpFn, dumpCtx);
+				copyStmt = copyBuf->data;
 			}
+			else
+			{
+				/* Restore using INSERT */
+				dumpFn = dumpClasses_dumpData;
+				copyStmt = NULL;
+			}
+
+			ArchiveEntry(fout, tblinfo[i].oid, tblinfo[i].relname,
+						 tblinfo[i].relnamespace->nspname,
+						 tblinfo[i].usename,
+						 "TABLE DATA", NULL, "", "", copyStmt,
+						 dumpFn, dumpCtx);
 		}
 	}
 
@@ -6980,7 +6978,7 @@ fmtQualifiedId(const char *schema, const char *id)
  * Return a column list clause for the given relation.
  *
  * Special case: if there are no undropped columns in the relation, return
- * NULL, not an invalid "()" column list.
+ * "", not an invalid "()" column list.
  */
 static const char *
 fmtCopyColumnList(const TableInfo *ti)
@@ -7010,7 +7008,7 @@ fmtCopyColumnList(const TableInfo *ti)
 	}
 
 	if (!needComma)
-		return NULL;			/* no undropped columns */
+		return "";				/* no undropped columns */
 
 	appendPQExpBuffer(q, ")");
 	return q->data;
-- 
GitLab