/*--------
 * Module:			parse.c
 *
 * Description:		This module contains routines related to parsing SQL
 *					statements.  This can be useful for two reasons:
 *
 *					1. So the query does not actually have to be executed
 *					to return data about it
 *
 *					2. To be able to return information about precision,
 *					nullability, aliases, etc. in the functions
 *					SQLDescribeCol and SQLColAttributes.  Currently,
 *					Postgres doesn't return any information about
 *					these things in a query.
 *
 * Classes:			none
 *
 * API functions:	none
 *
 * Comments:		See "readme.txt" for copyright and license information.
 *--------
 */
/* Multibyte support	Eiji Tokuya 2001-03-15 */

#include "psqlodbc.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "statement.h"
#include "connection.h"
#include "qresult.h"
#include "pgtypes.h"
#include "pgapifunc.h"
#include "catfunc.h"

#include "multibyte.h"
#include "misc.h"

#define FLD_INCR	32
#define TAB_INCR	8
#define COLI_INCR	16
#define COLI_RECYCLE	128

static const char *getNextToken(int ccsc, char escape_in_literal, const char *s, char *token, int smax, char *delim, char *quote, char *dquote, char *numeric);
static	void	getColInfo(COL_INFO *col_info, FIELD_INFO *fi, int k);
static	char	searchColInfo(COL_INFO *col_info, FIELD_INFO *fi);
static	BOOL	getColumnsInfo(ConnectionClass *, TABLE_INFO *, OID, StatementClass *);

Int4 FI_precision(const FIELD_INFO *fi)
{
	OID	ftype;

	if (!fi)	return -1;
	ftype = FI_type(fi);
	switch (ftype)
	{
		case PG_TYPE_NUMERIC:
			return fi->column_size;
		case PG_TYPE_DATETIME:
		case PG_TYPE_TIMESTAMP_NO_TMZONE:
			return fi->decimal_digits;
	}
	return 0;
}
Int4 FI_scale(const FIELD_INFO *fi)
{
	OID	ftype;

	if (!fi)	return -1;
	ftype = FI_type(fi);
	switch (ftype)
	{
		case PG_TYPE_NUMERIC:
			return fi->decimal_digits;
	}
	return 0;
}

static const char *
getNextToken(
	int ccsc, /* client encoding */
	char escape_ch,
	const char *s, char *token, int smax, char *delim, char *quote, char *dquote, char *numeric)
{
	size_t		out = 0;
	size_t		taglen;
	char		in_quote, in_dollar_quote, in_escape;
	const	UCHAR	*tag, *tagend;
	encoded_str	encstr;
	char		escape_in_literal;
	const UCHAR *tstr = (const UCHAR *) s;
	UCHAR	tchar, qc;

	if (smax <= 1)
		return NULL;

	smax--;

	/* skip leading delimiters */
	while (isspace(*tstr) || *tstr == ',')
	{
		/* MYLOG(0, "skipping '%c'\n", *tstr); */
		tstr++;
	}

	if (*tstr == '\0')
	{
		token[0] = '\0';
		return NULL;
	}

	if (quote)
		*quote = FALSE;
	if (dquote)
		*dquote = FALSE;
	if (numeric)
		*numeric = FALSE;

	encoded_str_constr(&encstr, ccsc, (const char *) tstr);
	/* get the next token */
	for (tchar = encoded_nextchar(&encstr); tchar && out < smax; tstr++, tchar = encoded_nextchar(&encstr))
	{
		if (MBCS_NON_ASCII(encstr))
		{
			token[out++] = tchar;
			continue;
		}
		if (isspace(tchar) || tchar == ',')
			break;
		/* Handle quoted stuff */
		in_quote = in_dollar_quote = FALSE;
		taglen = 0;
		tag = NULL;
		escape_in_literal = '\0';
		if (out == 0)
		{
			qc = tchar;
			if (qc == DOLLAR_QUOTE)
			{
				in_quote = in_dollar_quote = TRUE;
				tag = tstr;
				taglen = 1;
				if (tagend = (const UCHAR *) strchr((const char *) tstr + 1, DOLLAR_QUOTE), NULL != tagend)
					taglen = tagend - tstr + 1;
				tstr += (taglen - 1);
				encoded_position_shift(&encstr, taglen - 1);
				if (quote)
					*quote = TRUE;
			}
			else if (qc == LITERAL_QUOTE)
			{
				in_quote = TRUE;
				if (quote)
					*quote = TRUE;
				escape_in_literal = escape_ch;
				if (!escape_in_literal)
				{
					if (LITERAL_EXT == tstr[-1])
						escape_in_literal = ESCAPE_IN_LITERAL;
				}
			}
			else if (qc == IDENTIFIER_QUOTE)
			{
				in_quote = TRUE;
				if (dquote)
					*dquote = TRUE;
			}
		} /* out == 0 */
		if (in_quote) /* dquote, dollar_quote */
		{
			in_escape = FALSE;
			for (tstr++, tchar = encoded_nextchar(&encstr); tchar != '\0' && out != smax; tstr++, tchar = encoded_nextchar(&encstr))
			{
				if (MBCS_NON_ASCII(encstr))
				{
					token[out++] = tchar;
					continue;
				}
				if (in_escape)
					in_escape = FALSE;
				else if (tchar == qc)
				{
					if (!in_dollar_quote)
					{
						/*
						 * Peek at the next byte to see if this is a '' or
						 * "", i.e a quote character that has been escaped
						 * by doubling it.
						 */
						if (tstr[1] == qc)
						{
							tstr++;
							tchar = encoded_nextchar(&encstr);
						}
						else
							break;
					}
					else if (strncmp((const char *) tstr, (const char *) tag, taglen) == 0)
					{
						tstr += (taglen - 1);
						tchar = encoded_position_shift(&encstr, taglen - 1);
						break;
					}
					token[out++] = tchar;
				}
				else if (LITERAL_QUOTE == qc && tchar == escape_in_literal)
				{
					in_escape = TRUE;
				}
				else
				{
					token[out++] = tchar;
				}
			} /* for */
			if (tchar == qc)
				tstr++;
			break;
		} /* in_quote */

		/* Check for numeric literals */
		if (out == 0 && isdigit(tchar))
		{
			if (numeric)
				*numeric = TRUE;
			token[out++] = tchar;
			tstr++;
			while ((isalnum(*tstr) || *tstr == '.') && out < smax)
			{
				token[out++] = *tstr;
				tstr++;
			}

			break;
		}

		if (ispunct(tchar) && tchar != '_')
		{
			MYLOG(0, "got ispunct: s[] = '%c'\n", tchar);

			if (out == 0)
			{
				token[out++] = tchar;
				tstr++;
			}
			break;
		}

		if (out < smax)
			token[out++] = tchar;
	} /* for */

	/* MYLOG(0, "done -- s[] = '%c'\n", *tstr); */

	token[out] = '\0';

	/* find the delimiter  */
	while (isspace(*tstr))
		tstr++;

	/* return the most priority delimiter */
	if (*tstr == ',')
	{
		if (delim)
			*delim = *tstr;
	}
	else if (*tstr == '\0')
	{
		if (delim)
			*delim = '\0';
	}
	else
	{
		if (delim)
			*delim = ' ';
	}

	/* skip trailing blanks  */
	while (isspace(*tstr))
		tstr++;

	return (const char *) tstr;
}

static void
getColInfo(COL_INFO *col_info, FIELD_INFO *fi, int k)
{
	char	   *str;

MYLOG(DETAIL_LOG_LEVEL, "entering non-manual result\n");
	fi->dquote = TRUE;
	STR_TO_NAME(fi->column_name, QR_get_value_backend_text(col_info->result, k, COLUMNS_COLUMN_NAME));

	fi->columntype = (OID) QR_get_value_backend_int(col_info->result, k, COLUMNS_FIELD_TYPE, NULL);
	fi->column_size = QR_get_value_backend_int(col_info->result, k, COLUMNS_PRECISION, NULL);
	fi->length = QR_get_value_backend_int(col_info->result, k, COLUMNS_LENGTH, NULL);
	if (str = QR_get_value_backend_text(col_info->result, k, COLUMNS_SCALE), str)
		fi->decimal_digits = pg_atoi(str);
	else
		fi->decimal_digits = -1;
	fi->nullable = QR_get_value_backend_int(col_info->result, k, COLUMNS_NULLABLE, NULL);
	fi->display_size = QR_get_value_backend_int(col_info->result, k, COLUMNS_DISPLAY_SIZE, NULL);
	fi->auto_increment = QR_get_value_backend_int(col_info->result, k, COLUMNS_AUTO_INCREMENT, NULL);
}


static char
searchColInfo(COL_INFO *col_info, FIELD_INFO *fi)
{
	int			k,
				cmp, attnum, atttypmod;
	OID			basetype;
	const char	   *col;

MYLOG(DETAIL_LOG_LEVEL, "entering num_cols=" FORMAT_ULEN " col=%s\n", QR_get_num_cached_tuples(col_info->result), PRINT_NAME(fi->column_name));
	if (fi->attnum < 0)
		return FALSE;
	for (k = 0; k < QR_get_num_cached_tuples(col_info->result); k++)
	{
		if (fi->attnum > 0)
		{
			attnum = QR_get_value_backend_int(col_info->result, k, COLUMNS_PHYSICAL_NUMBER, NULL);
			if (basetype = (OID) strtoul(QR_get_value_backend_text(col_info->result, k, COLUMNS_BASE_TYPEID), NULL, 10), 0 == basetype)
				basetype = (OID) strtoul(QR_get_value_backend_text(col_info->result, k, COLUMNS_FIELD_TYPE), NULL, 10);
			atttypmod = QR_get_value_backend_int(col_info->result, k, COLUMNS_ATTTYPMOD, NULL);
MYLOG(DETAIL_LOG_LEVEL, "%d attnum=%d\n", k, attnum);
			if (attnum == fi->attnum &&
			    basetype == fi->basetype &&
			    atttypmod == fi->typmod)
			{
				getColInfo(col_info, fi, k);
				MYLOG(0, "PARSE: searchColInfo by attnum=%d\n", attnum);
				return TRUE;
			}
		}
		else if (NAME_IS_VALID(fi->column_name))
		{
			col = QR_get_value_backend_text(col_info->result, k, COLUMNS_COLUMN_NAME);
MYLOG(DETAIL_LOG_LEVEL, "%d col=%s\n", k, col);
			if (fi->dquote)
				cmp = strcmp(col, GET_NAME(fi->column_name));
			else
				cmp = stricmp(col, GET_NAME(fi->column_name));
			if (!cmp)
			{
				if (!fi->dquote)
					STR_TO_NAME(fi->column_name, col);
				getColInfo(col_info, fi, k);

				MYLOG(0, "PARSE: \n");
				return TRUE;
			}
		}
	}

	return FALSE;
}

/*
 *	lower the unquoted name
 */
static
void lower_the_name(char *name, ConnectionClass *conn, BOOL dquote)
{
	if (!dquote)
	{
		char		*ptr;
		encoded_str	encstr;
		make_encoded_str(&encstr, conn, name);

		/* lower case table name */
		for (ptr = name; *ptr; ptr++)
		{
			encoded_nextchar(&encstr);
			if (!MBCS_NON_ASCII(encstr))
				*ptr = tolower((UCHAR) *ptr);
		}
	}
}

/*
 *	Check relhasoids(before PG12), relhssubclass and get some relevant information.
 */
BOOL CheckPgClassInfo(StatementClass *stmt)
{
	const COL_INFO *coli;
	int	table_info;
	TABLE_INFO	*ti;
	BOOL	hasoids = FALSE, hassubclass =FALSE, keyFound = FALSE;

MYLOG(0, "Entering\n");
	if (0 != SC_checked_hasoids(stmt))
		return TRUE;
	if (!stmt->ti || !stmt->ti[0])
		return FALSE;
	ti = stmt->ti[0];
	MYLOG(DETAIL_LOG_LEVEL, "ti->col_info=%p\n", ti->col_info);
	if (TI_checked_hasoids(ti))
		;
	else if (coli = ti->col_info, NULL != coli)
	{
		table_info = coli->table_info;
		if (0 == (table_info & TBINFO_HASSUBCLASS))
		{
			TI_set_has_no_subclass(ti);
		}
		else
		{
			hassubclass = TRUE;
			TI_set_hassubclass(ti);
			STR_TO_NAME(ti->bestitem, TABLEOID_NAME);
			STRX_TO_NAME(ti->bestqual, "\"" TABLEOID_NAME "\" = %u");
		}
		if (!hassubclass)
		{
			if (0 == (table_info & TBINFO_HASOIDS))
			{
				TI_set_has_no_oids(ti);
			}
			else
			{
				hasoids = TRUE;
				TI_set_hasoids(ti);
				STR_TO_NAME(ti->bestitem, OID_NAME);
				STRX_TO_NAME(ti->bestqual, "\"" OID_NAME "\" = %u");
			}
		}
		ti->table_oid = coli->table_oid;
		if (!hasoids && !hassubclass)
		{
			QResultClass	*res = coli->result;
			int	num_tuples = res ? QR_get_num_cached_tuples(res) : -1;

			if (num_tuples > 0)
			{
				int	i;

				for (i = 0; i < num_tuples; i++)
				{
					if (QR_get_value_backend_int(res, i, COLUMNS_AUTO_INCREMENT, NULL) != 0&&
					    QR_get_value_backend_int(res, i, COLUMNS_FIELD_TYPE, NULL) == PG_TYPE_INT4)
					{
						char		query[512];

						STR_TO_NAME(ti->bestitem, QR_get_value_backend_text(res, i, COLUMNS_COLUMN_NAME));
						SPRINTF_FIXED(query, "\"%s\" = %%d", SAFE_NAME(ti->bestitem));
						STRX_TO_NAME(ti->bestqual, query);
						break;
					}
				}
			}
		}
		TI_set_hasoids_checked(ti);
	}
	else
		return FALSE;

	stmt->num_key_fields = PG_NUM_NORMAL_KEYS;
	if (TI_has_subclass(ti))
		keyFound = FALSE;
	else if (TI_has_oids(ti))
		keyFound = TRUE;
	else if (NAME_IS_NULL(ti->bestqual))
	{
		keyFound = TRUE;
		stmt->num_key_fields--;
	}
	else
		keyFound = TRUE;
	MYLOG(DETAIL_LOG_LEVEL, "subclass=%d oids=%d bestqual=%s keyFound=%d num_key_fields=%d\n", TI_has_subclass(ti), TI_has_oids(ti), PRINT_NAME(ti->bestqual), keyFound, stmt->num_key_fields);

	SC_set_checked_hasoids(stmt, keyFound);

	return TRUE;
}

static BOOL increaseNtab(StatementClass *stmt, const char *func)
{
	TABLE_INFO	**ti = stmt->ti, *wti;

	if (!(stmt->ntab % TAB_INCR))
	{
		SC_REALLOC_return_with_error(ti, TABLE_INFO *, (stmt->ntab + TAB_INCR) * sizeof(TABLE_INFO *), stmt, "PGAPI_AllocStmt failed in parse_statement for TABLE_INFO", FALSE);
		stmt->ti = ti;
	}
	wti = ti[stmt->ntab] = (TABLE_INFO *) malloc(sizeof(TABLE_INFO));
	if (wti == NULL)
	{
		SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "PGAPI_AllocStmt failed in parse_statement for TABLE_INFO(2).", func);
		return FALSE;
	}

	TI_Constructor(wti, SC_get_conn(stmt));
	stmt->ntab++;
	return TRUE;
}

static void setNumFields(IRDFields *irdflds, size_t numFields)
{
	FIELD_INFO	**fi = irdflds->fi;
	size_t		nfields = irdflds->nfields;

	if (numFields < nfields)
	{
		int	i;

		for (i = (int) numFields; i < (int) nfields; i++)
		{
			if (fi[i])
				fi[i]->flag = 0;
		}
	}
	irdflds->nfields = (UInt4) numFields;
}

void SC_initialize_cols_info(StatementClass *stmt, BOOL DCdestroy, BOOL parseReset)
{
	IRDFields	*irdflds = SC_get_IRDF(stmt);

	/* Free the parsed table information */
	if (stmt->ti)
	{
		TI_Destructor(stmt->ti, stmt->ntab);
		free(stmt->ti);
		stmt->ti = NULL;
	}
	stmt->ntab = 0;
	if (DCdestroy) /* Free the parsed field information */
		DC_Destructor((DescriptorClass *) SC_get_IRD(stmt));
	else
		setNumFields(irdflds, 0);
	if (parseReset)
	{
		stmt->parse_status = STMT_PARSE_NONE;
		SC_reset_updatable(stmt);
	}
}

static BOOL allocateFields(IRDFields *irdflds, size_t sizeRequested)
{
	FIELD_INFO	**fi = irdflds->fi;
	size_t		alloc_size, incr_size;

	if (sizeRequested <= irdflds->allocated)
		return TRUE;
	alloc_size = (0 != irdflds->allocated ? irdflds->allocated : FLD_INCR);
	for (; alloc_size < sizeRequested; alloc_size *= 2)
			;
	incr_size = sizeof(FIELD_INFO *) * (alloc_size - irdflds->allocated);

	fi = (FIELD_INFO **) realloc(fi, alloc_size * sizeof(FIELD_INFO *));
	if (!fi)
	{
		irdflds->fi = NULL;
		irdflds->allocated = irdflds->nfields = 0;
		return FALSE;
	}
	pg_memset(&fi[irdflds->allocated], 0, incr_size);
	irdflds->fi = fi;
	irdflds->allocated = (SQLSMALLINT) alloc_size;

	return TRUE;
}

/*
 *	This function may not be called but when it is called ...
 */
static void xxxxx(StatementClass *stmt, FIELD_INFO *fi, QResultClass *res, int i)
{
	STR_TO_NAME(fi->column_alias, QR_get_fieldname(res, i));
	fi->basetype = QR_get_field_type(res, i);
	if (0 == fi->columntype)
		fi->columntype = fi->basetype;
	if (fi->attnum < 0)
	{
		fi->nullable = FALSE;
		fi->updatable = FALSE;
	}
	else if (fi->attnum > 0)
	{
		int	unknowns_as = 0;
		int	type = pg_true_type(SC_get_conn(stmt), fi->columntype, fi->basetype);

		fi->nullable = TRUE;	/* probably ? */
		fi->column_size = pgtype_column_size(stmt, type, i, unknowns_as);
		fi->length = pgtype_buffer_length(stmt, type, i, unknowns_as);
		fi->decimal_digits = pgtype_decimal_digits(stmt, type, i);
		fi->display_size = pgtype_display_size(stmt, type, i, unknowns_as);
	}

	if (NAME_IS_NULL(fi->column_name))
	{
		switch (fi->attnum)
		{
			case CTID_ATTNUM:
				STR_TO_NAME(fi->column_name, "ctid");
				break;
			case OID_ATTNUM:
				STR_TO_NAME(fi->column_name, OID_NAME);
				break;
			case XMIN_ATTNUM:
				STR_TO_NAME(fi->column_name, XMIN_NAME);
				break;
		}
	}
}

/*
 *	SQLColAttribute tries to set the FIELD_INFO (using protocol 3).
 */
static BOOL
ColAttSet(StatementClass *stmt, TABLE_INFO *rti)
{
	CSTR		func = "ColAttSet";
	QResultClass	*res = SC_get_ExecdOrParsed(stmt);
	IRDFields	*irdflds = SC_get_IRDF(stmt);
	COL_INFO	*col_info = NULL;
	FIELD_INFO	**fi, *wfi;
	OID		reloid = 0;
	Int2		attid;
	int		i, num_fields;
	BOOL		fi_reuse, updatable, call_xxxxx;

MYLOG(0, "entering\n");

	if (reloid = rti->table_oid, 0 == reloid)
		return FALSE;
	if (0 != (rti->flags & TI_COLATTRIBUTE))
		return TRUE;
	col_info = rti->col_info;
	if (!QR_command_maybe_successful(res))
		return FALSE;
	if (num_fields = QR_NumPublicResultCols(res), num_fields <= 0)
		return FALSE;
	fi = irdflds->fi;
	if (num_fields > (int) irdflds->allocated)
	{
		if (!allocateFields(irdflds, num_fields))
			return FALSE;
		fi = irdflds->fi;
	}
	setNumFields(irdflds, num_fields);
	updatable = TI_is_updatable(rti);
MYLOG(0, "updatable=%d tab=%d fields=%d", updatable, stmt->ntab, num_fields);
	if (updatable)
	{
		if (1 > stmt->ntab)
			updatable = FALSE;
	}
MYPRINTF(0, "->%d\n", updatable);
	for (i = 0; i < num_fields; i++)
	{
		if (reloid == (OID) QR_get_relid(res, i))
		{
			if (wfi = fi[i], NULL == wfi)
			{
				wfi = (FIELD_INFO *) malloc(sizeof(FIELD_INFO));
				if (wfi == NULL)
				{
					SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "Couldn't allocate memory for field info.", func);
					return FALSE;
				}
				fi_reuse = FALSE;
				fi[i] = wfi;
			}
			else if (FI_is_applicable(wfi))
				continue;
			else
				fi_reuse = TRUE;
			FI_Constructor(wfi, fi_reuse);
			attid = (Int2) QR_get_attid(res, i);
			wfi->attnum = attid;
			wfi->basetype = QR_get_field_type(res, i);
			wfi->typmod = QR_get_atttypmod(res, i);
			call_xxxxx = TRUE;
			if (searchColInfo(col_info, wfi))
			{
				STR_TO_NAME(wfi->column_alias, QR_get_fieldname(res, i));
				wfi->basetype = QR_get_field_type(res, i);
				wfi->updatable = updatable;
				call_xxxxx = FALSE;
			}
			else
			{
				if (attid > 0)
				{
					if (getColumnsInfo(NULL, rti, reloid, stmt) &&
					    searchColInfo(col_info, wfi))
					{
						STR_TO_NAME(wfi->column_alias, QR_get_fieldname(res, i));
						wfi->basetype = QR_get_field_type(res, i);
						wfi->updatable = updatable;
						call_xxxxx= FALSE;
					}
				}
			}
			if (call_xxxxx)
				xxxxx(stmt, wfi, res, i);
			wfi->ti = rti;
			wfi->flag |= FIELD_COL_ATTRIBUTE;
		}
	}
	if (stmt->updatable < 0)
	{
		if (stmt->ntab > 1)
			updatable = FALSE;
		SC_set_updatable(stmt, updatable);
	}

	rti->flags |= TI_COLATTRIBUTE;
	return TRUE;
}

static BOOL
getCOLIfromTable(ConnectionClass *conn, pgNAME *schema_name, pgNAME table_name, COL_INFO **coli)
{
	int	colidx;
	BOOL	found = FALSE;

	*coli = NULL;
	if (NAME_IS_NULL(table_name))
		return TRUE;
	if (NAME_IS_NULL(*schema_name))
	{
		const char *curschema = CC_get_current_schema(conn);
		/*
		 * Though current_schema() doesn't have
		 * much sense in PostgreSQL, we first
		 * check the current_schema() when no
		 * explicit schema name is specified.
		 */
		if (curschema)
		{
			for (colidx = 0; colidx < conn->ntables; colidx++)
			{
				if (!NAMEICMP(conn->col_info[colidx]->table_name, table_name) &&
					!stricmp(SAFE_NAME(conn->col_info[colidx]->schema_name), curschema))
				{
					MYLOG(0, "FOUND col_info table='%s' current schema='%s'\n", PRINT_NAME(table_name), curschema);
					found = TRUE;
					STR_TO_NAME(*schema_name, curschema);
					break;
				}
			}
		}
		if (!found)
		{
			QResultClass	*res;
			char		token[256], relcnv[128];
			BOOL		tblFound = FALSE;

			/*
			 * We also have to check as follows.
			 */
			SPRINTF_FIXED(token,
					 "select nspname from pg_namespace n, pg_class c"
					 " where c.relnamespace=n.oid and c.oid='%s'::regclass",
					 identifierEscape((const SQLCHAR *) SAFE_NAME(table_name), SQL_NTS, conn, relcnv, sizeof(relcnv), TRUE));
			res = CC_send_query(conn, token, NULL, READ_ONLY_QUERY, NULL);
			if (QR_command_maybe_successful(res))
			{
				if (QR_get_num_total_tuples(res) == 1)
				{
					tblFound = TRUE;
					STR_TO_NAME(*schema_name, QR_get_value_backend_text(res, 0, 0));
				}
			}
			QR_Destructor(res);
			if (!tblFound)
				return FALSE;
		}
	}
	if (!found && NAME_IS_VALID(*schema_name))
	{
		for (colidx = 0; colidx < conn->ntables; colidx++)
		{
			if (!NAMEICMP(conn->col_info[colidx]->table_name, table_name) &&
			    !NAMEICMP(conn->col_info[colidx]->schema_name, *schema_name))
			{
				MYLOG(0, "FOUND col_info table='%s' schema='%s'\n", PRINT_NAME(table_name), PRINT_NAME(*schema_name));
				found = TRUE;
				break;
			}
		}
	}
	*coli = found ? conn->col_info[colidx] : NULL;
	return TRUE; /* success */
}

static BOOL
getColumnsInfo(ConnectionClass *conn, TABLE_INFO *wti, OID greloid, StatementClass *stmt)
{
	BOOL		found = FALSE;
	RETCODE		result;
	HSTMT		hcol_stmt = NULL;
	StatementClass	*col_stmt;
	QResultClass	*res;

	MYLOG(0, "entering Getting PG_Columns for table %u(%s)\n", greloid, PRINT_NAME(wti->table_name));

	if (NULL == conn)
		conn = SC_get_conn(stmt);
	result = PGAPI_AllocStmt(conn, &hcol_stmt, 0);
	if (!SQL_SUCCEEDED(result))
	{
		if (stmt)
			SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "PGAPI_AllocStmt failed in parse_statement for columns.", __FUNCTION__);
		goto cleanup;
	}

	col_stmt = (StatementClass *) hcol_stmt;

	if (greloid)
		result = PGAPI_Columns(hcol_stmt, NULL, 0,
				NULL, 0, NULL, 0, NULL, 0,
				PODBC_SEARCH_BY_IDS, greloid, 0);
	else
		result = PGAPI_Columns(hcol_stmt, NULL, 0,
							   (SQLCHAR *) SAFE_NAME(wti->schema_name), SQL_NTS,
							   (SQLCHAR *) SAFE_NAME(wti->table_name), SQL_NTS,
							   NULL, 0,
							   PODBC_NOT_SEARCH_PATTERN, 0, 0);

	MYLOG(0, "        Past PG_Columns\n");
	res = SC_get_ExecdOrParsed(col_stmt);
	if (SQL_SUCCEEDED(result)
		&& res != NULL && QR_get_num_cached_tuples(res) > 0)
	{
		BOOL		coli_exist = FALSE;
		COL_INFO	*coli = NULL, *ccoli = NULL, *tcoli;
		int		 k, tmp_refcnt = 0;
		time_t		acctime = 0;

		MYLOG(0, "      Success\n");
		if (greloid != 0)
		{
			/* We have reloid. Try to find appropriate coli object from connection COL_INFO cache. */
			for (k = 0; k < conn->ntables; k++)
			{
				tcoli = conn->col_info[k];
				if (tcoli->table_oid == greloid)
				{
					/* We found appropriate coli object, so we will use it. */
					coli = tcoli;
					coli_exist = TRUE;
					break;
				}
			}
		}
		if (!coli_exist)
		{
			/* Not found, try to find unused coli or oldest (if overflow) in connection COL_INFO cache. */
			for (k = 0; k < conn->ntables; k++)
			{
				tcoli = conn->col_info[k];
				if (1 < tcoli->refcnt)
					continue; /* This coli object is used somewhere else, skipping it. */
				if ((0 == tcoli->table_oid &&
				    NAME_IS_NULL(tcoli->table_name)) ||
				    strnicmp(SAFE_NAME(tcoli->schema_name), "pg_temp_", 8) == 0)
				{
					/* Found unused coli object, taking it. */
					coli = tcoli;
					coli_exist = TRUE;
					break;
				}
				if (NULL == ccoli || tcoli->acc_time < acctime)
				{
					/* Not yet found. Alongside, searching least recently used coli object. */
					ccoli = tcoli;
					acctime = tcoli->acc_time;
				}
			}
			if (!coli_exist && NULL != ccoli && conn->ntables >= COLI_RECYCLE)
			{
				/* Not found unsed object. Amount of them is on limit. Taking least recently used coli object. */
				coli_exist = TRUE;
				coli = ccoli;
			}
		}
		if (coli_exist)
		{
			/* We have ready to use coli object. Cleaning it. */
			tmp_refcnt = coli->refcnt; /* If we found coli with greloid, then some TABLE_INFO objects may have references to it -> save refcnt for them. */
			tmp_refcnt--; /* Down the road we will increase refcnt again to account for the reference from ConnectionClass object to coli object. */
			free_col_info_contents(coli);
		}
		else
		{
			/* We have no coli object. Must create a new one. */
			if (conn->ntables >= conn->coli_allocated)
			{
				/* No place in connection COL_INFO cache table. Allocating or reallocating. */
				Int2	new_alloc;
				COL_INFO **col_info;

				new_alloc = conn->coli_allocated * 2;
				if (new_alloc <= conn->ntables)
					new_alloc = COLI_INCR;
				MYLOG(0, "PARSE: Allocating col_info at ntables=%d\n", conn->ntables);

				col_info = (COL_INFO **) realloc(conn->col_info, new_alloc * sizeof(COL_INFO *));
				if (!col_info)
				{
					if (stmt)
						SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "PGAPI_AllocStmt failed in parse_statement for col_info.", __FUNCTION__);
					goto cleanup;
				}
				conn->col_info = col_info;
				conn->coli_allocated = new_alloc;
			}

			/* Allocating new COL_INFO object. */
			MYLOG(0, "PARSE: malloc at conn->col_info[%d]\n", conn->ntables);
			coli = conn->col_info[conn->ntables] = (COL_INFO *) malloc(sizeof(COL_INFO));
		}
		if (!coli)
		{
			if (stmt)
				SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "PGAPI_AllocStmt failed in parse_statement for col_info(2).", __FUNCTION__);
			goto cleanup;
		}
		col_info_initialize(coli);

		coli->refcnt = tmp_refcnt;
		coli->refcnt++; /* Counting one reference to coli object from connection COL_INFO cache table. */
		coli->result = res;
		if (res && QR_get_num_cached_tuples(res) > 0)
		{
			int num_tuples = QR_get_num_cached_tuples(res);
			int i;

			if (!greloid)
				greloid = (OID) strtoul(QR_get_value_backend_text(res, 0, COLUMNS_TABLE_OID), NULL, 10);
			if (!wti->table_oid)
				wti->table_oid = greloid;
			if (NAME_IS_NULL(wti->schema_name))
				STR_TO_NAME(wti->schema_name,
					QR_get_value_backend_text(res, 0, COLUMNS_SCHEMA_NAME));
			if (NAME_IS_NULL(wti->table_name))
				STR_TO_NAME(wti->table_name,
					QR_get_value_backend_text(res, 0, COLUMNS_TABLE_NAME));
			for (i = 0; i < num_tuples; i++)
			{
				if (NULL != QR_get_value_backend_text(res, 0, COLUMNS_TABLE_INFO))
				{
					coli->table_info = QR_get_value_backend_int(res, 0, COLUMNS_TABLE_INFO, NULL);
					break;
				}
			}
		}
MYLOG(DETAIL_LOG_LEVEL, "#2 %p->table_name=%s(%u)\n", wti, PRINT_NAME(wti->table_name), wti->table_oid);
		/*
		 * Store the table name and the SQLColumns result
		 * structure
		 */
		if (NAME_IS_VALID(wti->schema_name))
		{
			NAME_TO_NAME(coli->schema_name,  wti->schema_name);
		}
		else
			NULL_THE_NAME(coli->schema_name);
		NAME_TO_NAME(coli->table_name, wti->table_name);
		coli->table_oid = wti->table_oid;

		/*
		 * The connection will now free the result structures, so
		 * make sure that the statement doesn't free it
		 */
		SC_init_Result(col_stmt);

		if (!coli_exist)
			conn->ntables++;

if (res && QR_get_num_cached_tuples(res) > 0)
MYLOG(DETAIL_LOG_LEVEL, "oid item == %s\n", (const char *) QR_get_value_backend_text(res, 0, 3));

		MYLOG(0, "Created col_info table='%s', ntables=%d\n", PRINT_NAME(wti->table_name), conn->ntables);
		/* Associate a table from the statement with a SQLColumn info */
		found = TRUE;
		if (wti->col_info)
		{
			/* wti also has reference to COL_INFO object, so we must release it. */
MYLOG(0, "!!!refcnt %p:%d -> %d\n", wti->col_info, wti->col_info->refcnt, wti->col_info->refcnt - 1);
			wti->col_info->refcnt--;
			if (wti->col_info->refcnt <= 0)
			{
				free_col_info_contents(wti->col_info);
				free(wti->col_info);
			}
		}
		coli->refcnt++; /* Counting another one reference to coli object from TABLE_INFO wti object. */
		wti->col_info = coli;
	}
cleanup:
	if (hcol_stmt)
		PGAPI_FreeStmt(hcol_stmt, SQL_DROP);
	return found;
}

BOOL getCOLIfromTI(const char *func, ConnectionClass *conn, StatementClass *stmt, const OID reloid, TABLE_INFO **pti)
{
	BOOL	colatt = FALSE, found = FALSE;
	OID	greloid = reloid;
	TABLE_INFO	*wti = *pti;
	COL_INFO	*coli;

MYLOG(DETAIL_LOG_LEVEL, "entering reloid=%u ti=%p\n", reloid, wti);
	if (!conn)
		conn = SC_get_conn(stmt);
	if (!wti)	/* SQLColAttribute case */
	{
		int	i;

		if (0 == greloid)
			return FALSE;
		if (!stmt)
			return FALSE;
		colatt = TRUE;
		for (i = 0; i < stmt->ntab; i++)
		{
			if (stmt->ti[i]->table_oid == greloid)
			{
				wti = stmt->ti[i];
				break;
			}
		}
		if (!wti)
		{
MYLOG(DETAIL_LOG_LEVEL, "before increaseNtab\n");
			if (!increaseNtab(stmt, func))
				return FALSE;
			wti = stmt->ti[stmt->ntab - 1];
			wti->table_oid = greloid;
		}
		*pti = wti;
	}
MYLOG(DETAIL_LOG_LEVEL, "fi=%p greloid=%d col_info=%p\n", wti, greloid, wti->col_info);
	if (0 == greloid)
		greloid = wti->table_oid;
	if (NULL != wti->col_info)
	{
		found = TRUE;
		goto cleanup;
	}
	if (greloid != 0)
	{
		int	colidx;

		for (colidx = 0; colidx < conn->ntables; colidx++)
		{
			if (conn->col_info[colidx]->table_oid == greloid)
			{
				MYLOG(0, "FOUND col_info table=%ul\n", greloid);
				found = TRUE;
				wti->col_info = conn->col_info[colidx];
				wti->col_info->refcnt++;
				break;
			}
		}
	}
	else
	{
		if (!getCOLIfromTable(conn, &wti->schema_name, wti->table_name, &coli))
		{
			if (stmt)
			{
				SC_set_parse_status(stmt, STMT_PARSE_FATAL);
				SC_set_error(stmt, STMT_EXEC_ERROR, "Table not found", func);
				SC_reset_updatable(stmt);
			}
			return FALSE;
		}
		else if (NULL != coli)
		{
			found = TRUE;
			coli->refcnt++;
			wti->col_info = coli;
		}
	}
	if (found)
		goto cleanup;
	else if (0 != greloid || NAME_IS_VALID(wti->table_name))
		found = getColumnsInfo(conn, wti, greloid, stmt);
cleanup:
	if (found)
	{
		QResultClass	*res = wti->col_info->result;

		if (res && QR_get_num_cached_tuples(res) > 0)
		{
			if (!greloid)
				greloid = (OID) strtoul(QR_get_value_backend_text(res, 0, COLUMNS_TABLE_OID), NULL, 10);
			if (!wti->table_oid)
				wti->table_oid = greloid;
			if (NAME_IS_NULL(wti->schema_name))
				STR_TO_NAME(wti->schema_name,
					QR_get_value_backend_text(res, 0, COLUMNS_SCHEMA_NAME));
			if (NAME_IS_NULL(wti->table_name))
				STR_TO_NAME(wti->table_name,
					QR_get_value_backend_text(res, 0, COLUMNS_TABLE_NAME));
		}
MYLOG(DETAIL_LOG_LEVEL, "#1 %p->table_name=%s(%u)\n", wti, PRINT_NAME(wti->table_name), wti->table_oid);
		if (colatt /* SQLColAttribute case */
		    && 0 == (wti->flags & TI_COLATTRIBUTE))
		{
			if (stmt)
				ColAttSet(stmt, wti);
		}
		wti->col_info->acc_time = SC_get_time(stmt);
	}
	else if (!colatt && stmt)
		SC_set_parse_status(stmt, STMT_PARSE_FATAL);
MYLOG(DETAIL_LOG_LEVEL, "leaving returns %d\n", found);
	return found;
}

SQLRETURN
SC_set_SS_columnkey(StatementClass *stmt)
{
	IRDFields	*irdflds = SC_get_IRDF(stmt);
	FIELD_INFO	**fi = irdflds->fi, *tfi;
	size_t		nfields = irdflds->nfields;
	HSTMT		pstmt = NULL;
	SQLRETURN	ret = SQL_SUCCESS;
	BOOL		contains_key = FALSE;
	int		i;

MYLOG(DETAIL_LOG_LEVEL, "entering fields=" FORMAT_SIZE_T " ntab=%d\n", nfields, stmt->ntab);
	if (!fi)		return ret;
	if (0 >= nfields)	return ret;
	for (i = 0; i < stmt->ntab; i++)
	{
		TABLE_INFO	**ti = stmt->ti, *oneti;
		ConnectionClass *conn = SC_get_conn(stmt);
		OID	internal_asis_type = SQL_C_CHAR;
		char		keycolnam[MAX_INFO_STRING];
		SQLLEN		keycollen;

		ret = PGAPI_AllocStmt(conn, &pstmt, 0);
		if (!SQL_SUCCEEDED(ret))
			return ret;
		oneti = ti[i];
		ret = PGAPI_PrimaryKeys(pstmt, NULL, 0, NULL, 0, NULL, 0, oneti->table_oid);
		if (!SQL_SUCCEEDED(ret))
			goto cleanup;
#ifdef	UNICODE_SUPPORT
		if (CC_is_in_unicode_driver(conn))
			internal_asis_type = INTERNAL_ASIS_TYPE;
#endif /* UNICODE_SUPPORT */
		ret = PGAPI_BindCol(pstmt, 4, internal_asis_type, keycolnam, MAX_INFO_STRING, &keycollen);
		if (!SQL_SUCCEEDED(ret))
			goto cleanup;
		contains_key = TRUE;
		ret = PGAPI_Fetch(pstmt);
		while (SQL_SUCCEEDED(ret))
		{
			int	i;	// different from i of outer loop

			for (i = 0; i < nfields; i++)
			{
				if (tfi = fi[i], NULL == tfi)
					continue;
				if (!FI_is_applicable(tfi))
					continue;
				if (oneti == tfi->ti &&
				    strcmp(keycolnam, SAFE_NAME(tfi->column_name)) == 0)
				{
MYLOG(DETAIL_LOG_LEVEL, "key %s found at %p\n", keycolnam, fi + i);
					tfi->columnkey = TRUE;
					break;
				}
			}
			if (i >= nfields)
			{
				MYLOG(0, "%s not found\n", keycolnam);
				break;
			}
			ret = PGAPI_Fetch(pstmt);
		}
		if (SQL_SUCCEEDED(ret))
			contains_key = FALSE;
		else if (SQL_NO_DATA_FOUND != ret)
			goto cleanup;
		ret = SQL_SUCCESS;
	}
MYLOG(DETAIL_LOG_LEVEL, "contains_key=%d\n", contains_key);
	for (i = 0; i < nfields; i++)
	{
		if (tfi = fi[i], NULL == tfi)
			continue;
		if (!FI_is_applicable(tfi))
			continue;
		if (!contains_key || tfi->columnkey < 0)
			tfi->columnkey = FALSE;
	}
cleanup:
	if (pstmt)
		PGAPI_FreeStmt(pstmt, SQL_DROP);
	return ret;
}

static BOOL include_alias_wo_as(const char *token, const char *btoken)
{
MYLOG(0, "alias ? token=%s btoken=%s\n", token, btoken);
	if ('\0' == btoken[0])
		return FALSE;
	else if (0 == stricmp(")", token))
		return FALSE;
	else if (0 == stricmp("as", btoken) ||
		 0 == stricmp("and", btoken) ||
		 0 == stricmp("or", btoken) ||
		 0 == stricmp("not", btoken) ||
		 0 == stricmp(",", btoken))
		return FALSE;
	else
	{
		CSTR ops = "+-*/%^|!@&#~<>=.";
		const char *cptr, *optr;

		for (cptr = btoken; *cptr; cptr++)
		{
			for (optr = ops; *optr; optr++)
			{
				if (*optr != *cptr)
					return TRUE;
			}
		}
	}

	return FALSE;
}

static char *insert_as_to_the_statement(char *stmt, const char **pptr, const char **ptr)
{
	size_t	stsize = strlen(stmt), ppos = *pptr - stmt, remsize = stsize - ppos;
	const int  ins_size = 3;
	char	*newstmt = realloc(stmt, stsize + ins_size + 1);

	if (newstmt)
	{
		char	*sptr = newstmt + ppos;
		memmove(sptr + ins_size, sptr, remsize + 1);
		sptr[0] = 'a';
		sptr[1] = 's';
		sptr[2] = ' ';
		*ptr = sptr + (*ptr - *pptr) + ins_size;
		*pptr = sptr + ins_size;
	}

	return newstmt;
}

#define	TOKEN_SIZE	256
static char
parse_the_statement(StatementClass *stmt, BOOL check_hasoids, BOOL sqlsvr_check)
{
	CSTR		func = "parse_the_statement";
	char		delimdsp[2], token[TOKEN_SIZE], stoken[TOKEN_SIZE], btoken[TOKEN_SIZE];
	char		delim,
				quote,
				dquote,
				numeric,
				unquoted;
	const char *ptr;
	const char *pptr = NULL;
	char		in_select = FALSE,
				in_distinct = FALSE,
				in_on = FALSE,
				in_from = FALSE,
				in_where = FALSE,
				in_table = FALSE,
				out_table = TRUE;
	char		in_field = FALSE,
				in_expr = FALSE,
				in_func = FALSE,
				in_dot = FALSE,
				in_as = FALSE;
	int			j,
				i,
				k = 0,
				n,
				blevel = 0, old_blevel, subqlevel = 0,
				tbl_blevel = 0, allocated_size = -1, new_size,
				nfields;
	FIELD_INFO **fi, *wfi;
	TABLE_INFO **ti, *wti;
	char		parse = FALSE;
	po_ind_t	join_info = STMT_HAS_NO_JOIN;
	ConnectionClass *conn = SC_get_conn(stmt);
	IRDFields	*irdflds;
	BOOL		updatable = TRUE, column_has_alias = FALSE, fupdatable;

	MYLOG(0, "entering...\n");

	if (SC_parsed_status(stmt) != STMT_PARSE_NONE)
	{
		if (check_hasoids)
			CheckPgClassInfo(stmt);
		return TRUE;
	}
	nfields = 0;
	wfi = NULL;
	wti = NULL;
	ptr = stmt->statement;
	if (sqlsvr_check)
	{
		irdflds = NULL;
		fi = NULL;
		ti = NULL;
	}
	else
	{
		SC_set_updatable(stmt, FALSE);
		irdflds = SC_get_IRDF(stmt);
		fi = irdflds->fi;
		ti = stmt->ti;

		allocated_size = irdflds->allocated;
		SC_initialize_cols_info(stmt, FALSE, TRUE);
		stmt->from_pos = -1;
		stmt->where_pos = -1;
	}
#define	return	DONT_CALL_RETURN_FROM_HERE???

	delim = '\0';
	token[0] = '\0';
	while (pptr = (const char *) ptr, (delim != ',') ? STRCPY_FIXED(btoken, token) : (btoken[0] = '\0', 0), (ptr = getNextToken(conn->ccsc, CC_get_escape(conn), pptr, token, sizeof(token), &delim, &quote, &dquote, &numeric)) != NULL)
	{
		unquoted = !(quote || dquote);

		if (delim)
		{
			delimdsp[0] = delim;
			delimdsp[1] = '\0';
		}
		else
			delimdsp[0] = '\0';
		MYLOG(0, "unquoted=%d, quote=%d, dquote=%d, numeric=%d, delim='%s', token='%s', ptr='%s'\n", unquoted, quote, dquote, numeric, delimdsp, token, ptr);

		old_blevel = blevel;
		if (unquoted && blevel == 0)
		{
			if (in_select)
			{
				if (!stricmp(token, "distinct"))
				{
					in_distinct = TRUE;
					updatable = FALSE;

					MYLOG(0, "DISTINCT\n");
					continue;
				}
				else if (!stricmp(token, "into"))
				{
					in_select = FALSE;
					MYLOG(0, "INTO\n");
					stmt->statement_type = STMT_TYPE_CREATE;
					SC_set_parse_status(stmt, STMT_PARSE_FATAL);
					goto cleanup;
				}
				else if (!stricmp(token, "from"))
				{
					if (sqlsvr_check)
					{
						parse = TRUE;
						goto cleanup;
					}
					in_select = FALSE;
					in_from = TRUE;
					if (stmt->from_pos < 0 &&
						(!strnicmp(pptr, "from", 4)))
					{
						MYLOG(0, "First From\n");
						stmt->from_pos = pptr - stmt->statement;
					}
					else
						MYLOG(0, "FROM\n");
					continue;
				}
			} /* in_select && unquoted && blevel == 0 */
			else if ((!stricmp(token, "where") ||
				 !stricmp(token, "union") ||
				 !stricmp(token, "intersect") ||
				 !stricmp(token, "except") ||
				 !stricmp(token, "order") ||
				 !stricmp(token, "group") ||
				 !stricmp(token, "having")))
			{
				in_from = FALSE;
				in_where = TRUE;

				if (stmt->where_pos < 0)
					stmt->where_pos = pptr - stmt->statement;
				MYLOG(0, "%s...\n", token);
				if (stricmp(token, "where") &&
				    stricmp(token, "order"))
				{
					updatable = FALSE;
					break;
				}
				continue;
			}
		} /* unquoted && blevel == 0 */
		/* check the change of blevel etc */
		if (unquoted)
		{
			if (!stricmp(token, "select"))
			{
				stoken[0] = '\0';
				if (0 == blevel)
				{
					in_select = TRUE;
					MYLOG(0, "SELECT\n");
					continue;
				}
				else
				{
					MYLOG(0, "SUBSELECT\n");
					if (0 == subqlevel)
						subqlevel = blevel;
				}
			}
			else if (token[0] == '(')
			{
				blevel++;
				MYLOG(0, "blevel++ -> %d\n", blevel);
				/* aggregate function ? */
				if (stoken[0] && updatable && 0 == subqlevel)
				{
					if (stricmp(stoken, "count") == 0 ||
					    stricmp(stoken, "sum") == 0 ||
					    stricmp(stoken, "avg") == 0 ||
					    stricmp(stoken, "max") == 0 ||
					    stricmp(stoken, "min") == 0 ||
					    stricmp(stoken, "variance") == 0 ||
					    stricmp(stoken, "stddev") == 0)
						updatable = FALSE;
				}
			}
			else if (token[0] == ')')
			{
				blevel--;
				MYLOG(0, "blevel-- = %d\n", blevel);
				if (blevel < subqlevel)
					subqlevel = 0;
			}
			if (blevel >= old_blevel && ',' != delim)
				STRCPY_FIXED(stoken, token);
			else
				stoken[0] = '\0';
		}
		if (in_select)
		{
MYLOG(0, "blevel=%d btoken=%s in_dot=%d in_field=%d tbname=%s\n", blevel, btoken, in_dot, in_field, wfi ? SAFE_NAME(wfi->column_alias) : "<null>");
			if (0 == blevel &&
			    sqlsvr_check &&
			    dquote &&
			    '\0' != btoken[0] &&
			    !in_dot &&
			    in_field &&
			    (!column_has_alias))
			{
				if (include_alias_wo_as(token, btoken))
				{
					char *news;

					column_has_alias = TRUE;
					if (NULL != wfi)
						STRX_TO_NAME(wfi->column_alias, token);
					news = insert_as_to_the_statement(stmt->statement, &pptr, &ptr);
					if (news != stmt->statement)
					{
						free(stmt->statement);
						stmt->statement = news;
					}
				}
			}
			if (in_expr || in_func)
			{
				/* just eat the expression */
				MYLOG(0, "in_expr=%d or func=%d\n", in_expr, in_func);

				if (blevel == 0)
				{
					if (delim == ',')
					{
						MYLOG(0, "**** Got comma in_expr/func\n");
						in_func = FALSE;
						in_expr = FALSE;
						in_field = FALSE;
					}
					else if (unquoted && !stricmp(token, "as"))
					{
						MYLOG(0, "got AS in_expr\n");
						in_func = FALSE;
						in_expr = FALSE;
						in_as = TRUE;
						in_field = TRUE;
					}
				}
				continue;
			} /* (in_expr || in_func) && in_select */

			if (in_distinct)
			{
				MYLOG(0, "in distinct\n");

				if (unquoted && !stricmp(token, "on"))
				{
					in_on = TRUE;
					MYLOG(0, "got on\n");
					continue;
				}
				if (in_on)
				{
					in_distinct = FALSE;
					in_on = FALSE;
					continue;	/* just skip the unique on field */
				}
				MYLOG(0, "done distinct\n");
				in_distinct = FALSE;
			} /* in_distinct */

			if (!in_field)
			{
				BOOL	fi_reuse = FALSE;

				if (!token[0])
					continue;

				column_has_alias = FALSE;
				if (!sqlsvr_check)
				{
					/* if (!(irdflds->nfields % FLD_INCR)) */
					if (irdflds->nfields >= allocated_size)
					{
						MYLOG(0, "reallocing at nfld=%d\n", irdflds->nfields);
						new_size = irdflds->nfields + 1;
						if (!allocateFields(irdflds, new_size))
						{
							SC_set_parse_status(stmt, STMT_PARSE_FATAL);
							SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "PGAPI_AllocStmt failed in parse_statement for FIELD_INFO.", func);
							goto cleanup;
						}
						fi = irdflds->fi;
						allocated_size = irdflds->allocated;
					}

					wfi = fi[irdflds->nfields];
					if (NULL != wfi)
						fi_reuse = TRUE;
					else
						wfi = fi[irdflds->nfields] = (FIELD_INFO *) malloc(sizeof(FIELD_INFO));
					if (NULL == wfi)
					{
						SC_set_parse_status(stmt, STMT_PARSE_FATAL);
						SC_set_error(stmt, STMT_NO_MEMORY_ERROR, "PGAPI_AllocStmt failed in parse_statement for FIELD_INFO(2).", func);
						goto cleanup;
					}

					/* Initialize the field info */
					FI_Constructor(wfi, fi_reuse);
					wfi->flag = FIELD_PARSING;
				}

				/* double quotes are for qualifiers */
				if (dquote && NULL != wfi)
					wfi->dquote = TRUE;

				if (quote)
				{
					if (NULL != wfi)
					{
						wfi->quote = TRUE;
						wfi->column_size = (int) strlen(token);
					}
				}
				else if (numeric)
				{
					MYLOG(0, "**** got numeric: nfld = %d\n", nfields);
					if (NULL != wfi)
						wfi->numeric = TRUE;
				}
				else if (0 == old_blevel && blevel > 0)
				{				/* expression */
					MYLOG(0, "got EXPRESSION\n");
					if (NULL != wfi)
						wfi->expr = TRUE;
					in_expr = TRUE;
					/* continue; */
				}
				else if (NULL != wfi)
				{
					STRX_TO_NAME(wfi->column_name, token);
					NULL_THE_NAME(wfi->before_dot);
				}
				if (NULL != wfi)
					MYLOG(0, "got field='%s', dot='%s'\n", PRINT_NAME(wfi->column_name), PRINT_NAME(wfi->before_dot));

				if (delim == ',')
					MYLOG(0, "comma (1)\n");
				else
					in_field = TRUE;
				nfields++;
				if (NULL != irdflds)
					irdflds->nfields++;
				continue;
			} /* !in_field */

			/*
			 * We are in a field now
			 */
			if (!sqlsvr_check)
				wfi = fi[irdflds->nfields - 1];
			if (in_dot)
			{
				if (NULL != wfi)
				{
					if (NAME_IS_VALID(wfi->before_dot))
					{
						MOVE_NAME(wfi->schema_name, wfi->before_dot);
					}
					MOVE_NAME(wfi->before_dot, wfi->column_name);
					STRX_TO_NAME(wfi->column_name, token);
				}

				if (delim == ',')
				{
					MYLOG(0, "in_dot: got comma\n");
					in_field = FALSE;
				}
				in_dot = FALSE;
				continue;
			}

			if (in_as)
			{
				column_has_alias = TRUE;
				if (NULL != wfi)
				{
					STRX_TO_NAME(wfi->column_alias, token);
					MYLOG(0, "alias for field '%s' is '%s'\n", PRINT_NAME(wfi->column_name), PRINT_NAME(wfi->column_alias));
				}
				in_as = FALSE;
				in_field = FALSE;

				if (delim == ',')
					MYLOG(0, "comma(2)\n");
				continue;
			}

			/* Function */
			if (0 == old_blevel && blevel > 0)
			{
				in_dot = FALSE;
				in_func = TRUE;
				if (NULL != wfi)
				{
					wfi->func = TRUE;

					/*
					 * name will have the function name -- maybe useful some
					 * day
					 */
					MYLOG(0, "**** got function = '%s'\n", PRINT_NAME(wfi->column_name));
				}
				continue;
			}

			if (token[0] == '.')
			{
				in_dot = TRUE;
				MYLOG(0, "got dot\n");
				continue;
			}

			in_dot = FALSE;
			if (!stricmp(token, "as"))
			{
				in_as = TRUE;
				MYLOG(0, "got AS\n");
				continue;
			}

			/* otherwise, it's probably an expression */
			if (!column_has_alias)
			{
				in_expr = TRUE;
				if (NULL != wfi)
				{
					wfi->expr = TRUE;
					NULL_THE_NAME(wfi->column_name);
					wfi->column_size = 0;
				}
				MYLOG(0, "*** setting expression\n");
			}
			else
				MYLOG(0, "*** may be an alias for a field\n");
			if (0 == blevel && ',' == delim)
			{
				in_expr = in_func = in_field = FALSE;
			}
		} /* in_select end */

		if (in_from || in_where)
		{
			if (token[0] == ';') /* end of the first command */
			{
				in_select = in_from = in_where = in_table = FALSE;
				break;
			}
		}
		if (in_from)
		{
			switch (token[0])
			{
				case '\0':
					continue;
				case ',':
					out_table = TRUE;
					continue;
			}
			if (out_table && !in_table) /* new table */
			{
				BOOL	is_table_name, is_subquery;

				in_dot = FALSE;
				join_info = STMT_HAS_NO_JOIN;
				if (!dquote)
				{
					if (token[0] == '(' ||
					    token[0] == ')')
						continue;
				}

				if (sqlsvr_check)
					wti = NULL;
				else
				{
					if (!increaseNtab(stmt, func))
					{
						SC_set_parse_status(stmt, STMT_PARSE_FATAL);
						goto cleanup;
					}

					ti = stmt->ti;
					wti = ti[stmt->ntab - 1];
				}
				is_table_name = TRUE;
				is_subquery = FALSE;
				if (dquote)
					;
				else if (0 == stricmp(token, "select"))
				{
					MYLOG(0, "got subquery lvl=%d\n", blevel);
					is_table_name = FALSE;
					is_subquery = TRUE;
				}
				else if ('(' == ptr[0])
				{
					MYLOG(0, "got srf? = '%s'\n", token);
					is_table_name = FALSE;
				}
				if (NULL != wti)
				{
					if (is_table_name)
					{
						STRX_TO_NAME(wti->table_name, token);
						lower_the_name(GET_NAME(wti->table_name), conn, dquote);
						MYLOG(0, "got table = '%s'\n", PRINT_NAME(wti->table_name));
					}
					else
					{
						NULL_THE_NAME(wti->table_name);
						TI_no_updatable(wti);
					}
				}

				if (0 == blevel && delim == ',')
				{
					out_table = TRUE;
					MYLOG(0, "more than 1 tables\n");
				}
				else
				{
					out_table = FALSE;
					in_table = TRUE;
					if (is_subquery)
						tbl_blevel = blevel - 1;
					else
						tbl_blevel = blevel;
				}
				continue;
			}
			if (blevel > tbl_blevel)
				continue;
			/* out_table is FALSE here */
			if (!dquote && !in_dot)
			{
				if (')' == token[0])
					continue;
				if (stricmp(token, "LEFT") == 0 ||
					stricmp(token, "RIGHT") == 0 ||
					stricmp(token, "OUTER") == 0 ||
					stricmp(token, "FULL") == 0)
				{
					join_info = STMT_HAS_OUTER_JOIN;
					in_table = FALSE;
					continue;
				}
				else if (stricmp(token, "INNER") == 0 ||
						 stricmp(token, "CROSS") == 0)
				{
					join_info = STMT_HAS_INNER_JOIN;
					in_table = FALSE;
					continue;
				}
				else if (stricmp(token, "JOIN") == 0)
				{
					in_table = FALSE;
					out_table = TRUE;
					if (join_info == STMT_HAS_OUTER_JOIN)
					{
						SC_set_outer_join(stmt);
					}
					else
					{
						SC_set_inner_join(stmt);
					}
					join_info = STMT_HAS_NO_JOIN;
					continue;
				}
			}
			join_info = STMT_HAS_NO_JOIN;
			if (in_table)
			{
				if (!sqlsvr_check)
					wti = ti[stmt->ntab - 1];
				if (in_dot)
				{
					if (NULL != wfi)
					{
						MOVE_NAME(wti->schema_name, wti->table_name);
						STRX_TO_NAME(wti->table_name, token);
						lower_the_name(GET_NAME(wti->table_name), conn, dquote);
					}
					in_dot = FALSE;
					continue;
				}
				if (strcmp(token, ".") == 0)
				{
					in_dot = TRUE;
					continue;
				}
				if (dquote || stricmp(token, "as"))
				{
					if (!dquote)
					{
						if (stricmp(token, "ON") == 0)
						{
							in_table = FALSE;
							continue;
						}
					}
					if (NULL != wti)
					{
						STRX_TO_NAME(wti->table_alias, token);
						MYLOG(0, "alias for table '%s' is '%s'\n", PRINT_NAME(wti->table_name), PRINT_NAME(wti->table_alias));
					}
					in_table = FALSE;
					if (delim == ',')
					{
						out_table = TRUE;
						MYLOG(0, "more than 1 tables\n");
					}
				}
			}
		} /* in_from */
	}

	/*
	 * Resolve any possible field names with tables
	 */

	parse = TRUE;
	if (sqlsvr_check)
		goto cleanup;

	/* Resolve field names with tables */
	for (i = 0; i < (int) irdflds->nfields; i++)
	{
		wfi = fi[i];
		if (wfi->func || wfi->expr || wfi->numeric)
		{
			wfi->ti = NULL;
			wfi->columntype = wfi->basetype = (OID) 0;
			parse = FALSE;
			continue;
		}
		else if (wfi->quote)
		{						/* handle as text */
			wfi->ti = NULL;

			/*
			 * wfi->type = PG_TYPE_TEXT; wfi->column_size = 0; the
			 * following may be better
			 */
			wfi->basetype = PG_TYPE_UNKNOWN;
			if (wfi->column_size == 0)
			{
				wfi->basetype = PG_TYPE_VARCHAR;
				wfi->column_size = 254;
			}
			wfi->length = wfi->column_size;
			continue;
		}
		/* field name contains the schema name */
		else if (NAME_IS_VALID(wfi->schema_name))
		{
			int	matchidx = -1;

			for (k = 0; k < stmt->ntab; k++)
			{
				wti = ti[k];
				if (!NAMEICMP(wti->table_name, wfi->before_dot))
				{
					if (!NAMEICMP(wti->schema_name, wfi->schema_name))
					{
						wfi->ti = wti;
						break;
					}
					else if (NAME_IS_NULL(wti->schema_name))
					{
						if (matchidx < 0)
							matchidx = k;
						else
						{
							SC_set_parse_status(stmt, STMT_PARSE_FATAL);
							SC_set_error(stmt, STMT_EXEC_ERROR, "duplicated Table name", func);
							SC_reset_updatable(stmt);
							goto cleanup;
						}
					}
				}
			}
			if (matchidx >= 0)
				wfi->ti = ti[matchidx];
		}
		/* it's a dot, resolve to table or alias */
		else if (NAME_IS_VALID(wfi->before_dot))
		{
			for (k = 0; k < stmt->ntab; k++)
			{
				wti = ti[k];
				if (!NAMEICMP(wti->table_alias, wfi->before_dot))
				{
					wfi->ti = wti;
					break;
				}
				else if (!NAMEICMP(wti->table_name, wfi->before_dot))
				{
					wfi->ti = wti;
					break;
				}
			}
		}
		else if (stmt->ntab == 1)
			wfi->ti = ti[0];
	}

	MYLOG(0, "--------------------------------------------\n");
	MYLOG(0, "nfld=%d, ntab=%d\n", irdflds->nfields, stmt->ntab);
	if (0 == stmt->ntab)
	{
		SC_set_parse_status(stmt, STMT_PARSE_FATAL);
		goto cleanup;
	}

	for (i = 0; i < (int) irdflds->nfields; i++)
	{
		wfi = fi[i];
		MYLOG(0, "Field %d:  expr=%d, func=%d, quote=%d, dquote=%d, numeric=%d, name='%s', alias='%s', dot='%s'\n", i, wfi->expr, wfi->func, wfi->quote, wfi->dquote, wfi->numeric, PRINT_NAME(wfi->column_name), PRINT_NAME(wfi->column_alias), PRINT_NAME(wfi->before_dot));
		if (wfi->ti)
			MYLOG(0, "     ----> table_name='%s', table_alias='%s'\n", PRINT_NAME(wfi->ti->table_name), PRINT_NAME(wfi->ti->table_alias));
	}

	for (i = 0; i < stmt->ntab; i++)
	{
		wti = ti[i];
		MYLOG(0, "Table %d: name='%s', alias='%s'\n", i, PRINT_NAME(wti->table_name), PRINT_NAME(wti->table_alias));
	}

	/*
	 * Now save the SQLColumns Info for the parse tables
	 */

	/* Call SQLColumns for each table and store the result */
	if (stmt->ntab > 1)
		updatable = FALSE;
	else if (stmt->from_pos < 0)
		updatable = FALSE;
	for (i = 0; i < stmt->ntab; i++)
	{
		/* See if already got it */
		wti = ti[i];

		if (!getCOLIfromTI(func, NULL, stmt, 0, &wti))
			break;
	}
	if (STMT_PARSE_FATAL == SC_parsed_status(stmt))
	{
		goto cleanup;
	}

	MYLOG(0, "Done PG_Columns\n");

	/*
	 * Now resolve the fields to point to column info
	 */
	for (i = 0; i < (int) irdflds->nfields;)
	{
		wfi = fi[i];
		if (wfi->ti)
			fupdatable = updatable && TI_is_updatable(wfi->ti);
		else
			fupdatable = FALSE;
		wfi->updatable = fupdatable;
		/* Dont worry about functions or quotes */
		if (wfi->func || wfi->quote || wfi->numeric)
		{
			wfi->updatable = FALSE;
			i++;
			continue;
		}

		/* Stars get expanded to all fields in the table */
		else if (SAFE_NAME(wfi->column_name)[0] == '*')
		{
			char		do_all_tables;
			Int2			total_cols,
						cols,
						increased_cols;

			MYLOG(0, "expanding field %d\n", i);

			total_cols = 0;

			if (wfi->ti)		/* The star represents only the qualified
								 * table */
				total_cols = (Int2) QR_get_num_cached_tuples(wfi->ti->col_info->result);

			else
			{	/* The star represents all tables */
				/* Calculate the total number of columns after expansion */
				for (k = 0; k < stmt->ntab; k++)
					total_cols += (Int2) QR_get_num_cached_tuples(ti[k]->col_info->result);
			}
			increased_cols = total_cols - 1;

			/* Allocate some more field pointers if necessary */
			new_size = irdflds->nfields + increased_cols;

			MYLOG(0, "k=%d, increased_cols=%d, allocated_size=%d, new_size=%d\n", k, increased_cols, allocated_size, new_size);

			if (new_size > allocated_size)
			{
				int	new_alloc = new_size;

				MYLOG(0, "need more cols: new_alloc = %d\n", new_alloc);
				if (!allocateFields(irdflds, new_alloc))
				{
					SC_set_parse_status(stmt, STMT_PARSE_FATAL);
					goto cleanup;
				}
				fi = irdflds->fi;
				allocated_size = irdflds->allocated;
			}

			/*
			 * copy any other fields (if there are any) up past the
			 * expansion
			 */
			for (j = irdflds->nfields - 1; j > i; j--)
			{
				MYLOG(0, "copying field %d to %d\n", j, increased_cols + j);
				fi[increased_cols + j] = fi[j];
			}
			MYLOG(0, "done copying fields\n");

			/* Set the new number of fields */
			irdflds->nfields += increased_cols;
			MYLOG(0, "irdflds->nfields now at %d\n", irdflds->nfields);


			/* copy the new field info */
			do_all_tables = (wfi->ti ? FALSE : TRUE);
			wfi = NULL;

			for (k = 0; k < (do_all_tables ? stmt->ntab : 1); k++)
			{
				TABLE_INFO *the_ti = do_all_tables ? ti[k] : fi[i]->ti;

				cols = (Int2) QR_get_num_cached_tuples(the_ti->col_info->result);

				for (n = 0; n < cols; n++)
				{
					FIELD_INFO	*afi;
					BOOL		reuse = TRUE;

					MYLOG(0, "creating field info: n=%d\n", n);
					/* skip malloc (already did it for the Star) */
					if (k > 0 || n > 0)
					{
						MYLOG(0, "allocating field info at %d\n", n + i);
						fi[n + i] = (FIELD_INFO *) malloc(sizeof(FIELD_INFO));
						if (fi[n + i] == NULL)
						{
							SC_set_parse_status(stmt, STMT_PARSE_FATAL);
							goto cleanup;
						}
						reuse = FALSE;
					}
					afi = fi[n + i];
					/* Initialize the new space (or the * field) */
					FI_Constructor(afi, reuse);
					afi->ti = the_ti;

					MYLOG(0, "about to copy at %d\n", n + i);

					getColInfo(the_ti->col_info, afi, n);
					afi->updatable = fupdatable;

					MYLOG(0, "done copying\n");
				}

				i += cols;
				MYLOG(0, "i now at %d\n", i);
			}
		}

		/*
		 * We either know which table the field was in because it was
		 * qualified with a table name or alias -OR- there was only 1
		 * table.
		 */
		else if (wfi->ti)
		{
			if (!searchColInfo(fi[i]->ti->col_info, wfi))
			{
				parse = FALSE;
				wfi->updatable = FALSE;
			}
			i++;
		}

		/* Don't know the table -- search all tables in "from" list */
		else
		{
			for (k = 0; k < stmt->ntab; k++)
			{
				if (searchColInfo(ti[k]->col_info, wfi))
				{
					wfi->ti = ti[k];	/* now know the table */
					break;
				}
			}
			if (k >= stmt->ntab)
			{
				parse = FALSE;
				wfi->updatable = FALSE;
			}
			i++;
		}
	}

	if (check_hasoids && updatable)
		CheckPgClassInfo(stmt);
	SC_set_parse_status(stmt, parse ? STMT_PARSE_COMPLETE : STMT_PARSE_INCOMPLETE);
	for (i = 0; i < (int) irdflds->nfields; i++)
	{
		wfi = fi[i];
		wfi->flag &= ~FIELD_PARSING;
		if (0 != wfi->columntype || 0 != wfi->basetype)
			wfi->flag |= FIELD_PARSED_OK;
	}

	if (updatable)
	{
		if (stmt->ntab > 1)
			updatable = FALSE;
	}
	SC_set_updatable(stmt, updatable);
cleanup:
#undef	return
	if (!sqlsvr_check && STMT_PARSE_FATAL == SC_parsed_status(stmt))
	{
		SC_initialize_cols_info(stmt, FALSE, FALSE);
		parse = FALSE;
	}

	MYLOG(0, "laving parse=%d, parse_status=%d\n", parse, SC_parsed_status(stmt));
	return parse;
}

char
parse_statement(StatementClass *stmt, BOOL check_hasoids)
{
	return parse_the_statement(stmt, check_hasoids, FALSE);
}

char
parse_sqlsvr(StatementClass *stmt)
{
	return parse_the_statement(stmt, FALSE, TRUE);
}
