/*-------------------------------------------------------------------------
 *
 * copy.c
 *
 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.125 2000/12/02 20:49:24 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>
#include <sys/stat.h>

#include "access/genam.h"
#include "access/heapam.h"
#include "access/printtup.h"
#include "catalog/catname.h"
#include "catalog/index.h"
#include "catalog/pg_index.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_type.h"
#include "commands/copy.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/relcache.h"
#include "utils/syscache.h"

#ifdef MULTIBYTE
#include "mb/pg_wchar.h"
#endif

#define ISOCTAL(c) (((c) >= '0') && ((c) <= '7'))
#define VALUE(c) ((c) - '0')


/* non-export function prototypes */
static void CopyTo(Relation rel, bool binary, bool oids, FILE *fp, char *delim, char *null_print);
static void CopyFrom(Relation rel, bool binary, bool oids, FILE *fp, char *delim, char *null_print);
static Oid	GetInputFunction(Oid type);
static Oid	GetTypeElement(Oid type);
static bool IsTypeByVal(Oid type);
static void CopyReadNewline(FILE *fp, int *newline);
static char *CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print);
static void CopyAttributeOut(FILE *fp, char *string, char *delim);
static int	CountTuples(Relation relation);

/*
 * Static communication variables ... pretty grotty, but COPY has
 * never been reentrant...
 */
int			lineno = 0;			/* exported for use by elog() -- dz */
static bool fe_eof;

/*
 * These static variables are used to avoid incurring overhead for each
 * attribute processed.  attribute_buf is reused on each CopyReadAttribute
 * call to hold the string being read in.  Under normal use it will soon
 * grow to a suitable size, and then we will avoid palloc/pfree overhead
 * for subsequent attributes.  Note that CopyReadAttribute returns a pointer
 * to attribute_buf's data buffer!
 * encoding, if needed, can be set once at the start of the copy operation.
 */
static StringInfoData attribute_buf;

#ifdef MULTIBYTE
static int	encoding;

#endif


/*
 * Internal communications functions
 */
static void CopySendData(void *databuf, int datasize, FILE *fp);
static void CopySendString(char *str, FILE *fp);
static void CopySendChar(char c, FILE *fp);
static void CopyGetData(void *databuf, int datasize, FILE *fp);
static int	CopyGetChar(FILE *fp);
static int	CopyGetEof(FILE *fp);
static int	CopyPeekChar(FILE *fp);
static void CopyDonePeek(FILE *fp, int c, int pickup);

/*
 * CopySendData sends output data either to the file
 *	specified by fp or, if fp is NULL, using the standard
 *	backend->frontend functions
 *
 * CopySendString does the same for null-terminated strings
 * CopySendChar does the same for single characters
 *
 * NB: no data conversion is applied by these functions
 */
static void
CopySendData(void *databuf, int datasize, FILE *fp)
{
	if (!fp)
	{
		if (pq_putbytes((char *) databuf, datasize))
			fe_eof = true;
	}
	else
	{
		fwrite(databuf, datasize, 1, fp);
		if (ferror(fp))
			elog(ERROR, "CopySendData: %m");
	}
}

static void
CopySendString(char *str, FILE *fp)
{
	CopySendData(str, strlen(str), fp);
}

static void
CopySendChar(char c, FILE *fp)
{
	CopySendData(&c, 1, fp);
}

/*
 * CopyGetData reads output data either from the file
 *	specified by fp or, if fp is NULL, using the standard
 *	backend->frontend functions
 *
 * CopyGetChar does the same for single characters
 * CopyGetEof checks if it's EOF on the input (or, check for EOF result
 *		from CopyGetChar)
 *
 * NB: no data conversion is applied by these functions
 */
static void
CopyGetData(void *databuf, int datasize, FILE *fp)
{
	if (!fp)
	{
		if (pq_getbytes((char *) databuf, datasize))
			fe_eof = true;
	}
	else
		fread(databuf, datasize, 1, fp);
}

static int
CopyGetChar(FILE *fp)
{
	if (!fp)
	{
		unsigned char ch;

		if (pq_getbytes((char *) &ch, 1))
		{
			fe_eof = true;
			return EOF;
		}
		return ch;
	}
	else
		return getc(fp);
}

static int
CopyGetEof(FILE *fp)
{
	if (!fp)
		return fe_eof;
	else
		return feof(fp);
}

/*
 * CopyPeekChar reads a byte in "peekable" mode.
 * after each call to CopyPeekChar, a call to CopyDonePeek _must_
 * follow, unless EOF was returned.
 * CopyDonePeek will either take the peeked char off the steam
 * (if pickup is != 0) or leave it on the stream (if pickup == 0)
 */
static int
CopyPeekChar(FILE *fp)
{
	if (!fp)
	{
		int			ch = pq_peekbyte();

		if (ch == EOF)
			fe_eof = true;
		return ch;
	}
	else
		return getc(fp);
}

static void
CopyDonePeek(FILE *fp, int c, int pickup)
{
	if (!fp)
	{
		if (pickup)
		{

			/*
			 * We want to pick it up - just receive again into dummy
			 * buffer
			 */
			char		c;

			pq_getbytes(&c, 1);
		}
		/* If we didn't want to pick it up, just leave it where it sits */
	}
	else
	{
		if (!pickup)
		{
			/* We don't want to pick it up - so put it back in there */
			ungetc(c, fp);
		}
		/* If we wanted to pick it up, it's already there */
	}
}



/*
 *	 DoCopy executes the SQL COPY statement.
 */

void
DoCopy(char *relname, bool binary, bool oids, bool from, bool pipe,
	   char *filename, char *delim, char *null_print)
{
/*----------------------------------------------------------------------------
  Either unload or reload contents of class <relname>, depending on <from>.

  If <pipe> is false, transfer is between the class and the file named
  <filename>.  Otherwise, transfer is between the class and our regular
  input/output stream.	The latter could be either stdin/stdout or a
  socket, depending on whether we're running under Postmaster control.

  Iff <binary>, unload or reload in the binary format, as opposed to the
  more wasteful but more robust and portable text format.

  If in the text format, delimit columns with delimiter <delim> and print
  NULL values as <null_print>.

  When loading in the text format from an input stream (as opposed to
  a file), recognize a "." on a line by itself as EOF.	Also recognize
  a stream EOF.  When unloading in the text format to an output stream,
  write a "." on a line by itself at the end of the data.

  Iff <oids>, unload or reload the format that includes OID information.

  Do not allow a Postgres user without superuser privilege to read from
  or write to a file.

  Do not allow the copy if user doesn't have proper permission to access
  the class.
----------------------------------------------------------------------------*/

	FILE	   *fp;
	Relation	rel;
	const AclMode required_access = from ? ACL_WR : ACL_RD;
	int			result;

	/*
	 * Open and lock the relation, using the appropriate lock type.
	 */
	rel = heap_openr(relname, (from ? RowExclusiveLock : AccessShareLock));

	result = pg_aclcheck(relname, GetUserId(), required_access);
	if (result != ACLCHECK_OK)
		elog(ERROR, "%s: %s", relname, aclcheck_error_strings[result]);
	if (!pipe && !superuser())
		elog(ERROR, "You must have Postgres superuser privilege to do a COPY "
			 "directly to or from a file.  Anyone can COPY to stdout or "
			 "from stdin.  Psql's \\copy command also works for anyone.");
	/*
	 * This restriction is unfortunate, but necessary until the frontend
	 * COPY protocol is redesigned to be binary-safe...
	 */
	if (pipe && binary)
		elog(ERROR, "COPY BINARY is not supported to stdout or from stdin");

	/*
	 * Set up variables to avoid per-attribute overhead.
	 */
	initStringInfo(&attribute_buf);
#ifdef MULTIBYTE
	encoding = pg_get_client_encoding();
#endif

	if (from)
	{							/* copy from file to database */
		if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
			elog(ERROR, "You cannot change sequence relation %s", relname);
		if (pipe)
		{
			if (IsUnderPostmaster)
			{
				ReceiveCopyBegin();
				fp = NULL;
			}
			else
				fp = stdin;
		}
		else
		{
			fp = AllocateFile(filename, PG_BINARY_R);
			if (fp == NULL)
				elog(ERROR, "COPY command, running in backend with "
					 "effective uid %d, could not open file '%s' for "
					 "reading.  Errno = %s (%d).",
					 (int) geteuid(), filename, strerror(errno), errno);
		}
		CopyFrom(rel, binary, oids, fp, delim, null_print);
	}
	else
	{							/* copy from database to file */
		if (pipe)
		{
			if (IsUnderPostmaster)
			{
				SendCopyBegin();
				pq_startcopyout();
				fp = NULL;
			}
			else
				fp = stdout;
		}
		else
		{
			mode_t		oumask; /* Pre-existing umask value */

			/*
			 * Prevent write to relative path ... too easy to shoot oneself
			 * in the foot by overwriting a database file ...
			 */
			if (filename[0] != '/')
				elog(ERROR, "Relative path not allowed for server side"
					 " COPY command.");

			oumask = umask((mode_t) 022);
			fp = AllocateFile(filename, PG_BINARY_W);
			umask(oumask);

			if (fp == NULL)
				elog(ERROR, "COPY command, running in backend with "
					 "effective uid %d, could not open file '%s' for "
					 "writing.  Errno = %s (%d).",
					 (int) geteuid(), filename, strerror(errno), errno);
		}
		CopyTo(rel, binary, oids, fp, delim, null_print);
	}

	if (!pipe)
		FreeFile(fp);
	else if (!from)
	{
		if (!binary)
			CopySendData("\\.\n", 3, fp);
		if (IsUnderPostmaster)
			pq_endcopyout(false);
	}
	pfree(attribute_buf.data);

	/*
	 * Close the relation.	If reading, we can release the AccessShareLock
	 * we got; if writing, we should hold the lock until end of
	 * transaction to ensure that updates will be committed before lock is
	 * released.
	 */
	heap_close(rel, (from ? NoLock : AccessShareLock));
}


/*
 * Copy from relation TO file.
 */
static void
CopyTo(Relation rel, bool binary, bool oids, FILE *fp, char *delim, char *null_print)
{
	HeapTuple	tuple;
	TupleDesc	tupDesc;
	HeapScanDesc scandesc;
	int			attr_count,
				i;
	Form_pg_attribute *attr;
	FmgrInfo   *out_functions;
	Oid		   *elements;
	bool	   *isvarlena;
	int32	   *typmod;
	char	   *nulls;

	/*
	 * <nulls> is a (dynamically allocated) array with one character per
	 * attribute in the instance being copied.	nulls[I-1] is 'n' if
	 * Attribute Number I is null, and ' ' otherwise.
	 *
	 * <nulls> is meaningful only if we are doing a binary copy.
	 */
	char	   *string;

	scandesc = heap_beginscan(rel, 0, QuerySnapshot, 0, NULL);

	tupDesc = rel->rd_att;
	attr_count = rel->rd_att->natts;
	attr = rel->rd_att->attrs;

	/* For binary copy we really only need isvarlena, but compute it all... */
	out_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo));
	elements = (Oid *) palloc(attr_count * sizeof(Oid));
	isvarlena = (bool *) palloc(attr_count * sizeof(bool));
	typmod = (int32 *) palloc(attr_count * sizeof(int32));
	for (i = 0; i < attr_count; i++)
	{
		Oid			out_func_oid;

		if (!getTypeOutputInfo(attr[i]->atttypid,
							   &out_func_oid, &elements[i], &isvarlena[i]))
			elog(ERROR, "COPY: couldn't lookup info for type %u",
				 attr[i]->atttypid);
		fmgr_info(out_func_oid, &out_functions[i]);
		typmod[i] = attr[i]->atttypmod;
	}

	if (!binary)
	{
		nulls = NULL;			/* meaningless, but compiler doesn't know
								 * that */
	}
	else
	{
		int32		ntuples;

		nulls = (char *) palloc(attr_count);
		for (i = 0; i < attr_count; i++)
			nulls[i] = ' ';

		/* XXX expensive */

		ntuples = CountTuples(rel);
		CopySendData(&ntuples, sizeof(int32), fp);
	}

	while (HeapTupleIsValid(tuple = heap_getnext(scandesc, 0)))
	{
		if (QueryCancel)
			CancelQuery();

		if (oids && !binary)
		{
			string = DatumGetCString(DirectFunctionCall1(oidout,
									 ObjectIdGetDatum(tuple->t_data->t_oid)));
			CopySendString(string, fp);
			CopySendChar(delim[0], fp);
			pfree(string);
		}

		for (i = 0; i < attr_count; i++)
		{
			Datum		origvalue,
						value;
			bool		isnull;

			origvalue = heap_getattr(tuple, i + 1, tupDesc, &isnull);

			if (isnull)
			{
				if (!binary)
					CopySendString(null_print, fp);	/* null indicator */
				else
					nulls[i] = 'n';
			}
			else
			{
				/*
				 * If we have a toasted datum, forcibly detoast it to avoid
				 * memory leakage inside the type's output routine.
				 */
				if (isvarlena[i])
					value = PointerGetDatum(PG_DETOAST_DATUM(origvalue));
				else
					value = origvalue;

				if (!binary)
				{
					string = DatumGetCString(FunctionCall3(&out_functions[i],
												value,
												ObjectIdGetDatum(elements[i]),
												Int32GetDatum(typmod[i])));
					CopyAttributeOut(fp, string, delim);
					pfree(string);
				}

				/* Clean up detoasted copy, if any */
				if (value != origvalue)
					pfree(DatumGetPointer(value));
			}

			if (!binary)
			{
				if (i == attr_count - 1)
					CopySendChar('\n', fp);
				else
				{

					/*
					 * when copying out, only use the first char of the
					 * delim string
					 */
					CopySendChar(delim[0], fp);
				}
			}
		}

		if (binary)
		{
			int32		null_ct = 0,
						length;

			for (i = 0; i < attr_count; i++)
			{
				if (nulls[i] == 'n')
					null_ct++;
			}

			length = tuple->t_len - tuple->t_data->t_hoff;
			CopySendData(&length, sizeof(int32), fp);
			if (oids)
				CopySendData((char *) &tuple->t_data->t_oid, sizeof(int32), fp);

			CopySendData(&null_ct, sizeof(int32), fp);
			if (null_ct > 0)
			{
				for (i = 0; i < attr_count; i++)
				{
					if (nulls[i] == 'n')
					{
						CopySendData(&i, sizeof(int32), fp);
						nulls[i] = ' ';
					}
				}
			}
			CopySendData((char *) tuple->t_data + tuple->t_data->t_hoff,
						 length, fp);
		}
	}

	heap_endscan(scandesc);

	pfree(out_functions);
	pfree(elements);
	pfree(isvarlena);
	pfree(typmod);
	if (binary)
		pfree(nulls);
}


/*
 * Copy FROM file to relation.
 */
static void
CopyFrom(Relation rel, bool binary, bool oids, FILE *fp,
		 char *delim, char *null_print)
{
	HeapTuple	tuple;
	TupleDesc	tupDesc;
	Form_pg_attribute *attr;
	AttrNumber	attr_count;
	FmgrInfo   *in_functions;
	Oid		   *elements;
	int32	   *typmod;
	int			i;
	Oid			in_func_oid;
	Datum	   *values;
	char	   *nulls;
	bool	   *byval;
	bool		isnull;
	int			done = 0;
	char	   *string = NULL,
			   *ptr;
	int32		len,
				null_ct,
				null_id;
	int32		ntuples,
				tuples_read = 0;
	bool		reading_to_eof = true;
	ResultRelInfo *resultRelInfo;
	EState	   *estate = CreateExecutorState();	/* for ExecConstraints() */
	TupleTable	tupleTable;
	TupleTableSlot *slot;
	Oid			loaded_oid = InvalidOid;
	bool		skip_tuple = false;

	tupDesc = RelationGetDescr(rel);
	attr = tupDesc->attrs;
	attr_count = tupDesc->natts;

	/*
	 * We need a ResultRelInfo so we can use the regular executor's
	 * index-entry-making machinery.  (There used to be a huge amount
	 * of code here that basically duplicated execUtils.c ...)
	 */
	resultRelInfo = makeNode(ResultRelInfo);
	resultRelInfo->ri_RangeTableIndex = 1; /* dummy */
	resultRelInfo->ri_RelationDesc = rel;

	ExecOpenIndices(resultRelInfo);

	estate->es_result_relations = resultRelInfo;
	estate->es_num_result_relations = 1;
	estate->es_result_relation_info = resultRelInfo;

	/* Set up a dummy tuple table too */
	tupleTable = ExecCreateTupleTable(1);
	slot = ExecAllocTableSlot(tupleTable);
	ExecSetSlotDescriptor(slot, tupDesc);

	if (!binary)
	{
		in_functions = (FmgrInfo *) palloc(attr_count * sizeof(FmgrInfo));
		elements = (Oid *) palloc(attr_count * sizeof(Oid));
		typmod = (int32 *) palloc(attr_count * sizeof(int32));
		for (i = 0; i < attr_count; i++)
		{
			in_func_oid = (Oid) GetInputFunction(attr[i]->atttypid);
			fmgr_info(in_func_oid, &in_functions[i]);
			elements[i] = GetTypeElement(attr[i]->atttypid);
			typmod[i] = attr[i]->atttypmod;
		}
	}
	else
	{
		in_functions = NULL;
		elements = NULL;
		typmod = NULL;
		CopyGetData(&ntuples, sizeof(int32), fp);
		if (ntuples != 0)
			reading_to_eof = false;
	}

	values = (Datum *) palloc(attr_count * sizeof(Datum));
	nulls = (char *) palloc(attr_count * sizeof(char));
	byval = (bool *) palloc(attr_count * sizeof(bool));

	for (i = 0; i < attr_count; i++)
	{
		nulls[i] = ' ';
		byval[i] = IsTypeByVal(attr[i]->atttypid);
	}

	lineno = 0;
	fe_eof = false;

	while (!done)
	{
		if (QueryCancel)
		{
			lineno = 0;
			CancelQuery();
		}

		lineno++;

		if (!binary)
		{
			int			newline = 0;

			if (oids)
			{
				string = CopyReadAttribute(fp, &isnull, delim, &newline, null_print);
				if (string == NULL)
					done = 1;
				else
				{
					loaded_oid = DatumGetObjectId(DirectFunctionCall1(oidin,
												  CStringGetDatum(string)));
					if (loaded_oid == InvalidOid)
						elog(ERROR, "COPY TEXT: Invalid Oid");
				}
			}
			for (i = 0; i < attr_count && !done; i++)
			{
				string = CopyReadAttribute(fp, &isnull, delim, &newline, null_print);
				if (isnull)
				{
					values[i] = PointerGetDatum(NULL);
					nulls[i] = 'n';
				}
				else if (string == NULL)
					done = 1;
				else
				{
					values[i] = FunctionCall3(&in_functions[i],
											  CStringGetDatum(string),
											  ObjectIdGetDatum(elements[i]),
											  Int32GetDatum(typmod[i]));
				}
			}
			if (!done)
				CopyReadNewline(fp, &newline);
		}
		else
		{						/* binary */
			CopyGetData(&len, sizeof(int32), fp);
			if (CopyGetEof(fp))
				done = 1;
			else
			{
				if (oids)
				{
					CopyGetData(&loaded_oid, sizeof(int32), fp);
					if (loaded_oid == InvalidOid)
						elog(ERROR, "COPY BINARY: Invalid Oid");
				}
				CopyGetData(&null_ct, sizeof(int32), fp);
				if (null_ct > 0)
				{
					for (i = 0; i < null_ct; i++)
					{
						CopyGetData(&null_id, sizeof(int32), fp);
						nulls[null_id] = 'n';
					}
				}

				string = (char *) palloc(len);
				CopyGetData(string, len, fp);

				ptr = string;

				for (i = 0; i < attr_count; i++)
				{
					if (byval[i] && nulls[i] != 'n')
					{

						switch (attr[i]->attlen)
						{
							case sizeof(char):
								values[i] = (Datum) *(unsigned char *) ptr;
								ptr += sizeof(char);
								break;
							case sizeof(short):
								ptr = (char *) SHORTALIGN(ptr);
								values[i] = (Datum) *(unsigned short *) ptr;
								ptr += sizeof(short);
								break;
							case sizeof(int32):
								ptr = (char *) INTALIGN(ptr);
								values[i] = (Datum) *(uint32 *) ptr;
								ptr += sizeof(int32);
								break;
							default:
								elog(ERROR, "COPY BINARY: impossible size");
								break;
						}
					}
					else if (nulls[i] != 'n')
					{
						ptr = (char *) att_align(ptr, attr[i]->attlen, attr[i]->attalign);
						values[i] = (Datum) ptr;
						ptr = att_addlength(ptr, attr[i]->attlen, ptr);
					}
				}
			}
		}
		if (done)
			continue;

		tuple = heap_formtuple(tupDesc, values, nulls);
		if (oids)
			tuple->t_data->t_oid = loaded_oid;

		skip_tuple = false;

		/* BEFORE ROW INSERT Triggers */
		if (rel->trigdesc &&
			rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
		{
			HeapTuple	newtuple;

			newtuple = ExecBRInsertTriggers(rel, tuple);

			if (newtuple == NULL)		/* "do nothing" */
				skip_tuple = true;
			else if (newtuple != tuple) /* modified by Trigger(s) */
			{
				heap_freetuple(tuple);
				tuple = newtuple;
			}
		}

		if (!skip_tuple)
		{
			/* ----------------
			 * Check the constraints of the tuple
			 * ----------------
			 */
			ExecStoreTuple(tuple, slot, InvalidBuffer, false);

			if (rel->rd_att->constr)
				ExecConstraints("CopyFrom", resultRelInfo, slot, estate);

			/* ----------------
			 * OK, store the tuple and create index entries for it
			 * ----------------
			 */
			heap_insert(rel, tuple);

			if (resultRelInfo->ri_NumIndices > 0)
				ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);

			/* AFTER ROW INSERT Triggers */
			if (rel->trigdesc &&
				rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
				ExecARInsertTriggers(rel, tuple);
		}

		if (binary)
			pfree(string);

		for (i = 0; i < attr_count; i++)
		{
			if (!byval[i] && nulls[i] != 'n')
			{
				if (!binary)
					pfree((void *) values[i]);
			}
			/* reset nulls[] array for next time */
			nulls[i] = ' ';
		}

		heap_freetuple(tuple);
		tuples_read++;

		if (!reading_to_eof && ntuples == tuples_read)
			done = true;
	}

	/*
	 * Done, clean up
	 */
	lineno = 0;

	pfree(values);
	pfree(nulls);
	pfree(byval);

	if (!binary)
	{
		pfree(in_functions);
		pfree(elements);
		pfree(typmod);
	}

	ExecDropTupleTable(tupleTable, true);

	ExecCloseIndices(resultRelInfo);
}


static Oid
GetInputFunction(Oid type)
{
	HeapTuple	typeTuple;
	Oid			result;

	typeTuple = SearchSysCache(TYPEOID,
							   ObjectIdGetDatum(type),
							   0, 0, 0);
	if (!HeapTupleIsValid(typeTuple))
		elog(ERROR, "GetInputFunction: Cache lookup of type %u failed", type);
	result = ((Form_pg_type) GETSTRUCT(typeTuple))->typinput;
	ReleaseSysCache(typeTuple);
	return result;
}

static Oid
GetTypeElement(Oid type)
{
	HeapTuple	typeTuple;
	Oid			result;

	typeTuple = SearchSysCache(TYPEOID,
							   ObjectIdGetDatum(type),
							   0, 0, 0);
	if (!HeapTupleIsValid(typeTuple))
		elog(ERROR, "GetTypeElement: Cache lookup of type %u failed", type);
	result = ((Form_pg_type) GETSTRUCT(typeTuple))->typelem;
	ReleaseSysCache(typeTuple);
	return result;
}

static bool
IsTypeByVal(Oid type)
{
	HeapTuple	typeTuple;
	bool		result;

	typeTuple = SearchSysCache(TYPEOID,
							   ObjectIdGetDatum(type),
							   0, 0, 0);
	if (!HeapTupleIsValid(typeTuple))
		elog(ERROR, "IsTypeByVal: Cache lookup of type %u failed", type);
	result = ((Form_pg_type) GETSTRUCT(typeTuple))->typbyval;
	ReleaseSysCache(typeTuple);
	return result;
}


/*
 * Reads input from fp until an end of line is seen.
 */

static void
CopyReadNewline(FILE *fp, int *newline)
{
	if (!*newline)
	{
		elog(NOTICE, "CopyReadNewline: extra fields ignored");
		while (!CopyGetEof(fp) && (CopyGetChar(fp) != '\n'));
	}
	*newline = 0;
}

/*
 * Read the value of a single attribute.
 *
 * Result is either a string, or NULL (if EOF or a null attribute).
 * Note that the caller should not pfree the string!
 *
 * *isnull is set true if a null attribute, else false.
 * delim is the string of acceptable delimiter characters(s).
 * *newline remembers whether we've seen a newline ending this tuple.
 * null_print says how NULL values are represented
 */

static char *
CopyReadAttribute(FILE *fp, bool *isnull, char *delim, int *newline, char *null_print)
{
	int			c;

#ifdef MULTIBYTE
	int			mblen;
	unsigned char s[2];
	char	   *cvt;
	int			j;

	s[1] = 0;
#endif

	/* reset attribute_buf to empty */
	attribute_buf.len = 0;
	attribute_buf.data[0] = '\0';

	/* if last delimiter was a newline return a NULL attribute */
	if (*newline)
	{
		*isnull = (bool) true;
		return NULL;
	}

	*isnull = (bool) false;		/* set default */

	for (;;)
	{
		c = CopyGetChar(fp);
		if (c == EOF)
			goto endOfFile;
		if (c == '\n')
		{
			*newline = 1;
			break;
		}
		if (strchr(delim, c))
			break;
		if (c == '\\')
		{
			c = CopyGetChar(fp);
			if (c == EOF)
				goto endOfFile;
			switch (c)
			{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
					{
						int			val;

						val = VALUE(c);
						c = CopyPeekChar(fp);
						if (ISOCTAL(c))
						{
							val = (val << 3) + VALUE(c);
							CopyDonePeek(fp, c, 1);		/* Pick up the
														 * character! */
							c = CopyPeekChar(fp);
							if (ISOCTAL(c))
							{
								CopyDonePeek(fp, c, 1); /* pick up! */
								val = (val << 3) + VALUE(c);
							}
							else
							{
								if (c == EOF)
									goto endOfFile;
								CopyDonePeek(fp, c, 0); /* Return to stream! */
							}
						}
						else
						{
							if (c == EOF)
								goto endOfFile;
							CopyDonePeek(fp, c, 0);		/* Return to stream! */
						}
						c = val & 0377;
					}
					break;

					/*
					 * This is a special hack to parse `\N' as
					 * <backslash-N> rather then just 'N' to provide
					 * compatibility with the default NULL output. -- pe
					 */
				case 'N':
					appendStringInfoCharMacro(&attribute_buf, '\\');
					c = 'N';
					break;
				case 'b':
					c = '\b';
					break;
				case 'f':
					c = '\f';
					break;
				case 'n':
					c = '\n';
					break;
				case 'r':
					c = '\r';
					break;
				case 't':
					c = '\t';
					break;
				case 'v':
					c = '\v';
					break;
				case '.':
					c = CopyGetChar(fp);
					if (c != '\n')
						elog(ERROR, "CopyReadAttribute: end of record marker corrupted");
					goto endOfFile;
			}
		}
		appendStringInfoCharMacro(&attribute_buf, c);
#ifdef MULTIBYTE
		/* get additional bytes of the char, if any */
		s[0] = c;
		mblen = pg_encoding_mblen(encoding, s);
		for (j = 1; j < mblen; j++)
		{
			c = CopyGetChar(fp);
			if (c == EOF)
				goto endOfFile;
			appendStringInfoCharMacro(&attribute_buf, c);
		}
#endif
	}

#ifdef MULTIBYTE
	cvt = (char *) pg_client_to_server((unsigned char *) attribute_buf.data,
									   attribute_buf.len);
	if (cvt != attribute_buf.data)
	{
		/* transfer converted data back to attribute_buf */
		attribute_buf.len = 0;
		attribute_buf.data[0] = '\0';
		appendBinaryStringInfo(&attribute_buf, cvt, strlen(cvt));
		pfree(cvt);
	}
#endif

	if (strcmp(attribute_buf.data, null_print) == 0)
		*isnull = true;

	return attribute_buf.data;

endOfFile:
	return NULL;
}

static void
CopyAttributeOut(FILE *fp, char *server_string, char *delim)
{
	char	   *string;
	char		c;

#ifdef MULTIBYTE
	char	   *string_start;
	int			mblen;
	int			i;

#endif

#ifdef MULTIBYTE
	string = (char *) pg_server_to_client((unsigned char *) server_string,
										  strlen(server_string));
	string_start = string;
#else
	string = server_string;
#endif

#ifdef MULTIBYTE
	for (; (mblen = pg_encoding_mblen(encoding, string)) &&
		 ((c = *string) != '\0'); string += mblen)
#else
	for (; (c = *string) != '\0'; string++)
#endif
	{
		if (c == delim[0] || c == '\n' || c == '\\')
			CopySendChar('\\', fp);
#ifdef MULTIBYTE
		for (i = 0; i < mblen; i++)
			CopySendChar(*(string + i), fp);
#else
		CopySendChar(c, fp);
#endif
	}

#ifdef MULTIBYTE
	if (string_start != server_string)
		pfree(string_start);	/* pfree pg_server_to_client result */
#endif
}

/*
 * Returns the number of tuples in a relation.	Unfortunately, currently
 * must do a scan of the entire relation to determine this.
 *
 * relation is expected to be an open relation descriptor.
 */
static int
CountTuples(Relation relation)
{
	HeapScanDesc scandesc;
	HeapTuple	tuple;

	int			i;

	scandesc = heap_beginscan(relation, 0, QuerySnapshot, 0, NULL);

	i = 0;
	while (HeapTupleIsValid(tuple = heap_getnext(scandesc, 0)))
		i++;
	heap_endscan(scandesc);
	return i;
}
