/*-------------------------------------------------------------------------
 *
 * common.c
 *	  common routines between pg_dump and pg4_dump
 *
 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/bin/pg_dump/common.c,v 1.41 2000/04/12 17:16:14 momjian Exp $
 *
 * Modifications - 6/12/96 - dave@bensoft.com - version 1.13.dhb.2
 *
 *	 - Fixed dumpTable output to output lengths for char and varchar types!
 *	 - Added single. quote to twin single quote expansion for 'insert' string
 *	   mode.
 *
 *-------------------------------------------------------------------------
 */


#include <ctype.h>

#include "postgres.h"

#include "libpq-fe.h"
#ifndef HAVE_STRDUP
#include "strdup.h"
#endif

#include "pg_dump.h"

static char **findParentsByOid(TableInfo *tbinfo, int numTables,
				 InhInfo *inhinfo, int numInherits,
				 const char *oid,
				 int *numParents);
static int	findTableByOid(TableInfo *tbinfo, int numTables, const char *oid);
static void flagInhAttrs(TableInfo *tbinfo, int numTables,
			 InhInfo *inhinfo, int numInherits);
static int	strInArray(const char *pattern, char **arr, int arr_size);

/*
 * findTypeByOid
 *	  given an oid of a type, return its typename
 *
 * if oid is "0", return "opaque" -- this is a special case
 *
 * NOTE:  should hash this, but just do linear search for now
 */

char *
findTypeByOid(TypeInfo *tinfo, int numTypes, const char *oid)
{
	int			i;

	if (strcmp(oid, "0") == 0)
		return g_opaque_type;

	for (i = 0; i < numTypes; i++)
	{
		if (strcmp(tinfo[i].oid, oid) == 0)
			return tinfo[i].typname;
	}

	/* should never get here */
	fprintf(stderr, "failed sanity check, type with oid %s was not found\n",
			oid);
	exit(2);
}

/*
 * findOprByOid
 *	  given the oid of an operator, return the name of the operator
 *
 *
 * NOTE:  should hash this, but just do linear search for now
 *
 */
char *
findOprByOid(OprInfo *oprinfo, int numOprs, const char *oid)
{
	int			i;

	for (i = 0; i < numOprs; i++)
	{
		if (strcmp(oprinfo[i].oid, oid) == 0)
			return oprinfo[i].oprname;
	}

	/* should never get here */
	fprintf(stderr, "failed sanity check, opr with oid %s was not found\n",
			oid);
	exit(2);
}


/*
 * findParentsByOid
 *	  given the oid of a class, return the names of its parent classes
 * and assign the number of parents to the last argument.
 *
 *
 * returns NULL if none
 */

static char **
findParentsByOid(TableInfo *tblinfo, int numTables,
				 InhInfo *inhinfo, int numInherits, const char *oid,
				 int *numParentsPtr)
{
	int			i,
				j;
	int			parentInd,
				selfInd;
	char	  **result;
	int			numParents;

	numParents = 0;
	for (i = 0; i < numInherits; i++)
	{
		if (strcmp(inhinfo[i].inhrelid, oid) == 0)
			numParents++;
	}

	*numParentsPtr = numParents;

	if (numParents > 0)
	{
		result = (char **) malloc(sizeof(char *) * numParents);
		j = 0;
		for (i = 0; i < numInherits; i++)
		{
			if (strcmp(inhinfo[i].inhrelid, oid) == 0)
			{
				parentInd = findTableByOid(tblinfo, numTables,
										   inhinfo[i].inhparent);
				if (parentInd < 0)
				{
					selfInd = findTableByOid(tblinfo, numTables, oid);
					fprintf(stderr,
							"failed sanity check, parent oid %s of table %s (oid %s) was not found\n",
							inhinfo[i].inhparent,
						  (selfInd >= 0) ? tblinfo[selfInd].relname : "",
							oid);
					exit(2);
				}
				result[j++] = tblinfo[parentInd].relname;
			}
		}
		return result;
	}
	else
		return NULL;
}

/*
 * parseNumericArray
 *	  parse a string of numbers delimited by spaces into a character array
 */

void
parseNumericArray(const char *str, char **array, int arraysize)
{
	int			j,
				argNum;
	char		temp[100];
	char		s;

	argNum = 0;
	j = 0;
	for (;;)
	{
		s = *str++;
		if (s == ' ' || s == '\0')
		{
			if (j > 0)
			{
				if (argNum >= arraysize)
				{
					fprintf(stderr, "parseNumericArray: too many numbers\n");
					exit(2);
				}
				temp[j] = '\0';
				array[argNum++] = strdup(temp);
				j = 0;
			}
			if (s == '\0')
				break;
		}
		else
		{
			if (!isdigit(s) || j >= sizeof(temp) - 1)
			{
				fprintf(stderr, "parseNumericArray: bogus number\n");
				exit(2);
			}
			temp[j++] = s;
		}
	}
	while (argNum < arraysize)
		array[argNum++] = strdup("0");
}


/*
 * strInArray:
 *	  takes in a string and a string array and the number of elements in the
 * string array.
 *	  returns the index if the string is somewhere in the array, -1 otherwise
 *
 */

static int
strInArray(const char *pattern, char **arr, int arr_size)
{
	int			i;

	for (i = 0; i < arr_size; i++)
	{
		if (strcmp(pattern, arr[i]) == 0)
			return i;
	}
	return -1;
}

/*
 * dumpSchema:
 *	  we have a valid connection, we are now going to dump the schema
 * into the file
 *
 */

TableInfo  *
dumpSchema(FILE *fout,
		   int *numTablesPtr,
		   const char *tablename,
		   const bool aclsSkip)
{
	int			numTypes;
	int			numFuncs;
	int			numTables;
	int			numInherits;
	int			numAggregates;
	int			numOperators;
	TypeInfo   *tinfo = NULL;
	FuncInfo   *finfo = NULL;
	AggInfo    *agginfo = NULL;
	TableInfo  *tblinfo = NULL;
	InhInfo    *inhinfo = NULL;
	OprInfo    *oprinfo = NULL;

	if (g_verbose)
		fprintf(stderr, "%s reading user-defined types %s\n",
				g_comment_start, g_comment_end);
	tinfo = getTypes(&numTypes);

	if (g_verbose)
		fprintf(stderr, "%s reading user-defined functions %s\n",
				g_comment_start, g_comment_end);
	finfo = getFuncs(&numFuncs);

	if (g_verbose)
		fprintf(stderr, "%s reading user-defined aggregates %s\n",
				g_comment_start, g_comment_end);
	agginfo = getAggregates(&numAggregates);

	if (g_verbose)
		fprintf(stderr, "%s reading user-defined operators %s\n",
				g_comment_start, g_comment_end);
	oprinfo = getOperators(&numOperators);

	if (g_verbose)
		fprintf(stderr, "%s reading user-defined tables %s\n",
				g_comment_start, g_comment_end);
	tblinfo = getTables(&numTables, finfo, numFuncs);

	if (g_verbose)
		fprintf(stderr, "%s reading table inheritance information %s\n",
				g_comment_start, g_comment_end);
	inhinfo = getInherits(&numInherits);

	if (g_verbose)
		fprintf(stderr, "%s finding the attribute names and types for each table %s\n",
				g_comment_start, g_comment_end);
	getTableAttrs(tblinfo, numTables);

	if (g_verbose)
		fprintf(stderr, "%s flagging inherited attributes in subtables %s\n",
				g_comment_start, g_comment_end);
	flagInhAttrs(tblinfo, numTables, inhinfo, numInherits);

	if (!tablename && fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out database comment %s\n",
					g_comment_start, g_comment_end);
		dumpDBComment(fout);
	}

	if (!tablename && fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out user-defined types %s\n",
					g_comment_start, g_comment_end);
		dumpTypes(fout, finfo, numFuncs, tinfo, numTypes);
	}

	if (fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out tables %s\n",
					g_comment_start, g_comment_end);
		dumpTables(fout, tblinfo, numTables, inhinfo, numInherits,
				   tinfo, numTypes, tablename, aclsSkip);
	}

	if (!tablename && fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out user-defined procedural languages %s\n",
					g_comment_start, g_comment_end);
		dumpProcLangs(fout, finfo, numFuncs, tinfo, numTypes);
	}

	if (!tablename && fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out user-defined functions %s\n",
					g_comment_start, g_comment_end);
		dumpFuncs(fout, finfo, numFuncs, tinfo, numTypes);
	}

	if (!tablename && fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out user-defined aggregates %s\n",
					g_comment_start, g_comment_end);
		dumpAggs(fout, agginfo, numAggregates, tinfo, numTypes);
	}

	if (!tablename && fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out user-defined operators %s\n",
					g_comment_start, g_comment_end);
		dumpOprs(fout, oprinfo, numOperators, tinfo, numTypes);
	}

	*numTablesPtr = numTables;
	clearAggInfo(agginfo, numAggregates);
	clearOprInfo(oprinfo, numOperators);
	clearTypeInfo(tinfo, numTypes);
	clearFuncInfo(finfo, numFuncs);
	clearInhInfo(inhinfo, numInherits);
	return tblinfo;
}

/*
 * dumpSchemaIdx:
 *	  dump indexes at the end for performance
 *
 */

extern void
dumpSchemaIdx(FILE *fout, const char *tablename,
			  TableInfo *tblinfo, int numTables)
{
	int			numIndices;
	IndInfo    *indinfo;

	if (g_verbose)
		fprintf(stderr, "%s reading indices information %s\n",
				g_comment_start, g_comment_end);
	indinfo = getIndices(&numIndices);

	if (fout)
	{
		if (g_verbose)
			fprintf(stderr, "%s dumping out indices %s\n",
					g_comment_start, g_comment_end);
		dumpIndices(fout, indinfo, numIndices, tblinfo, numTables, tablename);
	}
	clearIndInfo(indinfo, numIndices);
}

/* flagInhAttrs -
 *	 for each table in tblinfo, flag its inherited attributes
 * so when we dump the table out, we don't dump out the inherited attributes
 *
 * initializes the parentRels field of each table
 *
 * modifies tblinfo
 *
 */
static void
flagInhAttrs(TableInfo *tblinfo, int numTables,
			 InhInfo *inhinfo, int numInherits)
{
	int			i,
				j,
				k;
	int			parentInd;

	/*
	 * we go backwards because the tables in tblinfo are in OID order,
	 * meaning the subtables are after the parent tables we flag inherited
	 * attributes from child tables first
	 */
	for (i = numTables - 1; i >= 0; i--)
	{
		tblinfo[i].parentRels = findParentsByOid(tblinfo, numTables,
												 inhinfo, numInherits,
												 tblinfo[i].oid,
												 &tblinfo[i].numParents);
		for (k = 0; k < tblinfo[i].numParents; k++)
		{
			parentInd = findTableByName(tblinfo, numTables,
										tblinfo[i].parentRels[k]);
			if (parentInd < 0)
			{
				/* shouldn't happen unless findParentsByOid is broken */
				fprintf(stderr, "failed sanity check, table %s not found by flagInhAttrs\n",
						tblinfo[i].parentRels[k]);
				exit(2);
			}
			for (j = 0; j < tblinfo[i].numatts; j++)
			{
				if (strInArray(tblinfo[i].attnames[j],
							   tblinfo[parentInd].attnames,
							   tblinfo[parentInd].numatts) != -1)
					tblinfo[i].inhAttrs[j] = 1;
			}
		}
	}
}


/*
 * findTableByName
 *	  finds the index (in tblinfo) of the table with the given relname
 *	returns -1 if not found
 *
 * NOTE:  should hash this, but just do linear search for now
 */

int
findTableByName(TableInfo *tblinfo, int numTables, const char *relname)
{
	int			i;

	for (i = 0; i < numTables; i++)
	{
		if (strcmp(tblinfo[i].relname, relname) == 0)
			return i;
	}
	return -1;
}

/*
 * findTableByOid
 *	  finds the index (in tblinfo) of the table with the given oid
 *	returns -1 if not found
 *
 * NOTE:  should hash this, but just do linear search for now
 */

static int
findTableByOid(TableInfo *tblinfo, int numTables, const char *oid)
{
	int			i;

	for (i = 0; i < numTables; i++)
	{
		if (strcmp(tblinfo[i].oid, oid) == 0)
			return i;
	}
	return -1;
}


/*
 * findFuncByName
 *	  finds the index (in finfo) of the function with the given name
 *	returns -1 if not found
 *
 * NOTE:  should hash this, but just do linear search for now
 */

int
findFuncByName(FuncInfo *finfo, int numFuncs, const char *name)
{
	int			i;

	for (i = 0; i < numFuncs; i++)
	{
		if (strcmp(finfo[i].proname, name) == 0)
			return i;
	}
	return -1;
}

/*
 * fmtId
 *
 *	checks input string for non-lowercase characters
 *	returns pointer to input string or string surrounded by double quotes
 *
 *	Note that the returned string should be used immediately since it
 *	uses a static buffer to hold the string. Non-reentrant but faster?
 */
const char *
fmtId(const char *rawid, bool force_quotes)
{
	static PQExpBuffer id_return = NULL;
	const char *cp;

	if (!force_quotes)
	{
		if (!islower(*rawid))
			force_quotes = true;
		else
			for (cp = rawid; *cp; cp++)
			{
				if (!(islower(*cp) || isdigit(*cp) || (*cp == '_')))
				{
					force_quotes = true;
					break;
				}
			}
	}

	if (!force_quotes)
		return rawid;			/* no quoting needed */

	if (id_return)
		resetPQExpBuffer(id_return);
	else
		id_return = createPQExpBuffer();

	appendPQExpBufferChar(id_return, '\"');
	for (cp = rawid; *cp; cp++)
	{
		if (*cp == '\"')
			appendPQExpBufferChar(id_return, '\\');
		appendPQExpBufferChar(id_return, *cp);
	}
	appendPQExpBufferChar(id_return, '\"');

	return id_return->data;
}	/* fmtId() */
