//____________________________________________________________
//  
//		PROGRAM:	C Preprocessor
//		MODULE:		sqe.cpp
//		DESCRIPTION:	SQL expression parser
//  
//  The contents of this file are subject to the Interbase Public
//  License Version 1.0 (the "License"); you may not use this file
//  except in compliance with the License. You may obtain a copy
//  of the License at http://www.Inprise.com/IPL.html
//  
//  Software distributed under the License is distributed on an
//  "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
//  or implied. See the License for the specific language governing
//  rights and limitations under the License.
//  
//  The Original Code was created by Inprise Corporation
//  and its predecessors. Portions created by Inprise Corporation are
//  Copyright (C) Inprise Corporation.
//  
//  All Rights Reserved.
//  Contributor(s): ______________________________________.
//  $Id: sqe.cpp,v 1.9 2003/03/03 08:36:03 brodsom Exp $
//  Revision 1.3  2000/11/16 15:54:29  fsg
//  Added new switch -verbose to gpre that will dump
//  parsed lines to stderr
//  
//  Fixed gpre bug in handling row names in WHERE clauses
//  that are reserved words now (DATE etc)
//  (this caused gpre to dump core when parsing tan.e)
//  
//  Fixed gpre bug in handling lower case table aliases
//  in WHERE clauses for sql dialect 2 and 3.
//  (cause a core dump in a test case from C.R. Zamana)
//  
//  TMN (Mike Nordell) 11.APR.2001 - Reduce compiler warnings
//  
//
//____________________________________________________________
//
//	$Id: sqe.cpp,v 1.9 2003/03/03 08:36:03 brodsom Exp $
//
#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include "../gpre/gpre.h"
#include "../gpre/parse.h"
#include "../gpre/cme_proto.h"
#include "../gpre/cmp_proto.h"
#include "../gpre/exp_proto.h"
#include "../gpre/gpre_proto.h"
#include "../gpre/hsh_proto.h"
#include "../gpre/gpre_meta.h"
#include "../gpre/msc_proto.h"
#include "../gpre/par_proto.h"
#include "../gpre/sqe_proto.h"
#include "../gpre/sql_proto.h"


extern "C" {


#define ERROR_LENGTH	256

struct scope {
	struct gpre_ctx *req_contexts;
	USHORT req_scope_level;		/* scope level for SQL subquery parsing */
	USHORT req_in_aggregate;	/* now processing value expr for aggr */
	USHORT req_in_select_list;	/* processing select list */
	USHORT req_in_where_clause;	/* processing where clause */
	USHORT req_in_having_clause;	/* processing having clause */
	USHORT req_in_order_by_clause;	/* processing order by clause */
	USHORT req_in_subselect;	/* processing a subselect clause */
};

extern ACT cur_routine;
extern struct tok prior_token;

static BOOLEAN compare_expr(GPRE_NOD, GPRE_NOD);
static GPRE_NOD copy_fields(GPRE_NOD, MAP);
static GPRE_NOD explode_asterisk(GPRE_NOD, int, RSE);
static GPRE_NOD explode_asterisk_all(GPRE_NOD, int, RSE, BOOLEAN);
static GPRE_FLD get_ref(GPRE_NOD);
static GPRE_NOD implicit_any(GPRE_REQ, GPRE_NOD, enum nod_t, enum nod_t);
static GPRE_NOD merge(GPRE_NOD, GPRE_NOD);
static GPRE_NOD merge_fields(GPRE_NOD, GPRE_NOD, int, BOOLEAN);
static GPRE_NOD negate(GPRE_NOD);
static void pair(GPRE_NOD, GPRE_NOD);
static GPRE_CTX par_alias_list(GPRE_REQ, GPRE_NOD);
static GPRE_CTX par_alias(GPRE_REQ, TEXT *);
static GPRE_NOD par_and(GPRE_REQ, USHORT *);
static GPRE_REL par_base_table(GPRE_REQ, GPRE_REL, TEXT *);
static GPRE_NOD par_collate(GPRE_REQ, GPRE_NOD);
static GPRE_NOD par_in(GPRE_REQ, GPRE_NOD);
static GPRE_CTX par_joined_relation(GPRE_REQ, GPRE_CTX);
static GPRE_CTX par_join_clause(GPRE_REQ, GPRE_CTX);
static NOD_T par_join_type(void);
static GPRE_NOD par_multiply(GPRE_REQ, BOOLEAN, USHORT *, USHORT *);
static GPRE_NOD par_not(GPRE_REQ, USHORT *);
static void par_order(GPRE_REQ, RSE, SSHORT, USHORT);
static GPRE_NOD par_plan(GPRE_REQ);
static GPRE_NOD par_plan_item(GPRE_REQ, BOOLEAN);
static GPRE_NOD par_primitive_value(GPRE_REQ, BOOLEAN, USHORT *, USHORT *);
static GPRE_NOD par_relational(GPRE_REQ, USHORT *);
static RSE par_rse(GPRE_REQ, GPRE_NOD, BOOLEAN);
static RSE par_select(GPRE_REQ, RSE);
static GPRE_NOD par_stat(GPRE_REQ);
static GPRE_NOD par_subscript(GPRE_REQ);
static void par_terminating_parens(USHORT *, USHORT *);
static GPRE_NOD par_udf(GPRE_REQ);
static GPRE_NOD par_udf_or_field(GPRE_REQ, BOOLEAN);
static GPRE_NOD par_udf_or_field_with_collate(GPRE_REQ, BOOLEAN);
static GPRE_NOD post_fields(GPRE_NOD, MAP);
static GPRE_NOD post_map(GPRE_NOD, MAP);
static GPRE_NOD post_select_list(GPRE_NOD, MAP);
static void pop_scope(GPRE_REQ, struct scope *);
static void push_scope(GPRE_REQ, struct scope *);
static GPRE_FLD resolve(GPRE_NOD, GPRE_CTX, GPRE_CTX *, ACT *);
static GPRE_CTX resolve_asterisk(TOK, RSE);
static void set_ref(GPRE_NOD, GPRE_FLD);
static char *upcase_string(char *);
static BOOLEAN validate_references(GPRE_NOD, GPRE_NOD);
static void dialect1_bad_type(USHORT);




typedef struct ops {
	enum nod_t rel_op;
	enum kwwords rel_kw;
	enum nod_t rel_negation;
} OPS;

static OPS rel_ops[] = {
	{ nod_eq, KW_EQ, nod_ne },
	{ nod_eq, KW_EQUALS, nod_ne },
	{ nod_ne, KW_NE, nod_eq },
	{ nod_gt, KW_GT, nod_le },
	{ nod_ge, KW_GE, nod_lt },
	{ nod_le, KW_LE, nod_gt },
	{ nod_lt, KW_LT, nod_ge },
	{ nod_containing, KW_CONTAINING, nod_any },
	{ nod_starting, KW_STARTING, nod_any },
	{ nod_matches, KW_MATCHES, nod_any },
	{ nod_any, KW_none, nod_any },
	{ nod_ansi_any, KW_none, nod_ansi_any },
	{ nod_ansi_all, KW_none, nod_ansi_all }};
#ifdef NOT_USED_OR_REPLACED
static OPS scalar_stat_ops[] = {
	{ nod_count, KW_COUNT, nod_any },
	{ nod_max, KW_MAX, nod_any },
	{ nod_min, KW_MIN, nod_any },
	{ nod_total, KW_TOTAL, nod_any },
	{ nod_total, KW_SUM, nod_any },
	{ nod_average, KW_AVERAGE, nod_any },
	{ nod_via, KW_none, nod_any}};
#endif
static OPS stat_ops[] = {
	{ nod_agg_count, KW_COUNT, nod_any },
	{ nod_agg_max, KW_MAX, nod_any },
	{ nod_agg_min, KW_MIN, nod_any },
	{ nod_agg_total, KW_TOTAL, nod_any },
	{ nod_agg_total, KW_SUM, nod_any },
	{ nod_agg_average, KW_AVERAGE, nod_any },
	{ nod_any, KW_none, nod_any },
	{ nod_ansi_any, KW_none, nod_ansi_any },
	{ nod_ansi_all, KW_none, nod_ansi_all }};

static NOD_T relationals[] = {
	nod_eq, nod_ne, nod_gt, nod_ge, nod_le, nod_lt, nod_containing,
	nod_starting, nod_matches, nod_any, nod_missing, nod_between, nod_like,
	nod_and, nod_or, nod_not, nod_ansi_any, nod_ansi_all, (NOD_T) 0
};


//____________________________________________________________
//  
//		Parse an OR boolean expression.
//  

GPRE_NOD SQE_boolean( GPRE_REQ request, USHORT * paren_count)
{
	GPRE_NOD expr1;
	USHORT local_count;

	assert_IS_REQ(request);

	if (!paren_count) {
		local_count = 0;
		paren_count = &local_count;
	}

	expr1 = par_and(request, paren_count);

	if (!MATCH(KW_OR) && !MATCH(KW_OR1)) {
		par_terminating_parens(paren_count, &local_count);
		return expr1;
	}

	expr1 = MSC_binary(nod_or, expr1, SQE_boolean(request, paren_count));
	par_terminating_parens(paren_count, &local_count);

	return expr1;
}


//____________________________________________________________
//  
//		Parse a reference to a relation name
//		and generate a context for it.
//  

GPRE_CTX SQE_context(GPRE_REQ request)
{
	SYM symbol;
	GPRE_CTX context, conflict;
	GPRE_PRC procedure;
	USHORT local_count;
	SCHAR r_name[NAME_SIZE + 1], db_name[NAME_SIZE + 1],
		owner_name[NAME_SIZE + 1];
	SCHAR s[ERROR_LENGTH];
	GPRE_FLD field;
	GPRE_NOD *input;

	assert_IS_REQ(request);

	context = MAKE_CONTEXT(request);
	SQL_relation_name(r_name, db_name, owner_name);

	if (!(context->ctx_relation =
		  SQL_relation(request, r_name, db_name, owner_name, FALSE))) {
		/* check for a procedure */
		if (procedure = context->ctx_procedure =
			SQL_procedure(request, r_name, db_name, owner_name, FALSE)) {
			if (procedure->prc_inputs) {
				if (!MATCH(KW_LEFT_PAREN))
					SYNTAX_ERROR("( <procedure input parameters> )");
				/* parse input references */
				context->ctx_prc_inputs = SQE_list(SQE_value, request, FALSE);
				local_count = 1;
				par_terminating_parens(&local_count, &local_count);
				if (procedure->prc_in_count !=
					context->ctx_prc_inputs->
					nod_count)
PAR_error("count of input values doesn't match count of parameters");
				for (input = context->ctx_prc_inputs->nod_arg, field =
					 procedure->prc_inputs; field;
					 input++, field =
					 field->fld_next) SQE_post_field(*input, field);
			}
		}
		else {
			if (owner_name[0])
				sprintf(s, "table %s.%s not defined", owner_name, r_name);
			else
				sprintf(s, "table %s not defined", r_name);
			PAR_error(s);
		}

	}

//  If the next token is recognized as a keyword, it can't be a SQL "alias".
//  It may, however, be an "end of line" token.  If so, trade it in on the
//  next "real" token. 

	if ((symbol = token.tok_symbol) && symbol->sym_type == SYM_keyword) {
		if (!token.tok_length)
			CPR_token();
		return context;
	}

//  we have what we assume to be an alias; check to make sure that 
//  it does not conflict with any relation, procedure or context names
//  at the same scoping level in this query 

	for (conflict = request->req_contexts; conflict;
		 conflict = conflict->ctx_next) if ((symbol = conflict->ctx_symbol)
											&& (symbol->sym_type ==
												SYM_relation
												|| symbol->sym_type ==
												SYM_context
												|| symbol->sym_type ==
												SYM_procedure)
											&&
											(!strcmp
											 (symbol->sym_string,
											  token.tok_string))
											&& (conflict->ctx_scope_level ==
												request->
												req_scope_level)) break;

	if (conflict) {
		SCHAR *error_type;
		if (symbol->sym_type == SYM_relation)
			error_type = "table";
		else if (symbol->sym_type == SYM_procedure)
			error_type = "procedure";
		else
			error_type = "context";

		sprintf(s, "context %s conflicts with a %s in the same statement",
				token.tok_string, error_type);
		PAR_error(s);
	}

	symbol = MSC_symbol(SYM_context, token.tok_string, token.tok_length, 0);
	symbol->sym_object = context;
	context->ctx_symbol = symbol;
	context->ctx_alias = symbol->sym_name;
	HSH_insert(symbol);

	ADVANCE_TOKEN;

	return context;
}


//____________________________________________________________
//  
//		Parse an item is a select list.  This is particularly nasty
//		since neither the relations nor context variables have been
//		processed yet.  So, rather than generating a simple field
//		reference, make up a more or less dummy block containing
//		a pointer to a field system and possible a qualifier symbol.
//		this will be turned into a reference later.
//  

GPRE_NOD SQE_field(GPRE_REQ request, BOOLEAN aster_ok)
{
	GPRE_NOD node, tail;
	SYM symbol, temp_symbol;
	GPRE_CTX context;
	REF reference;
	GPRE_REL relation;
	GPRE_PRC procedure;
	TOK f_token;
	LLS upper_dim, lower_dim;
	int count = 0;
	GPRE_REQ slice_req;
	SLC slice;
	ACT action;
	TEXT s[ERROR_LENGTH];
	struct tok hold_token;
	slc::slc_repeat * tail_ptr;

	assert_IS_REQ(request);

	upper_dim = lower_dim = NULL;
	hold_token.tok_type = tok_t(0);

	if (aster_ok && MATCH(KW_ASTERISK)) {
		node = MAKE_NODE(nod_asterisk, 1);
		return node;
	}

//  if the token isn't an identifier, complain 

	SQL_resolve_identifier("<column name>", s);

//  For domains we can't be resolving tokens to field names
//  in the CHECK constraint. 

	if (request &&
		request->req_type == REQ_ddl &&
		(action = request->req_actions) &&
		(action->act_type == ACT_create_domain ||
		 action->act_type == ACT_alter_domain)) {
		sprintf(s, "Illegal use of identifier: %s in domain constraint",
				token.tok_string);
		PAR_error(s);
	}


//  Note: We *always* want to make a defered name block - to handle
//  scoping of alias names in subselects properly, when we haven't
//  seen the list of relations (& aliases for them).  This occurs
//  during the select list, but by the time we see the having, group,
//  order, or where we do know all aliases and should be able to
//  precisely match.
//  Note that the case of request == NULL should never
//  occur, and request->req_contexts == NULL should only
//  occur for the very first select list in a request.
//  1994-October-03 David Schnepper 
//  

//  if the request is null, make a defered name block 

	if (!request || !request->req_contexts || request->req_in_select_list) {
		node = MAKE_NODE(nod_defered, 3);
		node->nod_count = 0;
		f_token = (TOK) ALLOC(TOK_LEN);
		node->nod_arg[0] = (GPRE_NOD) f_token;
		f_token->tok_length = token.tok_length;
		SQL_resolve_identifier("<identifier>", f_token->tok_string);
		CPR_token();

		if (MATCH(KW_DOT)) {
			if ((int) token.tok_keyword == (int) KW_ASTERISK) {
				if (aster_ok)
					node->nod_type = nod_asterisk;
				else
					PAR_error("* not allowed");
			}
			else {
				node->nod_arg[1] = node->nod_arg[0];
				f_token = (TOK) ALLOC(TOK_LEN);
				node->nod_arg[0] = (GPRE_NOD) f_token;
				f_token->tok_length = token.tok_length;
				SQL_resolve_identifier("<identifier>", f_token->tok_string);
			}
			CPR_token();
		}
		if (MATCH(KW_L_BRCKET)) {
			/* We have a complete array or an array slice here */

			if (!MATCH(KW_R_BRCKET)) {
				slice_req = MAKE_REQUEST(REQ_slice);
				do {
					count++;
					tail = par_subscript(slice_req);
					PUSH(tail, &lower_dim);
					if (MATCH(KW_COLON)) {
						/* if (!MATCH (KW_DOT))
						   SYNTAX_ERROR ("<period>"); */
						tail = par_subscript(slice_req);
						PUSH(tail, &upper_dim);
					}
					else
						PUSH(tail, &upper_dim);
				} while (MATCH(KW_COMMA));

				if (!MATCH(KW_R_BRCKET))
					SYNTAX_ERROR("<right bracket>");
				slice_req->req_slice = slice = (SLC) ALLOC(SLC_LEN(count));
				tail_ptr = &slice->slc_rpt[count];
				slice->slc_dimensions = count;
				slice->slc_parent_request = request;
				while (lower_dim) {
					--tail_ptr;
					tail_ptr->slc_lower = POP(&lower_dim);
					tail_ptr->slc_upper = POP(&upper_dim);
				}
				node->nod_arg[2] = (GPRE_NOD) slice_req;

				/* added this to assign the correct nod_count 
				   The nod type is converted to nod_field in SQE_resolve()
				   The nod_count is check to confirm if the array slice
				   has been initialized in cmd.c
				 */
				node->nod_count = 3;
			}
			else {
				slice_req = (GPRE_REQ) ALLOC(REQ_LEN);
				slice_req->req_type = REQ_slice;
				node->nod_arg[2] = (GPRE_NOD) slice_req;
			}
		}

		return node;
	}

	reference = (REF) ALLOC(REF_LEN);
	node = MSC_unary(nod_field, (GPRE_NOD) reference);

	if (symbol = token.tok_symbol) {
		/* if there is a homonym which is a context, use the context;
		   otherwise we may match with a relation or procedure which 
		   is not in the request, resulting in a bogus error */

		if (symbol->sym_type != SYM_field) {
			for (temp_symbol = symbol; temp_symbol;
				 temp_symbol = temp_symbol->sym_homonym) {
				if (temp_symbol->sym_type == SYM_context) {
					symbol = temp_symbol;
					break;
				}
				else if (temp_symbol->sym_type == SYM_relation) {
					symbol = temp_symbol;
					continue;
				}
				else if (temp_symbol->sym_type == SYM_procedure) {
					if (symbol->sym_type == SYM_relation)
						continue;
					else {
						symbol = temp_symbol;
						continue;
					}
				}
			}

			if (symbol->sym_type == SYM_context) {
				context = symbol->sym_object;
				CPR_token();
				if (!MATCH(KW_DOT))
					SYNTAX_ERROR("<period> in qualified column");
				if (context->ctx_request != request)
					PAR_error("context not part of this request");
				SQL_resolve_identifier("<Column Name>", s);
				if (!
					(reference->ref_field =
					 MET_context_field(context, token.tok_string))) {
					sprintf(s, "column \"%s\" not in context",
							token.tok_string);
					PAR_error(s);
				}
				if (SQL_DIALECT_V5 == sw_sql_dialect) {
					USHORT field_dtype;
					field_dtype = reference->ref_field->fld_dtype;
					if ((dtype_sql_date == field_dtype) ||
						(dtype_sql_time == field_dtype) ||
						(dtype_int64 == field_dtype)) {
						dialect1_bad_type(field_dtype);
					}
				}
				reference->ref_context = context;
				CPR_token();
				return node;
			}
			else if (symbol->sym_type == SYM_relation) {
				relation = (GPRE_REL) symbol->sym_object;
				if (relation->rel_database != request->req_database)
					PAR_error("table not in appropriate database");

				CPR_token();

				/* if we do not see a KW_DOT, perhaps what we think is a relation
				   ** is actually a column with the same name as the relation in
				   ** the HSH table.  if so...skip down to the end of the routine
				   ** and find out if we do have such a column. but first, since
				   ** we just did a CPR_token, we have to move prior_token into
				   ** current token, and hold the current token for later.
				 */

				if (!MATCH(KW_DOT)) {
					hold_token = token;
					token = prior_token;
					token.tok_symbol = 0;
				}
				else {
		/** We've got the column name. resolve it **/
					SQL_resolve_identifier("<Columnn Name>", s);
					for (context = request->req_contexts; context;
						 context = context->ctx_next)
							if (context->ctx_relation == relation &&
								(reference->ref_field =
								 MET_field(context->ctx_relation,
										   token.tok_string))) {
							if (SQL_DIALECT_V5 == sw_sql_dialect) {
								USHORT field_dtype;
								field_dtype = reference->ref_field->fld_dtype;
								if ((dtype_sql_date == field_dtype) ||
									(dtype_sql_time == field_dtype) ||
									(dtype_int64 == field_dtype)) {
									dialect1_bad_type(field_dtype);
								}
							}
							reference->ref_context = context;
							CPR_token();
							if (reference->ref_field->fld_array_info) {
								node =
									EXP_array(request, reference->ref_field,
											  TRUE, TRUE);
								node->nod_arg[0] = (GPRE_NOD) reference;
							}
							return node;
						}

					sprintf(s, "column \"%s\" not in context",
							token.tok_string);
					PAR_error(s);
				}
			}
			else if (symbol->sym_type == SYM_procedure) {
				procedure = (GPRE_PRC) symbol->sym_object;
				if (procedure->prc_database != request->req_database)
					PAR_error("procedure not in appropriate database");
				CPR_token();
				if (!MATCH(KW_DOT))
					SYNTAX_ERROR("<period> in qualified column");
				SQL_resolve_identifier("<Column Name>", s);
				for (context = request->req_contexts; context;
					 context = context->ctx_next)
						if (context->ctx_procedure == procedure &&
							(reference->ref_field =
							 MET_context_field(context, token.tok_string))) {
						if (SQL_DIALECT_V5 == sw_sql_dialect) {
							USHORT field_dtype;
							field_dtype = reference->ref_field->fld_dtype;
							if ((dtype_sql_date == field_dtype) ||
								(dtype_sql_time == field_dtype) ||
								(dtype_int64 == field_dtype)) {
								dialect1_bad_type(field_dtype);
							}
						}
						reference->ref_context = context;
						if (reference->ref_field->fld_array_info) {
							node =
								EXP_array(request, reference->ref_field, TRUE,
										  TRUE);
							node->nod_arg[0] = (GPRE_NOD) reference;
						}
						CPR_token();
						return node;
					}

				sprintf(s, "column \"%s\" not in context", token.tok_string);
				PAR_error(s);
			}
		}
	}

//  Hmmm.  So it wasn't a qualified field.  Try any field. 

	SQL_resolve_identifier("<Column Name>", s);
	for (context = request->req_contexts; context;
		 context = context->ctx_next) if (reference->ref_field =
										  MET_context_field(context,
															token.
															tok_string)) {
			if (SQL_DIALECT_V5 == sw_sql_dialect) {
				USHORT field_dtype;
				field_dtype = reference->ref_field->fld_dtype;
				if ((dtype_sql_date == field_dtype) ||
					(dtype_sql_time == field_dtype) ||
					(dtype_int64 == field_dtype)) {
					dialect1_bad_type(field_dtype);
				}
			}
			reference->ref_context = context;

			/* if we skipped down from the SYM_relation case above, we need to
			   ** switch token and prior_token back to their original values to
			   ** continue.
			 */
			if (hold_token.tok_type != 0) {
				prior_token = token;
				token = hold_token;
			}
			else
				CPR_token();
			if (reference->ref_field->fld_array_info) {
				node = EXP_array(request, reference->ref_field, TRUE, TRUE);
				node->nod_arg[0] = (GPRE_NOD) reference;
			}
			if (request->req_map)
				return post_map(node, request->req_map);
			return node;
		}

	SYNTAX_ERROR("<column name>");
	return NULL;				/* silence compiler */
}


//____________________________________________________________
//  
//		Parse a list of "things", separated by commas.  Return the
//		whole mess in a list node.
//  

GPRE_NOD SQE_list(pfn_SQE_list_cb routine, GPRE_REQ request, BOOLEAN aster_ok)
{
	LLS stack;
	GPRE_NOD list, *ptr;
	int count;

	assert_IS_REQ(request);

	stack = NULL;
	count = 0;

	do {
		count++;
		PUSH((*routine) (request, aster_ok, 0, 0), &stack);
	}
	while (MATCH(KW_COMMA));

	list = MAKE_NODE(nod_list, (SSHORT) count);
	ptr = &list->nod_arg[count];

	while (stack)
		*--ptr = (GPRE_NOD) POP(&stack);

	return list;
}


//____________________________________________________________
//  
//		Parse procedure input parameters which are constants or
//		host variable reference and, perhaps, a missing
//		flag reference, which may be prefaced by the noiseword,
//       "INDICATOR".
//  

REF SQE_parameter(GPRE_REQ request, BOOLEAN aster_ok)
{
	REF reference;
	SYM symbol;
	SCHAR *string, *s;
	int sign;

	assert_IS_REQ(request);

	if (token.tok_type == tok_number) {
		reference = (REF) ALLOC(REF_LEN);
		string = (TEXT *) ALLOC(token.tok_length + 1);
		COPY(token.tok_string, token.tok_length, string);
		reference->ref_value = string;
		reference->ref_flags |= REF_literal;
		CPR_token();
		return reference;
	}
	if ((QUOTED(token.tok_type) && sw_sql_dialect == 1) ||
		SINGLE_QUOTED(token.tok_type)) {
	/** 
    Since we have stripped the quotes, it is time now to put it back
    so that the host language will interpret it correctly as a string 
    literal.
    ***/
		reference = (REF) ALLOC(REF_LEN);
		string = (TEXT *) ALLOC(token.tok_length + 3);
		string[0] = '\"';
		COPY(token.tok_string, token.tok_length, string + 1);
		string[token.tok_length + 1] = '\"';
		string[token.tok_length + 2] = 0;
		reference->ref_value = string;
		reference->ref_flags |= REF_literal;
		CPR_token();
		return reference;
	}
	if (KEYWORD(KW_PLUS) || KEYWORD(KW_MINUS)) {
		if (KEYWORD(KW_MINUS))
			sign = 1;
		else
			sign = 0;
		CPR_token();
		if (token.tok_type != tok_number)
			SYNTAX_ERROR("<host variable> or <constant>");
		reference = (REF) ALLOC(REF_LEN);
		s = string = (TEXT *) ALLOC(token.tok_length + 1 + sign);
		if (sign)
			*s++ = '-';
		COPY(token.tok_string, token.tok_length, s);
		reference->ref_value = string;
		reference->ref_flags |= REF_literal;
		CPR_token();
		return reference;
	}

	if (!MATCH(KW_COLON))
		SYNTAX_ERROR("<host variable> or <constant>");

	if (token.tok_type != tok_ident)
		SYNTAX_ERROR("<host variable> or <constant>");

	reference = (REF) ALLOC(REF_LEN);

	for (symbol = token.tok_symbol; symbol; symbol = symbol->sym_homonym)
		if (symbol->sym_type == SYM_variable) {
			reference->ref_field = (GPRE_FLD) symbol->sym_object;
			break;
		}

	reference->ref_value = PAR_native_value(FALSE, FALSE);

	MATCH(KW_INDICATOR);

	if (MATCH(KW_COLON))
		reference->ref_null_value = PAR_native_value(FALSE, FALSE);

	return reference;
}


//____________________________________________________________
//  
//		Given an expression node, for values that don't have a 
//		field, post the given field.
//		Procedure called from EXP_array to post the "subscript field".
//  

void SQE_post_field( GPRE_NOD input, GPRE_FLD field)
{
	GPRE_NOD *ptr, *end;
	REF reference;
	GPRE_NOD node;
	MEL element;

	if (!input || !field)
		return;

	assert_IS_NOD(input);

	switch (input->nod_type) {
	case nod_value:
		{
			reference = (REF) input->nod_arg[0];
			if (!reference->ref_field) {
				/* We're guessing that hostvar reference->ref_value matches
				 * the datatype of field->fld_dtype
				 */
				reference->ref_field = field;
			}
			return;
		}

	case nod_field:
	case nod_literal:
	case nod_defered:
	case nod_array:
		return;

	case nod_map_ref:
		element = (MEL) input->nod_arg[0];
		node = element->mel_expr;
		SQE_post_field(node, field);
		return;

	default:
		for (ptr = input->nod_arg, end = ptr + input->nod_count;
			 ptr < end; ptr++)
			SQE_post_field(*ptr, field);
		return;
	}
}


//____________________________________________________________
//  
//		Post an external reference to a request.  If the expression
//		in question already exists, re-use it.  If there isn't a field,
//		generate a pseudo-field to hold datatype information.  If there
//		isn't a context, well, there isn't a context.
//  

REF SQE_post_reference(GPRE_REQ request, GPRE_FLD field, GPRE_CTX context, GPRE_NOD node)
{
	REF reference;

	assert_IS_REQ(request);
	assert_IS_NOD(node);

//  If the beast is already a field reference, get component parts 

	if (node && node->nod_type == nod_field) {
		reference = (REF) node->nod_arg[0];
		field = reference->ref_field;
		context = reference->ref_context;
	}

//  See if there is already a reference to this guy.  If so, return it. 

	for (reference = request->req_references; reference;
		 reference = reference->ref_next)
			if (
				(reference->ref_expr
				 && compare_expr(node, reference->ref_expr))
				|| (!reference->ref_expr && field == reference->ref_field
					&& context == reference->ref_context))
			return reference;

//  If there isn't a field given, make one up 

	if (!field) {
		field = (GPRE_FLD) ALLOC(FLD_LEN);
		CME_get_dtype(node, field);
		if (field->fld_dtype && (field->fld_dtype <= dtype_any_text))
			field->fld_flags |= FLD_text;
	}

//  No reference -- make one 

	reference = (REF) ALLOC(REF_LEN);
	reference->ref_context = context;
	reference->ref_field = field;
	reference->ref_expr = node;

	reference->ref_next = request->req_references;
	request->req_references = reference;

	return reference;
}


//____________________________________________________________
//  
//		Resolve a kludgy field node build by par_s_item into
//		a bona fide field reference.  If a request
//		is supplied, resolve the reference to any context available
//		in the request.  Otherwise resolve the field to a given
//		record selection expression.
//  
//		If the expression contains a global aggregate, return TRUE,
//		otherwise FALSE.
//  

BOOLEAN SQE_resolve(GPRE_NOD node, GPRE_REQ request, RSE rse)
{
	REF reference;
	GPRE_CTX context;
	GPRE_FLD field;
	GPRE_NOD *ptr, *end, node_arg;
	TOK f_token, q_token;
	SSHORT i;
	BOOLEAN result;
	SCHAR s[ERROR_LENGTH];
	ACT slice_action = 0;

	assert_IS_REQ(request);
	assert_IS_NOD(node);

	result = FALSE;

	switch (node->nod_type) {
	case nod_plus:
	case nod_minus:
	case nod_times:
	case nod_divide:
	case nod_negate:
	case nod_upcase:
	case nod_concatenate:
	case nod_cast:
		ptr = node->nod_arg;
		end = ptr + node->nod_count;
		for (; ptr < end; ptr++)
			result |= SQE_resolve(*ptr, request, rse);
		return result;

	case nod_agg_max:
	case nod_agg_min:
	case nod_agg_total:
	case nod_agg_average:
	case nod_agg_count:
		if (node->nod_arg[0]) {
			SQE_resolve(node->nod_arg[0], request, rse);
			node_arg = node->nod_arg[0];
			reference = (REF) node_arg->nod_arg[0];
			if (node_arg->nod_type == nod_field && reference &&
				reference->ref_field && reference->ref_field->fld_array_info)
				PAR_error
					("Array columns not permitted in aggregate functions");
		}
		return TRUE;

	case nod_udf:
		if (node->nod_arg[0]) {
			ptr = node->nod_arg[0]->nod_arg;
			end = ptr + node->nod_arg[0]->nod_count;
			for (; ptr < end; ptr++)
				result |= SQE_resolve(*ptr, request, rse);
		}
		return result;

	case nod_gen_id:
		return SQE_resolve(node->nod_arg[0], request, rse);

// ** Begin date/time/timestamp support *
	case nod_extract:
		result |= SQE_resolve(node->nod_arg[1], request, rse);
		return result;
// ** End date/time/timestamp support *

	case nod_defered:
		break;

	default:
		return FALSE;
	}

	f_token = (TOK) node->nod_arg[0];
	f_token->tok_symbol = HSH_lookup(f_token->tok_string);
	if (q_token = (TOK) node->nod_arg[1])
		q_token->tok_symbol = HSH_lookup(q_token->tok_string);

	field = NULL;

	if (request)
		for (context = request->req_contexts; context;
			 context = context->ctx_next) {
			if (!context->ctx_stream
				&& (field = resolve(node, context, 0, &slice_action))) break;
		}
	else
		for (i = 0; i < rse->rse_count; i++) {
			if (field =
				resolve(node, rse->rse_context[i], &context,
						&slice_action)) break;
		}

	if (!field) {
		if (q_token)
			sprintf(s, "column \"%s.%s\" cannot be resolved",
					q_token->tok_string, f_token->tok_string);
		else
			sprintf(s, "column \"%s\" cannot be resolved",
					f_token->tok_string);
		PAR_error(s);
	}

//  Make sure that a dialect-1 program isn't trying to select a
//  dialect-3-only field type. 
	if ((SQL_DIALECT_V5 == sw_sql_dialect) &&
		((dtype_sql_date == field->fld_dtype) ||
		 (dtype_sql_time == field->fld_dtype) ||
		 (dtype_int64 == field->fld_dtype)))
			dialect1_bad_type(field->fld_dtype);

	reference = (REF) ALLOC(REF_LEN);
	reference->ref_field = field;
	reference->ref_context = context;
	reference->ref_slice = (SLC) slice_action;

//  donot reinit if this is a nod_deffered type 
	if (node->nod_type != nod_defered)
		node->nod_count = 0;


	node->nod_type = nod_field;
	node->nod_arg[0] = (GPRE_NOD) reference;

	return FALSE;
}


//____________________________________________________________
//  
//		Parse a SELECT (sans keyword) expression.
//  

RSE SQE_select(GPRE_REQ request, USHORT view_flag)
{
	RSE select, rse1, rse2;
	GPRE_NOD node;
	LLS context_stack;
	GPRE_CTX context;
	MAP map, old_map;
	BOOLEAN have_union;

	assert_IS_REQ(request);

	context_stack = NULL;
	select = rse1 = rse2 = NULL;
	old_map = request->req_map;
	have_union = FALSE;

//  Get components of union.  Most likely there isn't one, so this is
//  probably wasted work.  

	select = rse1 = par_select(request, NULL);

//  "Look for ... the UNION label ... " 
	while (MATCH(KW_UNION)) {
		BOOLEAN union_all;

		have_union = TRUE;
		union_all = FALSE;
		if (MATCH(KW_ALL))
			union_all = TRUE;
		if (!MATCH(KW_SELECT))
			SYNTAX_ERROR("SELECT");

		PUSH((GPRE_NOD) request->req_contexts, &context_stack);
		request->req_contexts = NULL;
		request->req_map = NULL;
		rse2 = par_select(request, rse1);

		/* We've got a bona fide union.  Make a union node to hold sub-rse
		   and then a new rse to point to it. */

		select = (RSE) ALLOC(RSE_LEN(1));
		select->rse_context[0] = context = MAKE_CONTEXT(request);
		select->rse_union = node = MAKE_NODE(nod_union, 2);
		node->nod_arg[0] = (GPRE_NOD) rse1;
		node->nod_arg[1] = (GPRE_NOD) rse2;

		rse1->rse_map = map = (MAP) ALLOC(sizeof(struct map));
		map->map_context = context;
		select->rse_fields = post_select_list(rse1->rse_fields, map);

		rse2->rse_map = map = (MAP) ALLOC(sizeof(struct map));
		map->map_context = context;
		(void) post_select_list(rse2->rse_fields, map);

		select->rse_into = rse1->rse_into;
		if (!union_all)
			select->rse_reduced = select->rse_fields;

		/* Result of this UNION might be the left side of the NEXT UNION */
		rse1 = select;
	}

//  Restore the context lists that were forgotten 
//  <context> holds the most recently allocated context, which is
//  already linked into the request block 

	while (context_stack) {
		while (context->ctx_next)
			context = context->ctx_next;
		context->ctx_next = (GPRE_CTX) POP(&context_stack);
	}

//  Pick up any dangling ORDER clause 

	++request->req_in_order_by_clause;
	par_order(request, select, have_union, view_flag);
	--request->req_in_order_by_clause;
	request->req_map = old_map;

	return select;
}


//____________________________________________________________
//  
//		Parse either of the low precedence operators + and -.
//  

GPRE_NOD SQE_value(GPRE_REQ request,
			  BOOLEAN aster_ok, USHORT * paren_count, USHORT * bool_flag)
{
	GPRE_NOD node, arg;
	nod_t operator_;
	USHORT local_count, local_flag;

	assert_IS_REQ(request);

	if (!paren_count) {
		local_count = 0;
		paren_count = &local_count;
	}
	if (!bool_flag) {
		local_flag = FALSE;
		bool_flag = &local_flag;
	}

	MATCH(KW_PLUS);
	node = par_multiply(request, aster_ok, paren_count, bool_flag);
	assert_IS_NOD(node);
	if (node->nod_type == nod_asterisk) {
		par_terminating_parens(paren_count, &local_count);
		return node;
	}

	while (TRUE) {
		if (MATCH(KW_PLUS))
			operator_ = nod_plus;
		else if (MATCH(KW_MINUS))
			operator_ = nod_minus;
		else if (MATCH(KW_OR1))
			operator_ = nod_concatenate;
		else {
			par_terminating_parens(paren_count, &local_count);
			return node;
		}
		arg = node;
		node =
			MSC_binary(operator_, arg,
					   par_multiply(request, FALSE, paren_count, bool_flag));
	}
}


//____________________________________________________________
//  
//		Parse either a literal NULL expression or a value
//		expression.
//  

GPRE_NOD SQE_value_or_null(GPRE_REQ request,
					  BOOLEAN aster_ok,
					  USHORT * paren_count, USHORT * bool_flag)
{
	if (MATCH(KW_NULL)) {
		return MAKE_NODE(nod_null, 0);
	}
	else
		return SQE_value(request, aster_ok, paren_count, bool_flag);
}


//____________________________________________________________
//  
//		Parse host variable reference and, perhaps, a missing
//		flag reference, which may be prefaced by the noiseword,
//       "INDICATOR".
//  

REF SQE_variable(GPRE_REQ request, BOOLEAN aster_ok)
{
	REF reference;
	SYM symbol;

	assert_IS_REQ(request);

	if (!MATCH(KW_COLON))
		SYNTAX_ERROR("<colon>");

	if (QUOTED(token.tok_type))
		SYNTAX_ERROR("<host variable>");

	reference = (REF) ALLOC(REF_LEN);

	for (symbol = token.tok_symbol; symbol; symbol = symbol->sym_homonym)
		if (symbol->sym_type == SYM_variable) {
			reference->ref_field = (GPRE_FLD) symbol->sym_object;
			break;
		}

	reference->ref_value = PAR_native_value(FALSE, FALSE);

	MATCH(KW_INDICATOR);

	if (MATCH(KW_COLON))
		reference->ref_null_value = PAR_native_value(FALSE, FALSE);

	return reference;
}


//____________________________________________________________
//  
//		Compare two expressions symbollically.  If they're the same,
//		return TRUE, otherwise FALSE.
//  

static BOOLEAN compare_expr( GPRE_NOD node1, GPRE_NOD node2)
{
	REF ref1, ref2;

	assert_IS_NOD(node1);
	assert_IS_NOD(node2);

	if (node1->nod_type != node2->nod_type)
		return FALSE;

	switch (node1->nod_type) {
	case nod_field:
		ref1 = (REF) node1->nod_arg[0];
		ref2 = (REF) node2->nod_arg[0];
		if (ref1->ref_context != ref2->ref_context ||
			ref1->ref_field != ref2->ref_field ||
			ref1->ref_master != ref2->ref_master) return FALSE;
		return TRUE;

	case nod_map_ref:
		if (node1->nod_arg[0] != node2->nod_arg[0])
			return FALSE;
		return TRUE;

	case nod_udf:
	case nod_gen_id:
		if (node1->nod_arg[0] != node2->nod_arg[0] ||
			node1->nod_arg[1] != node2->nod_arg[1])
			return FALSE;
		return TRUE;

	default:
		return FALSE;
	}
}


//____________________________________________________________
//  
//		Copy a field list for a SELECT against an artificial context.
//  

static GPRE_NOD copy_fields( GPRE_NOD fields, MAP map)
{
	GPRE_NOD list;
	USHORT i;

	assert_IS_NOD(fields);

	list = MAKE_NODE(nod_list, fields->nod_count);

	for (i = 0; i < fields->nod_count; i++)
		list->nod_arg[i] = post_fields(fields->nod_arg[i], map);

	return list;
}


//____________________________________________________________
//  
//		Expand an '*' in a field list to the corresponding fields.
//  

static GPRE_NOD explode_asterisk( GPRE_NOD fields, int n, RSE rse)
{
	GPRE_CTX context;
	GPRE_NOD node;
	TOK q_token;
	TEXT s[ERROR_LENGTH];

	assert_IS_NOD(fields);

	node = fields->nod_arg[n];
	if (q_token = (TOK) node->nod_arg[0]) {
		/* expand for single relation */

		if (context = resolve_asterisk(q_token, rse))
			fields = merge_fields(fields, MET_fields(context), n, TRUE);
		else {
			sprintf(s, "columns \"%s.*\" cannot be resolved",
					q_token->tok_string);
			PAR_error(s);
		}
	}
	else {
		/* expand for all relations in context list */

		fields = explode_asterisk_all(fields, n, rse, TRUE);
	}

	return fields;
}


//____________________________________________________________
//  
//		Expand an '*' for all relations
//		in the context list.
//  

static GPRE_NOD explode_asterisk_all( GPRE_NOD fields, int n, RSE rse, BOOLEAN replace)
{
	GPRE_CTX context;
	int i, old_count;

	assert_IS_NOD(fields);

	for (i = 0; i < rse->rse_count; i++) {
		context = rse->rse_context[i];
		old_count = fields->nod_count;
		if (context->ctx_stream)
			fields =
				explode_asterisk_all(fields, n, context->ctx_stream, replace);
		else
			fields = merge_fields(fields, MET_fields(context), n, replace);
		n += fields->nod_count - old_count;
		replace = FALSE;
	}

	return fields;
}


//____________________________________________________________
//  
//		Get an element of an expression to act as a reference
//		field for determining the data type of a host variable.
//  

static GPRE_FLD get_ref( GPRE_NOD expr)
{
	REF ref;
	GPRE_NOD *ptr, *end, node;
	GPRE_FLD field;
	MEL element;

	assert_IS_NOD(expr);

	if (expr->nod_type == nod_via || expr->nod_type == nod_cast) {
		field = (GPRE_FLD) ALLOC(FLD_LEN);
		CME_get_dtype(expr, field);
		if (field->fld_dtype && (field->fld_dtype <= dtype_any_text))
			field->fld_flags |= FLD_text;
		return field;
	}

	switch (expr->nod_type) {
	case nod_field:
		ref = (REF) expr->nod_arg[0];
		return ref->ref_field;


	case nod_array:
		ref = (REF) expr->nod_arg[0];
		return ref->ref_field->fld_array;

	case nod_agg_count:
	case nod_agg_max:
	case nod_agg_min:
	case nod_agg_total:
	case nod_agg_average:
	case nod_plus:
	case nod_minus:
	case nod_times:
	case nod_divide:
	case nod_negate:
	case nod_upcase:
	case nod_concatenate:
		for (ptr = expr->nod_arg, end = ptr + expr->nod_count; ptr < end;
			 ptr++)
			if (field = get_ref(*ptr))
				return field;
		break;

// ** Begin date/time/timestamp support *
	case nod_extract:
		if (field = get_ref(expr->nod_arg[1]))
			return field;
		break;
// ** End date/time/timestamp support *
	case nod_map_ref:
		element = (MEL) expr->nod_arg[0];
		node = element->mel_expr;
		return get_ref(node);
	}

	return 0;
}


//____________________________________________________________
//  
//		Finish off processing an implicit ANY clause.  Assume that the word
//		"select" has already been recognized.  If the outer thing is a group
//		by, so we're looking at a "having", re-resolve the input value to the
//		map.
//  

static GPRE_NOD implicit_any(
						GPRE_REQ request,
						GPRE_NOD value, enum nod_t comparison, enum nod_t any_all)
{
	GPRE_NOD value2, node, node2, field_list;
	RSE rse, sub;
	GPRE_CTX original;
	BOOLEAN distinct;
	struct scope previous_scope;

	assert_IS_REQ(request);
	assert_IS_NOD(value);

	original = request->req_contexts;

	request->req_in_subselect++;
	push_scope(request, &previous_scope);

	if (!(original->ctx_relation || original->ctx_procedure) &&
		request->req_map) value = post_fields(value, request->req_map);

//  Handle the ALL and DISTINCT options 
	distinct = (!MATCH(KW_ALL) && MATCH(KW_DISTINCT)) ? TRUE : FALSE;

	request->req_in_select_list++;
	value2 = SQE_value(request, FALSE, 0, 0);
	request->req_in_select_list--;

	field_list = MAKE_NODE(nod_list, 1);
	field_list->nod_arg[0] = value2;

	rse = par_rse(request, field_list, distinct);
	value2 = rse->rse_fields->nod_arg[0];

	if (sub = rse->rse_aggregate) {
		if (validate_references(value2, sub->rse_group_by))
			PAR_error
				("simple column reference not allowed in aggregate context");
		if (sub->rse_group_by) {
			node = MSC_binary(comparison, value, value2);
			pair(node->nod_arg[0], node->nod_arg[1]);
			rse->rse_boolean = merge(rse->rse_boolean, node);
			if (any_all == nod_ansi_all)
				node = MAKE_NODE(nod_ansi_all, 1);
			else if (!(request->req_database->dbb_flags & DBB_v3))
				node = MAKE_NODE(nod_ansi_any, 1);
			else
				node = MAKE_NODE(nod_any, 1);
			node->nod_count = 0;
			node->nod_arg[0] = (GPRE_NOD) rse;
		}
		else {
			node2 = MAKE_NODE(nod_via, 3);
			node2->nod_count = 0;
			node2->nod_arg[0] = (GPRE_NOD) rse;
			node2->nod_arg[2] = MAKE_NODE(nod_null, 0);
			node2->nod_arg[1] = value2;
			node = MSC_binary(comparison, value, node2);
			pair(node->nod_arg[0], node->nod_arg[1]);
		}
	}
	else {
		node = MSC_binary(comparison, value, value2);
		pair(node->nod_arg[0], node->nod_arg[1]);
		rse->rse_boolean = merge(rse->rse_boolean, node);
		if (any_all == nod_ansi_all)
			node = MAKE_NODE(nod_ansi_all, 1);
		else if (!(request->req_database->dbb_flags & DBB_v3))
			node = MAKE_NODE(nod_ansi_any, 1);
		else
			node = MAKE_NODE(nod_any, 1);
		node->nod_count = 0;
		node->nod_arg[0] = (GPRE_NOD) rse;
	}

	EXP_rse_cleanup(rse);

	pop_scope(request, &previous_scope);
	request->req_in_subselect--;

	return node;
}


//____________________________________________________________
//  
//		Merge two (possibly null) booleans into a single conjunct.
//  

static GPRE_NOD merge( GPRE_NOD expr1, GPRE_NOD expr2)
{

	if (!expr1)
		return expr2;

	if (!expr2)
		return expr1;

	assert_IS_NOD(expr1);
	assert_IS_NOD(expr1);

	return MSC_binary(nod_and, expr1, expr2);
}


//____________________________________________________________
//  
//		Merge 2 field lists
//		  2nd list is added over nth entry in 1st list
//		  if replace is TRUE, otherwise it is added
//		  after the nth entry.
//  

static GPRE_NOD merge_fields( GPRE_NOD fields_1, GPRE_NOD fields_2, int n, BOOLEAN replace)
{
	GPRE_NOD fields;
	int i, count, offset;

	assert_IS_NOD(fields_1);
	assert_IS_NOD(fields_2);

	count = fields_1->nod_count + fields_2->nod_count;
	if (replace)
		count--;
	fields = MAKE_NODE(nod_list, (SSHORT) count);

	count = n;
	if (!replace)
		count++;
	for (i = 0; i < count; i++)
		fields->nod_arg[i] = fields_1->nod_arg[i];

	for (i = 0; i < fields_2->nod_count; i++)
		fields->nod_arg[i + count] = fields_2->nod_arg[i];

	offset = 0;
	if (replace) {
		count++;
		offset = 1;
	}

	for (i = count; i < fields_1->nod_count; i++)
		fields->nod_arg[i + fields_2->nod_count - offset] =
			fields_1->nod_arg[i];

	return fields;
}


//____________________________________________________________
//  
//		Construct negation of expression.
//  

static GPRE_NOD negate( GPRE_NOD expr)
{

	assert_IS_NOD(expr);

	return MSC_unary(nod_not, expr);
}


//____________________________________________________________
//  
//		Given two value expressions associated in a relational
//		expression, see if one is a field reference and the other
//		is a host language variable..  If so, match the field to the
//		host language variable.
//  

static void pair( GPRE_NOD expr1, GPRE_NOD expr2)
{
	GPRE_FLD field, temp;

	assert_IS_NOD(expr1);
	assert_IS_NOD(expr2);

//  Verify that an array field without subscripts is not
//  being used inappropriately 

	if (expr1 && expr2) {
		if (expr1->nod_type == nod_array && !expr1->nod_arg[1])
			PAR_error("Invalid array column reference");
		if (expr2->nod_type == nod_array && !expr2->nod_arg[1])
			PAR_error("Invalid array column reference");
	}

	field = 0;
	if (expr2)
		field = get_ref(expr2);
	if (!field)
		field = get_ref(expr1);

	if (!field)
		return;

	set_ref(expr1, field);

	if (!expr2)
		return;

	if (temp = get_ref(expr1))
		field = temp;

	set_ref(expr2, field);
}


//____________________________________________________________
//  
//		The passed alias list fully specifies a relation.
//		The first alias represents a relation specified in
//		the from list at this scope levels.  Subsequent
//		contexts, if there are any, represent base relations
//		in a view stack.  They are used to fully specify a 
//		base relation of a view.  The aliases used in the 
//		view stack are those used in the view definition.
//  

static GPRE_CTX par_alias_list( GPRE_REQ request, GPRE_NOD alias_list)
{
	GPRE_CTX context, new_context;
	GPRE_REL relation;
	GPRE_NOD *arg, *end;
	USHORT alias_length;
	TEXT *p, *q, *alias;
	SCHAR error_string[ERROR_LENGTH];

	assert_IS_REQ(request);
	assert_IS_NOD(alias_list);

	arg = alias_list->nod_arg;
	end = alias_list->nod_arg + alias_list->nod_count;

//  check the first alias in the list with the relations
//  in the current context for a match 

	if (context = par_alias(request, (TEXT *) * arg)) {
		if (alias_list->nod_count == 1)
			return context;
		relation = context->ctx_relation;
	}

//  if the first alias didn't specify a table in the context stack, 
//  look through all contexts to find one which might be a view with
//  a base table having a matching table name or alias 

	if (!context)
		for (context = request->req_contexts; context;
			 context = context->ctx_next) {
			if (context->ctx_scope_level != request->req_scope_level)
				continue;
			if (!context->ctx_relation)
				continue;
			if (relation =
				par_base_table(request, context->ctx_relation,
							   (TEXT *) * arg)) break;
		}

	if (!context) {
		sprintf(error_string,
				"there is no alias or table named %s at this scope level",
				(TEXT *) * arg);
		PAR_error(error_string);
	}

//  find the base table using the specified alias list, skipping the first one
//  since we already matched it to the context 

	for (arg++; arg < end; arg++)
		if (!(relation = par_base_table(request, relation, (TEXT *) * arg)))
			break;

	if (!relation) {
		sprintf(error_string,
				"there is no alias or table named %s at this scope level",
				(TEXT *) * arg);
		PAR_error(error_string);
	}

//  make up a dummy context to hold the resultant relation 

	new_context = (GPRE_CTX) ALLOC(CTX_LEN);
	new_context->ctx_request = request;
	new_context->ctx_internal = context->ctx_internal;
	new_context->ctx_relation = relation;

//  concatenate all the contexts to form the alias name;
//  calculate the length leaving room for spaces and a null 

	alias_length = alias_list->nod_count;
	for (arg = alias_list->nod_arg; arg < end; arg++)
		alias_length += strlen((TEXT *) * arg);

	alias = (TEXT *) ALLOC(alias_length);

	p = new_context->ctx_alias = alias;
	for (arg = alias_list->nod_arg; arg < end; arg++) {
		for (q = (TEXT *) * arg; *q;)
			*p++ = *q++;
		*p++ = ' ';
	}

	p[-1] = 0;

	return new_context;
}


//____________________________________________________________
//  
//		The passed relation or alias represents 
//		a context which was previously specified
//		in the from list.  Find and return the 
//		proper context.
//  

static GPRE_CTX par_alias( GPRE_REQ request, TEXT * alias)
{
	GPRE_CTX context, relation_context = NULL;
	SCHAR error_string[ERROR_LENGTH];
	TEXT *p, *q;

	assert_IS_REQ(request);

//  look through all contexts at this scope level
//  to find one that has a relation name or alias
//  name which matches the identifier passed 

	for (context = request->req_contexts; context;
		 context = context->ctx_next) {
		if (context->ctx_scope_level != request->req_scope_level)
			continue;

		/* check for matching alias */

		if (context->ctx_alias) {
			for (p = context->ctx_alias, q = alias; *p && *q; p++, q++)
				if (UPPER(*p) != UPPER(*q))
					break;
			if (!*p && !*q)
				return context;
		}

		/* check for matching relation name; aliases take priority so
		   save the context in case there is an alias of the same name;
		   also to check that there is no self-join in the query */

		if (context->ctx_relation &&
			!strcmp(context->ctx_relation->rel_symbol->sym_string, alias)) {
			if (relation_context) {
				sprintf(error_string,
						"the table %s is referenced twice; use aliases to differentiate",
						alias);
				PAR_error(error_string);
			}
			relation_context = context;
		}
	}

	return relation_context;
}


//____________________________________________________________
//  
//		Check if the relation in the passed context
//		has a base table which matches the passed alias.
//  

static GPRE_REL par_base_table( GPRE_REQ request, GPRE_REL relation, TEXT * alias)
{

	assert_IS_REQ(request);

	return MET_get_view_relation(request, relation->rel_symbol->sym_string,
								 alias, 0);
}


//____________________________________________________________
//  
//		Parse an AND boolean expression.
//  

static GPRE_NOD par_and( GPRE_REQ request, USHORT * paren_count)
{
	GPRE_NOD expr1;

	assert_IS_REQ(request);

	expr1 = par_not(request, paren_count);

	if (!MATCH(KW_AND))
		return expr1;

	return merge(expr1, par_and(request, paren_count));
}


//____________________________________________________________
//  
//  

static GPRE_NOD par_collate( GPRE_REQ request, GPRE_NOD arg)
{
	GPRE_FLD field;
	GPRE_NOD node;

	assert_IS_REQ(request);
	assert_IS_NOD(arg);

	node = MAKE_NODE(nod_cast, 2);
	node->nod_count = 1;
	node->nod_arg[0] = arg;
	field = (GPRE_FLD) ALLOC(FLD_LEN);
	node->nod_arg[1] = (GPRE_NOD) field;
	CME_get_dtype(arg, field);
	if (field->fld_dtype > dtype_any_text) {
		/* cast expression to VARYING with implementation-defined */
		/* maximum length */

		field->fld_dtype = dtype_varying;
		field->fld_char_length = 30;
		field->fld_length = 0;	/* calculated by SQL_adjust_field_dtype */
		field->fld_scale = 0;
		field->fld_sub_type = 0;
	}
	else if (field->fld_sub_type) {
		field->fld_character_set = MET_get_text_subtype(field->fld_sub_type);
	}
	SQL_par_field_collate(request, field);
	SQL_adjust_field_dtype(field);

	return node;
}


//____________________________________________________________
//  
//		Parse a SQL "IN" expression.  This comes in two flavors:
//  
//			<value> IN (<value_comma_list>)
//			<value> IN (SELECT <column> <from_nonsense>)
//  

static GPRE_NOD par_in( GPRE_REQ request, GPRE_NOD value)
{
	GPRE_NOD value2, node;
	REF ref1, ref2;
	SCHAR s[ERROR_LENGTH];

	assert_IS_REQ(request);
	assert_IS_NOD(value);

	EXP_left_paren(0);

//  If the next token isn't SELECT, we must have the comma list flavor. 

	if (MATCH(KW_SELECT))
		node = implicit_any(request, value, nod_eq, nod_ansi_any);
	else {
		node = NULL;
		while (TRUE) {
			value2 = par_primitive_value(request, FALSE, 0, 0);
			if (value2->nod_type == nod_value) {
				ref2 = (REF) value2->nod_arg[0];
				if (value->nod_type == nod_field) {
					ref1 = (REF) value->nod_arg[0];
					ref2->ref_field = ref1->ref_field;
				}
				else {
					sprintf(s, "datatype of %s can not be determined",
							token.tok_string);
					PAR_error(s);
				}
			}
			if (!node)
				node = MSC_binary(nod_eq, value, value2);
			else
				node =
					MSC_binary(nod_or, node,
							   MSC_binary(nod_eq, value, value2));

			if (!(MATCH(KW_COMMA)))
				break;
		}
	}

	if (!EXP_match_paren())
		return NULL;

	return node;
}


//____________________________________________________________
//  
//		Parse a join relation clause.
//  

static GPRE_CTX par_joined_relation( GPRE_REQ request, GPRE_CTX prior_context)
{
	GPRE_CTX context1;

	assert_IS_REQ(request);

	if (MATCH(KW_LEFT_PAREN)) {
		context1 = par_joined_relation(request, NULL);
		EXP_match_paren();
	}
	else if (!(context1 = SQE_context(request)))
		return NULL;

	return par_join_clause(request, context1);
}


//____________________________________________________________
//  
//		Parse a join relation clause.
//  

static GPRE_CTX par_join_clause( GPRE_REQ request, GPRE_CTX context1)
{
	GPRE_CTX context2;
	NOD_T join_type;
	GPRE_NOD node;
	RSE rse;

	assert_IS_REQ(request);

	join_type = par_join_type();
	if (join_type == (NOD_T) 0)
		return context1;

	if (!(context2 = par_joined_relation(request, context1)))
		SYNTAX_ERROR("<joined table clause>");

	if (!MATCH(KW_ON))
		SYNTAX_ERROR("ON");

	node = SQE_boolean(request, NULL);

	rse = (RSE) ALLOC(RSE_LEN(2));
	rse->rse_count = 2;
	rse->rse_context[0] = context1;
	rse->rse_context[1] = context2;
	rse->rse_boolean = node;
	rse->rse_join_type = join_type;

	context1 = MAKE_CONTEXT(request);
	context1->ctx_stream = rse;

	return par_join_clause(request, context1);
}


//____________________________________________________________
//  
//		Parse a join type.
//  

static NOD_T par_join_type(void)
{
	NOD_T operator_;

	if (MATCH(KW_INNER))
		operator_ = nod_join_inner;
	else if (MATCH(KW_LEFT))
		operator_ = nod_join_left;
	else if (MATCH(KW_RIGHT))
		operator_ = nod_join_right;
	else if (MATCH(KW_FULL))
		operator_ = nod_join_full;
	else if (MATCH(KW_JOIN))
		return nod_join_inner;
	else
		return (NOD_T) 0;

	if (operator_ != nod_join_inner)
		MATCH(KW_OUTER);

	if (!MATCH(KW_JOIN))
		SYNTAX_ERROR("JOIN");

	return operator_;
}


//____________________________________________________________
//  
//		Parse either of the high precedence operators * and /.
//  

static GPRE_NOD par_multiply(
						GPRE_REQ request,
						BOOLEAN aster_ok,
						USHORT * paren_count, USHORT * bool_flag)
{
	GPRE_NOD node, arg;
	enum nod_t operator_;

	assert_IS_REQ(request);
	node = par_primitive_value(request, aster_ok, paren_count, bool_flag);
	if (node->nod_type == nod_asterisk)
		return node;

	if (KEYWORD(KW_COLLATE))
		return par_collate(request, node);

	while (TRUE) {
		if (MATCH(KW_ASTERISK))
			operator_ = nod_times;
		else if (MATCH(KW_SLASH))
			operator_ = nod_divide;
		else
			return node;
		arg = node;
		node =
			MSC_binary(operator_, arg,
					   par_primitive_value(request, FALSE, paren_count,
										   bool_flag));
	}
}


//____________________________________________________________
//  
//		Parse an NOT boolean expression.
//  

static GPRE_NOD par_not( GPRE_REQ request, USHORT * paren_count)
{
	RSE rse;
	GPRE_NOD node, expr, field;
	enum nod_t type;
	struct scope saved_scope;

	assert_IS_REQ(request);

	if (MATCH(KW_NOT))
		return negate(par_not(request, paren_count));

	type = (enum nod_t) 0;

	if (MATCH(KW_EXISTS))
		type = nod_any;
	else if (MATCH(KW_SINGULAR))
		type = nod_unique;
	if (type == nod_any || type == nod_unique) {
		push_scope(request, &saved_scope);

		EXP_left_paren(0);
		if (!MATCH(KW_SELECT))
			SYNTAX_ERROR("SELECT");

		request->req_in_select_list++;
		if (MATCH(KW_ASTERISK))
			field = NULL;
		else if (!(field = par_udf(request)))
			field = SQE_field(request, FALSE);
		request->req_in_select_list--;

		node = MAKE_NODE(type, 1);
		node->nod_count = 0;
		rse = par_rse(request, 0, FALSE);
		node->nod_arg[0] = (GPRE_NOD) rse;
		if (field) {
			SQE_resolve(field, 0, rse);
			expr = MSC_unary(nod_missing, field);
			rse->rse_boolean = merge(negate(expr), rse->rse_boolean);
		}
		EXP_rse_cleanup((RSE) node->nod_arg[0]);
		pop_scope(request, &saved_scope);
		EXP_match_paren();
		return node;
	}

	return par_relational(request, paren_count);
}


//____________________________________________________________
//  
//		Parse ORDER clause of SELECT expression.  This is 
//		particularly difficult since the ORDER clause can
//		refer to fields by position.
//  

static void par_order(
					  GPRE_REQ request,
					  RSE select, SSHORT union_f, USHORT view_flag)
{
	GPRE_NOD sort, *ptr, values;
	LLS items, directions;
	MAP map;
	int count, direction;
	USHORT i;

	assert_IS_REQ(request);

//  This doesn't really belong here, but it's convenient.  Parse the
//  SQL "FOR UPDATE OF ..." clause.  Just eat it and ignore it. 

	if (MATCH(KW_FOR)) {
		MATCH(KW_UPDATE);
		MATCH(KW_OF);
		do
			CPR_token();
		while (MATCH(KW_COMMA));
	}

	if (!MATCH(KW_ORDER))
		return;
	if (view_flag)
		PAR_error("sort clause not allowed in a view definition");

	MATCH(KW_BY);
	items = directions = NULL;
	count = direction = 0;
	values = select->rse_fields;

	while (TRUE) {
		direction = FALSE;
		if (token.tok_type == tok_number) {
			i = EXP_USHORT_ordinal(FALSE);
			if (i < 1 || i > values->nod_count)
				SYNTAX_ERROR("<ordinal column position>");
			sort = values->nod_arg[i - 1];
			ADVANCE_TOKEN;
			if (KEYWORD(KW_COLLATE))
				sort = par_collate(request, sort);
		}
		else {
			if (union_f)
				SYNTAX_ERROR("<column position in union>");
			sort = SQE_value(request, FALSE, 0, 0);
			if (request && (map = request->req_map))
				sort = post_map(sort, map);
		}
		if (MATCH(KW_ASCENDING))
			direction = FALSE;
		else if (MATCH(KW_DESCENDING))
			direction = TRUE;
		count++;
		PUSH((GPRE_NOD) direction, &directions);
		PUSH(sort, &items);
		if (!MATCH(KW_COMMA))
			break;
	}

	select->rse_sort = sort = MAKE_NODE(nod_sort, (SSHORT) (count * 2));
	sort->nod_count = count;
	ptr = sort->nod_arg + count * 2;

	while (items) {
		*--ptr = (GPRE_NOD) POP(&items);
		*--ptr = (GPRE_NOD) POP(&directions);
	}
}


//____________________________________________________________
//  
//		Allow the user to specify the access plan
//		for a query as part of a select expression.
//  

static GPRE_NOD par_plan( GPRE_REQ request)
{
	NOD_T operator_;
	GPRE_NOD plan_expression;

	assert_IS_REQ(request);

//  parse the join type 

	if (MATCH(KW_JOIN))
		operator_ = nod_join;
	else if (MATCH(KW_MERGE))
		operator_ = nod_merge;
	else if (MATCH(KW_SORT) && MATCH(KW_MERGE))
		operator_ = nod_merge;
	else
		operator_ = nod_join;

//  make up the plan expression node 

	plan_expression = MAKE_NODE(nod_plan_expr, 2);

	if (operator_ != nod_join)
		plan_expression->nod_arg[0] = MAKE_NODE(operator_, 0);

//  parse the plan items at this level 

	EXP_left_paren(0);

#pragma FB_COMPILER_MESSAGE("Fix! Wrong function ptr type!")
//
// Please search for "reinterpret_cast<pfn_SQE_list_cb>",
// there are more in this file.
//
	plan_expression->nod_arg[1] =
		SQE_list(reinterpret_cast < pfn_SQE_list_cb > (par_plan_item),
				 request, FALSE);

	if (!EXP_match_paren())
		return NULL;
	return plan_expression;
}


//____________________________________________________________
//  
//		Parse an individual plan item for an 
//		access plan.
//  

static GPRE_NOD par_plan_item( GPRE_REQ request, BOOLEAN aster_ok)
{
	LLS stack = NULL;
	int count;
	GPRE_NOD plan_item, alias_list, access_type, index_list, *ptr;
	GPRE_CTX context;

	assert_IS_REQ(request);

//  check for a plan expression 

	if (KEYWORD(KW_JOIN) ||
		KEYWORD(KW_SORT) ||
		KEYWORD(KW_MERGE) || KEYWORD(KW_LEFT_PAREN)) return par_plan(request);

//  parse the list of one or more table names or
//  aliases (more than one is used when there is
//  a need to differentiate base tables of a view) 

	for (count = 0; token.tok_type == tok_ident; count++) {
		if (KEYWORD(KW_NATURAL) || KEYWORD(KW_ORDER) || KEYWORD(KW_INDEX))
			break;

		PUSH((GPRE_NOD) upcase_string(token.tok_string), &stack);
		ADVANCE_TOKEN;
	}

	if (!count)
		SYNTAX_ERROR("<table name> or <alias>");

	alias_list = MAKE_NODE(nod_list, (SSHORT) count);
	for (ptr = &alias_list->nod_arg[count]; stack;)
		*--ptr = (GPRE_NOD) POP(&stack);

//  lookup the contexts for the aliases 

	context = par_alias_list(request, alias_list);

//  parse the access type 

	if (KEYWORD(KW_NATURAL)) {
		access_type = MAKE_NODE(nod_natural, 0);
		ADVANCE_TOKEN;
	}
	else if (KEYWORD(KW_ORDER)) {
		access_type = MAKE_NODE(nod_index_order, 1);
		access_type->nod_count = 0;
		ADVANCE_TOKEN;

		if (token.tok_type != tok_ident)
			SYNTAX_ERROR("<index name>");
		access_type->nod_arg[0] = (GPRE_NOD) upcase_string(token.tok_string);
		ADVANCE_TOKEN;
	}
	else if (KEYWORD(KW_INDEX)) {
		access_type = MAKE_NODE(nod_index, 1);
		access_type->nod_count = 0;
		ADVANCE_TOKEN;

		EXP_left_paren(0);

		stack = NULL;
		for (count = 0; token.tok_type == tok_ident;) {
			PUSH((GPRE_NOD) upcase_string(token.tok_string), &stack);
			ADVANCE_TOKEN;

			count++;

			if (!MATCH(KW_COMMA))
				break;
		}
		if (!count)
			SYNTAX_ERROR("<table name> or <alias>");

		access_type->nod_arg[0] = index_list =
			MAKE_NODE(nod_list, (SSHORT) count);
		for (ptr = &index_list->nod_arg[count]; stack;)
			*--ptr = (GPRE_NOD) POP(&stack);

		if (!EXP_match_paren())
			return NULL;
	}
	else {
		SYNTAX_ERROR("NATURAL, ORDER, or INDEX");
	}


//  generate the plan item node 

	plan_item = MAKE_NODE(nod_plan_item, 3);
	plan_item->nod_count = 2;
	plan_item->nod_arg[0] = alias_list;
	plan_item->nod_arg[1] = access_type;
	plan_item->nod_arg[2] = (GPRE_NOD) context;

	return plan_item;
}


//____________________________________________________________
//  
//		Parse a value expression.  The value could be any of the
//		following:
//  
//			"quoted string"
//			_CHARSET"quoted string"
//			123
//			+1.234E-3
//			field
//			relation.field
//			context.field
//			user defined function
//  

static GPRE_NOD par_primitive_value(
							   GPRE_REQ request,
							   BOOLEAN aster_ok,
							   USHORT * paren_count, USHORT * bool_flag)
{
	GPRE_NOD node, node_arg;
	REF reference;
	OPS *op;
	MAP map;
	USHORT distinct, local_count;
	ACT action;
	KWWORDS kw_word;

	assert_IS_REQ(request);

	node = FALSE;

	if (!paren_count) {
		local_count = 0;
		paren_count = &local_count;
	}

	if (MATCH(KW_SELECT))
		return par_stat(request);

	if (MATCH(KW_MINUS))
		return MSC_unary(nod_negate,
						 par_primitive_value(request, FALSE, paren_count, 0));

	MATCH(KW_PLUS);

	if (MATCH(KW_USER)) {
		return MAKE_NODE(nod_user_name, 0);
	}

	if (MATCH(KW_VALUE)) {
		/* If request is NULL we must be processing a subquery - and 
		 * without the request to refer to we're kinda hosed
		 */
		if (!request)
			PAR_error("VALUE cannot be used in this context");
		action = request->req_actions;
		if (request->req_type != REQ_ddl ||
			!action ||
			!(action->act_type == ACT_create_domain ||
			  action->act_type == ACT_alter_domain))
				PAR_error("VALUE cannot be used in this context");

		return MAKE_NODE(nod_dom_value, 0);
	}

	if (MATCH(KW_LEFT_PAREN)) {
		(*paren_count)++;
		if (bool_flag && *bool_flag)
			node = SQE_boolean(request, paren_count);
		else
			node = SQE_value(request, FALSE, paren_count, bool_flag);
		EXP_match_paren();
		(*paren_count)--;
		return node;
	}

//  Check for an aggregate statistical expression.  If we already have a
//  map defined for the request, we're part of either HAVING or a trailing
//  ORDER clause.  In this case, post only the complete expression, and not
//  the sub-expressions. 

	for (op = stat_ops; (int) op->rel_kw != (int) KW_none; op++) {
		MATCH(KW_ALL);
		if (MATCH(op->rel_kw)) {
			if (request && (request->req_in_aggregate ||
							!(request->req_in_select_list ||
							  request->req_in_having_clause ||
							  request->req_in_order_by_clause)))
				/* either nested aggregate, or not part of a select
				   list, having clause, or order by clause (in any subquery)
				 */
				PAR_error("Invalid aggregate reference");

			node = MAKE_NODE(op->rel_op, 2);
			node->nod_count = 1;
			EXP_left_paren("left parenthesis in statistical function");
			distinct = MATCH(KW_DISTINCT);
			if (request) {
				map = request->req_map;
				request->req_map = NULL;
				++request->req_in_aggregate;
			}
			if (node->nod_type == nod_agg_count && MATCH(KW_ASTERISK))
				node->nod_count = 0;
			else {
				node->nod_arg[0] = SQE_value(request, FALSE, 0, 0);
				/* Disallow arrays as arguments to aggregate functions  */
				node_arg = node->nod_arg[0];
				if (node_arg && node_arg->nod_type == nod_array)
					PAR_error
						("Array columns not permitted in aggregate functions");
			}

			if (distinct)
				node->nod_arg[1] = node->nod_arg[0];
			EXP_match_paren();
			if (request) {
				if (map)
					node = post_map(node, map);
				request->req_map = map;
				--request->req_in_aggregate;
			}
			return node;
		}
	}

//  If it's a number or a quoted string, it's a literal 

	if (token.tok_type == tok_number ||
		(QUOTED(token.tok_type) && sw_sql_dialect == 1) ||
		SINGLE_QUOTED(token.tok_type)) {
		node = EXP_literal();
		return node;
	}

//  moved this timestamp support down some lines, because it caused
//  gpre to segfault when it was done here.
//  FSG 15.Nov.2000
//  


//  If the next token is a colon, it is a variable reference 

	if ((int) token.tok_keyword == (int) KW_COLON) {
		if (!request) {
			/* We must be processing a subquery - and without the request to
			 * post the :hostvar to we can't continue.
			 * (core dump when de-refer of NULL request)
			 */
			PAR_error(":hostvar reference not supported in this context");
			return NULL;
		}
		reference = SQE_variable(request, FALSE);
		node = MSC_unary(nod_value, (GPRE_NOD) reference);
		reference->ref_next = request->req_values;
		request->req_values = reference;
		return node;
	}



//  Must be a field or a udf.  If there is a map, post the field to it. 
	node = par_udf_or_field(request, aster_ok);

//  
//if (request && (map = request->req_map))
//   return post_map (node, map);
//  

	if (!node)
//  I don't know what it's good for, but let's try it anyway if we haven't found 
//  anything that makes sense until now 
	{
// ** Begin date/time/timestamp support *
		kw_word = token.tok_keyword;

		if (MATCH(KW_DATE) || MATCH(KW_TIME) || MATCH(KW_TIMESTAMP)) {
			token.tok_keyword = kw_word;
			node = EXP_literal();
			return node;
		}

// ** End date/time/timestamp support *

	}

	return node;
}


//____________________________________________________________
//  
//		Parse relational expression.
//  

static GPRE_NOD par_relational( GPRE_REQ request, USHORT * paren_count)
{
	GPRE_NOD node, expr1, expr2;
	REF ref_value;
	OPS *op;
	int negation;
	USHORT local_flag;
	NOD_T *relational_ops;

	assert_IS_REQ(request);

	local_flag = TRUE;

	negation = FALSE;
	expr1 = SQE_value(request, FALSE, paren_count, &local_flag);
	if (KEYWORD(KW_RIGHT_PAREN))
		return expr1;
	if (KEYWORD(KW_SEMI_COLON))
		for (relational_ops = relationals; *relational_ops != (NOD_T) 0;
			 relational_ops++)
			if (expr1->nod_type == *relational_ops)
				return expr1;

	if (MATCH(KW_NOT))
		negation = TRUE;

//  Check for one of the binary operators 

	if (MATCH(KW_IN))
		node = par_in(request, expr1);
	else if (MATCH(KW_BETWEEN)) {
		node = MAKE_NODE(nod_between, 3);
		node->nod_arg[0] = expr1;
		node->nod_arg[1] = SQE_value(request, FALSE, 0, 0);
		MATCH(KW_AND);
		node->nod_arg[2] = SQE_value(request, FALSE, 0, 0);
		pair(node->nod_arg[0], node->nod_arg[1]);
		pair(node->nod_arg[0], node->nod_arg[2]);
	}
	else if (MATCH(KW_LIKE)) {
		node = MAKE_NODE(nod_like, 3);
		node->nod_arg[0] = expr1;
		node->nod_arg[1] = SQE_value(request, FALSE, 0, 0);
		pair(node->nod_arg[0], node->nod_arg[1]);
		if (MATCH(KW_ESCAPE)) {
			node->nod_arg[2] = expr2 = SQE_value(request, FALSE, 0, 0);
			if (expr2->nod_type == nod_value) {
				ref_value = (REF) expr2->nod_arg[0];
				ref_value->ref_field =
					MET_make_field("like_escape_character", dtype_text, 2,
								   FALSE);
			}
		}
		else
			node->nod_count = 2;
	}
	else if (MATCH(KW_IS)) {
		if (MATCH(KW_NOT))
			negation = !negation;
		if (!MATCH(KW_NULL))
			SYNTAX_ERROR("NULL");
		if (expr1->nod_type == nod_array)
			expr1->nod_type = nod_field;
		node = MSC_unary(nod_missing, expr1);
	}
	else {
		node = NULL;
		for (op = rel_ops; (int) op->rel_kw != (int) KW_none; op++)
			if (MATCH(op->rel_kw))
				break;
		if ((int) op->rel_kw == (int) KW_none) {
			for (relational_ops = relationals; *relational_ops != (NOD_T) 0;
				 relational_ops++)

				if (expr1->nod_type == *relational_ops)
					return expr1;
			SYNTAX_ERROR("<relational operator>");
		}
		if ((int) op->rel_kw == (int) KW_STARTING)
			MATCH(KW_WITH);
		if (MATCH(KW_ANY)) {
			if (!MATCH(KW_LEFT_PAREN) || !MATCH(KW_SELECT))
				SYNTAX_ERROR("<select clause> for ANY");
			node = implicit_any(request, expr1, op->rel_op, nod_any);
			EXP_match_paren();
		}
		else if (MATCH(KW_ALL)) {
			if (!MATCH(KW_LEFT_PAREN) || !MATCH(KW_SELECT))
				SYNTAX_ERROR("<select clause> for ALL");
			if (op->rel_negation == nod_any
				|| op->rel_negation == nod_ansi_any
				|| op->rel_negation ==
				nod_ansi_all) SYNTAX_ERROR("<relational operator> for ALL");
			node = implicit_any(request, expr1, op->rel_op, nod_ansi_all);
			EXP_match_paren();
		}
		else {
			node =
				MSC_binary(op->rel_op, expr1,
						   SQE_value(request, FALSE, 0, 0));
			pair(node->nod_arg[0], node->nod_arg[1]);
		}
	}

	return (negation) ? negate(node) : node;
}


//____________________________________________________________
//  
//		Parse the SQL equivalent of a record selection expression --
//		FROM, WHERE, and ORDER clauses.  A field list may or may not
//		be present.
//  

static RSE par_rse( GPRE_REQ request, GPRE_NOD fields, BOOLEAN distinct)
{
	GPRE_CTX context;
	MAP map;
	GPRE_NOD *ptr, *end, node;
	RSE select, sub_rse;
	BOOLEAN aggregate;
	int count, i, old_count;
	LLS stack;

	assert_IS_REQ(request);
	assert_IS_NOD(fields);

//  Get list and count of relations 

	if (!MATCH(KW_FROM))
		SYNTAX_ERROR("FROM");

	stack = NULL;
	count = 0;

	do
		if (context = par_joined_relation(request, NULL)) {
			PUSH((GPRE_NOD) context, &stack);
			count++;
		}
		else
			return NULL;
	while (MATCH(KW_COMMA));

//  Now allocate a record select expression
//  block for the beast and fill in what we already know.  

	select = (RSE) ALLOC(RSE_LEN(count));
	select->rse_count = count;

	while (count--)
		select->rse_context[count] = (GPRE_CTX) POP(&stack);

//  If a field list has been presented, resolve references now 

	aggregate = FALSE;

	if (fields) {
		for (count = fields->nod_count, ptr = fields->nod_arg, i = 0;
			 i < count; i++) {
			node = *(ptr + i);
			if (node->nod_type == nod_asterisk) {
				old_count = count;
				fields = explode_asterisk(fields, i, select);
				count = fields->nod_count;
				i += count - old_count;
				ptr = fields->nod_arg;
			}
			else {
				aggregate |= SQE_resolve(node, 0, select);
				pair(node, 0);
				switch (node->nod_type) {
				case nod_agg_count:
				case nod_agg_max:
				case nod_agg_min:
				case nod_agg_average:
				case nod_agg_total:
					if ((node->nod_arg[1]) &&
						(request->req_database->dbb_flags & DBB_v3))
						select->rse_reduced =
							MSC_unary(nod_sort, node->nod_arg[1]);
					break;
				}
			}
		}
	}
	select->rse_fields = fields;
	if (distinct)
		select->rse_reduced = fields;

//  Handle a boolean, if present 

	if (MATCH(KW_WITH)) {
		++request->req_in_where_clause;
		select->rse_boolean = SQE_boolean(request, 0);
		--request->req_in_where_clause;
	}

	if (MATCH(KW_GROUP)) {
		MATCH(KW_BY);
		select->rse_group_by =
			SQE_list(reinterpret_cast < pfn_SQE_list_cb >
					 (par_udf_or_field_with_collate), request, FALSE);
		for (ptr = select->rse_group_by->nod_arg, end =
			 ptr + select->rse_group_by->nod_count; ptr < end; ptr++) {
			if ((*ptr)->nod_type == nod_array)
				PAR_error("Array columns not permitted in GROUP BY clause");
		}
	}

	if (select->rse_group_by || aggregate) {
		if (validate_references(select->rse_fields, select->rse_group_by))
			PAR_error
				("simple column reference not allowed in aggregate context");
		sub_rse = select;
		sub_rse->rse_map = map = (MAP) ALLOC(sizeof(struct map));
		if (select->rse_group_by)
			request->req_map = map;
		map->map_context = MAKE_CONTEXT(request);
		select = (RSE) ALLOC(RSE_LEN(0));
		select->rse_aggregate = sub_rse;

		if (fields)
			select->rse_fields = copy_fields(sub_rse->rse_fields, map);

		if (MATCH(KW_HAVING)) {
			++request->req_in_having_clause;
			select->rse_boolean = SQE_boolean(request, 0);
			--request->req_in_having_clause;
			if (validate_references
				(select->rse_boolean,
				 sub_rse->
				 rse_group_by))
PAR_error("simple column reference in HAVING must be referenced in GROUP BY");
		}
	}

//  parse a user-specified access plan 

	if (MATCH(KW_PLAN))
		select->rse_plan = par_plan(request);

	return select;
}


//____________________________________________________________
//  
//		Parse a SELECT (sans keyword) expression (except UNION).  This
//		is called exclusively by SQE_select, which handles unions.  Note:
//		if "union_rse" is non-null, we are a subsequent SELECT in a union.
//		In this case, check datatypes of the field against the rse field
//		list.
//  

static RSE par_select( GPRE_REQ request, RSE union_rse)
{
	RSE select;
	GPRE_NOD s_list, into_list;
	BOOLEAN distinct;

	assert_IS_REQ(request);

//  Handle the ALL and DISTINCT options 

	distinct = (!MATCH(KW_ALL) && MATCH(KW_DISTINCT)) ? TRUE : FALSE;

//  Make select list out of select items 

	++request->req_in_select_list;
	s_list = SQE_list(SQE_value_or_null, request, TRUE);
	--request->req_in_select_list;

//  If this is not a declare cursor statement and an INTO list is present,
//  parse it. 

	if (!(request->req_flags & REQ_sql_declare_cursor))
		into_list =
			(MATCH(KW_INTO)) ? SQE_list(reinterpret_cast < pfn_SQE_list_cb >
										(SQE_variable), request,
										FALSE) : NULL;
	else
		into_list = NULL;

	select = par_rse(request, s_list, distinct);
	if (select->rse_into = into_list)
		select->rse_flags |= RSE_singleton;

	if (union_rse && s_list->nod_count != union_rse->rse_fields->nod_count)
		PAR_error("select lists for UNION don't match");

	return select;
}


//____________________________________________________________
//  
//		Parse a dumb SQL scalar statistical expression.  Somebody else
//		has already eaten the SELECT on the front.
//  

static GPRE_NOD par_stat( GPRE_REQ request)
{
	GPRE_NOD node, field_list;
	GPRE_NOD item;
	RSE select;
	BOOLEAN distinct;
	struct scope previous_scope;

	assert_IS_REQ(request);

	request->req_in_subselect++;
	push_scope(request, &previous_scope);

	distinct = (!MATCH(KW_ALL) && MATCH(KW_DISTINCT)) ? TRUE : FALSE;

	request->req_in_select_list++;
	if (!(item = par_udf(request)))
		item = SQE_value(request, FALSE, 0, 0);
	request->req_in_select_list--;

	field_list = MAKE_NODE(nod_list, 1);
	field_list->nod_arg[0] = item;
	select = par_rse(request, field_list, distinct);
	select->rse_flags |= RSE_singleton;

	item = select->rse_fields->nod_arg[0];

	node = MAKE_NODE(nod_via, 3);
	node->nod_count = 0;
	node->nod_arg[0] = (GPRE_NOD) select;
	node->nod_arg[2] = MAKE_NODE(nod_null, 0);
	node->nod_arg[1] = item;

	EXP_rse_cleanup(select);
	pop_scope(request, &previous_scope);
	request->req_in_subselect--;

	return node;
}


//____________________________________________________________
//  
//       Parse a subscript value.  
//  

static GPRE_NOD par_subscript( GPRE_REQ request)
{
	GPRE_NOD node;
	REF reference;
	SCHAR *string;

	assert_IS_REQ(request);

	reference = (REF) ALLOC(REF_LEN);
	node = MSC_unary(nod_value, (GPRE_NOD) reference);

//  Special case literals 

	if (token.tok_type == tok_number) {
		node->nod_type = nod_literal;
		reference->ref_value = string = (TEXT *) ALLOC(token.tok_length + 1);
		COPY(token.tok_string, token.tok_length, string);
		ADVANCE_TOKEN;
		return node;
	}

	if (!MATCH(KW_COLON))
		SYNTAX_ERROR("<colon>");

	reference->ref_value = PAR_native_value(FALSE, FALSE);

	if (request) {
		reference->ref_next = request->req_values;
		request->req_values = reference;
	}

	return node;
}


//____________________________________________________________
//  
//		Match several trailing parentheses.
//  

static void par_terminating_parens(
								   USHORT * paren_count, USHORT * local_count)
{

	if (*paren_count && paren_count == local_count)
		do
			EXP_match_paren();
		while (--(*paren_count));
}


//____________________________________________________________
//  
//		Parse a user defined function.  If the current token isn't one,
//		return NULL.  Otherwise try to parse one.  If things go badly,
//		complain bitterly.
//  

static GPRE_NOD par_udf( GPRE_REQ request)
{
	GPRE_NOD node;
	GPRE_NOD *input;
	UDF udf, tmp_udf;
	USHORT local_count;
	GPRE_FLD field;
	SCHAR s[ERROR_LENGTH];
	DBB db;
	TEXT *gen_name;

	if (!request)
		return NULL;

	assert_IS_REQ(request);

//  Check for user defined functions 
// ** resolve only if an identifier *
	if ((QUOTED(token.tok_type)) || token.tok_type == tok_ident)
		SQL_resolve_identifier("<Udf Name>", s);
	if (request->req_database)
		udf = MET_get_udf(request->req_database, token.tok_string);
	else {
		/* no database was specified, check the metadata for all the databases
		   for the existence of the udf */

		udf = NULL;
		for (db = isc_databases; db; db = db->dbb_next)
			if (tmp_udf = MET_get_udf(db, token.tok_string))
				if (udf) {
					/* udf was found in more than one database */
					sprintf(s, "UDF %s is ambiguous", token.tok_string);
					PAR_error(s);
				}
				else {
					udf = tmp_udf;
					request->req_database = db;
				}
	}

	if (udf) {
		if ((SQL_DIALECT_V5 == sw_sql_dialect) &&
			((dtype_sql_date == udf->udf_dtype) ||
			 (dtype_sql_time == udf->udf_dtype) ||
			 (dtype_int64 == udf->udf_dtype)))
				dialect1_bad_type(udf->udf_dtype);

		node = MAKE_NODE(nod_udf, 2);
		node->nod_count = 1;
		node->nod_arg[1] = (GPRE_NOD) udf;
		ADVANCE_TOKEN;
		EXP_left_paren(0);
		if (!(token.tok_keyword == KW_RIGHT_PAREN)) {
			/* parse udf parameter references */
			node->nod_arg[0] = SQE_list(SQE_value, request, FALSE);

			if (udf->udf_args != node->nod_arg[0]->nod_count)
				PAR_error("count of UDF parameters doesn't match definition");

			/* Match parameter types to the declared parameters */
			for (input = node->nod_arg[0]->nod_arg,
				 field = udf->udf_inputs;
				 field;
				 input++, field = field->fld_next)
			 SQE_post_field(*input, field);
		}
		else {
			node->nod_arg[0] = (GPRE_NOD) 0;
			node->nod_count = 0;
		}
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	if (!request)
		return NULL;

//  Check for GEN_ID () 
	if (MATCH(KW_GEN_ID)) {
		gen_name = (TEXT *) ALLOC(NAME_SIZE);
		node = MAKE_NODE(nod_gen_id, 2);
		node->nod_count = 1;
		EXP_left_paren(0);
		SQL_resolve_identifier("<Generator Name>", gen_name);
		node->nod_arg[1] = (GPRE_NOD) gen_name;
		ADVANCE_TOKEN;
		if (!MATCH(KW_COMMA))
			SYNTAX_ERROR("<comma>");
		node->nod_arg[0] = SQE_value(request, FALSE, 0, 0);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

//  Check for DATE constants 
// ** Begin date/time/timesamp *
	if (MATCH(KW_CURRENT_DATE))
		return MAKE_NODE(nod_current_date, 0);
	else if (MATCH(KW_CURRENT_TIME))
		return MAKE_NODE(nod_current_time, 0);
	else if (MATCH(KW_CURRENT_TIMESTAMP))
		return MAKE_NODE(nod_current_timestamp, 0);

//  End date/time/timesamp *

//  Check for SQL II defined functions 

// ** Begin date/time/timesamp *
	if (MATCH(KW_EXTRACT)) {
		KWWORDS kw_word;
		node = MAKE_NODE(nod_extract, 2);
		EXP_left_paren(0);
		kw_word = token.tok_keyword;
		if (MATCH(KW_YEAR) || MATCH(KW_MONTH) || MATCH(KW_DAY) ||
			MATCH(KW_HOUR) || MATCH(KW_MINUTE) || MATCH(KW_SECOND) ||
			MATCH(KW_WEEKDAY) || MATCH(KW_YEARDAY)) {
			node->nod_arg[0] = (struct gpre_nod *) kw_word;
			if (!MATCH(KW_FROM))
				SYNTAX_ERROR("FROM");
		}
		else
			SYNTAX_ERROR("valid extract part");
		node->nod_arg[1] = SQE_value(request, FALSE, 0, 0);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

//  End date/time/timesamp *

	if (MATCH(KW_UPPER)) {
		node = MAKE_NODE(nod_upcase, 1);
		EXP_left_paren(0);
		node->nod_arg[0] = SQE_value(request, FALSE, 0, 0);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	if (MATCH(KW_CAST)) {
		node = MAKE_NODE(nod_cast, 2);
		node->nod_count = 1;
		EXP_left_paren(0);
		node->nod_arg[0] = SQE_value_or_null(request, FALSE, 0, 0);
		if (!MATCH(KW_AS))
			SYNTAX_ERROR("AS");
		field = (GPRE_FLD) ALLOC(FLD_LEN);
		node->nod_arg[1] = (GPRE_NOD) field;
		SQL_par_field_dtype(request, field, FALSE);
		SQL_par_field_collate(request, field);
		SQL_adjust_field_dtype(field);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	return NULL;
}


//____________________________________________________________
//  
//		Parse a user defined function or a field name.
//  

static GPRE_NOD par_udf_or_field( GPRE_REQ request, BOOLEAN aster_ok)
{
	GPRE_NOD node;

	assert_IS_REQ(request);

	if (!(node = par_udf(request)))
		node = SQE_field(request, aster_ok);

	return node;
}


//____________________________________________________________
//  
//		Parse a user defined function or a field name.
//		Allow the collate clause to follow.
//  

static GPRE_NOD par_udf_or_field_with_collate( GPRE_REQ request, BOOLEAN aster_ok)
{
	GPRE_NOD node;

	assert_IS_REQ(request);

	node = par_udf_or_field(request, aster_ok);
	if (KEYWORD(KW_COLLATE))
		node = par_collate(request, node);

	return node;
}


//____________________________________________________________
//  
//		Post a field or aggregate to a map.  This is used to references
//		to aggregates and unions.  Return a reference to the map (rather
//		than the expression itself).  Post only the aggregates and fields,
//		not the computations around them.
//  

static GPRE_NOD post_fields( GPRE_NOD node, MAP map)
{
	GPRE_NOD *ptr, *end;

	assert_IS_NOD(node);

	switch (node->nod_type) {
		/* Removed during fix to BUG_8021 - this would post a literal to
		 * the map record for each literal used in an expression - which 
		 * would result in unneccesary data movement as the literal is more
		 * easily experssed in the assignment portion of the mapping select
		 * operation.
		 case nod_literal:
		 * 1995-Jul-10 David Schnepper 
		 */
	case nod_field:
	case nod_agg_max:
	case nod_agg_min:
	case nod_agg_average:
	case nod_agg_total:
	case nod_agg_count:
	case nod_map_ref:
		return post_map(node, map);

	case nod_udf:
	case nod_gen_id:
		node->nod_arg[0] = post_fields(node->nod_arg[0], map);
		break;

	case nod_list:
	case nod_upcase:
	case nod_concatenate:
	case nod_cast:
	case nod_plus:
	case nod_minus:
	case nod_times:
	case nod_divide:
	case nod_negate:
		for (ptr = node->nod_arg, end = ptr + node->nod_count; ptr < end;
			 ptr++)
			*ptr = post_fields(*ptr, map);
		break;
// ** Begin date/time/timestamp support *
	case nod_extract:
		node->nod_arg[1] = post_fields(node->nod_arg[1], map);
		break;
// ** End date/time/timestamp support *
	}

	return node;
}


//____________________________________________________________
//  
//		Post a value expression to a map.  This is used to references
//		to aggregates and unions.  Return a reference to the map (rather
//		than the expression itself).
//  

static GPRE_NOD post_map( GPRE_NOD node, MAP map)
{
	MEL element;

	assert_IS_NOD(node);

//  Search existing map for equivalent expression.  If we find one,
//  return a reference to it. 

	if (node->nod_type == nod_map_ref) {
		element = (MEL) node->nod_arg[0];
		if (element->mel_context == map->map_context)
			return node;
	}

	for (element = map->map_elements; element; element = element->mel_next)
		if (compare_expr(node, element->mel_expr))
			return MSC_unary(nod_map_ref, (GPRE_NOD) element);

//  We need to make up a new map reference 

	element = (MEL) ALLOC(sizeof(struct mel));
	element->mel_next = map->map_elements;
	map->map_elements = element;
	element->mel_position = map->map_count++;
	element->mel_expr = node;
	element->mel_context = map->map_context;

//  Make up a reference to the map element 

	return MSC_unary(nod_map_ref, (GPRE_NOD) element);
}


//____________________________________________________________
//  
//		Copy a selection list to the map generated for a UNION
//		construct.  Note at this level we want the full expression
//		selected posted, not just the portions that come from the
//		stream.  Thus CAST and other operations will be passed into
//		a UNION.  See BUG_8021 & BUG_8000 for examples.
//  

static GPRE_NOD post_select_list( GPRE_NOD fields, MAP map)
{
	GPRE_NOD list;
	USHORT i;

	assert_IS_NOD(fields);

	list = MAKE_NODE(nod_list, fields->nod_count);

	for (i = 0; i < fields->nod_count; i++)
		list->nod_arg[i] = post_map(fields->nod_arg[i], map);

	return list;
}


//____________________________________________________________
//  
//		Restore saved scoping information to the request block
//  

static void pop_scope( GPRE_REQ request, struct scope *save_scope)
{
	assert_IS_REQ(request);

	request->req_contexts = save_scope->req_contexts;
	request->req_scope_level = save_scope->req_scope_level;
	request->req_in_aggregate = save_scope->req_in_aggregate;
	request->req_in_select_list = save_scope->req_in_select_list;
	request->req_in_where_clause = save_scope->req_in_where_clause;
	request->req_in_having_clause = save_scope->req_in_having_clause;
	request->req_in_order_by_clause = save_scope->req_in_order_by_clause;
}


//____________________________________________________________
//  
//		Save scoping information from the request block
//  

static void push_scope( GPRE_REQ request, struct scope *save_scope)
{
	assert_IS_REQ(request);

	save_scope->req_contexts = request->req_contexts;
	save_scope->req_scope_level = request->req_scope_level;
	save_scope->req_in_aggregate = request->req_in_aggregate;
	save_scope->req_in_select_list = request->req_in_select_list;
	save_scope->req_in_where_clause = request->req_in_where_clause;
	save_scope->req_in_having_clause = request->req_in_having_clause;
	save_scope->req_in_order_by_clause = request->req_in_order_by_clause;
	save_scope->req_in_subselect = request->req_in_subselect;
	request->req_scope_level++;
	request->req_in_aggregate = 0;
	request->req_in_select_list = 0;
	request->req_in_where_clause = 0;
	request->req_in_having_clause = 0;
	request->req_in_order_by_clause = 0;
	request->req_in_subselect = 0;
}


//____________________________________________________________
//  
//		Attempt to resolve a field in a context.  If successful, return
//		the field.  Otherwise return NULL.  Let somebody else worry about
//		errors.
//  

static GPRE_FLD resolve(
				   GPRE_NOD node,
				   GPRE_CTX context, GPRE_CTX * found_context, ACT * slice_action)
{
	SYM symbol, temp_symbol;
	GPRE_FLD field;
	TOK f_token, q_token;
	RSE rs_stream;
	SSHORT i;
	GPRE_REQ slice_req;
	SLC slice;
	ARY ary_info;
	ACT action;

	assert_IS_NOD(node);

	if (rs_stream = context->ctx_stream) {
		for (i = 0; i < rs_stream->rse_count; i++)
			if (field =
				resolve(node, rs_stream->rse_context[i], found_context,
						slice_action)) return field;

		return NULL;
	}

	f_token = (TOK) node->nod_arg[0];
	q_token = (TOK) node->nod_arg[1];

	if (!(context->ctx_relation || context->ctx_procedure))
		return NULL;

//  Handle unqualified fields first for simplicity 

	if (!q_token)
		field = MET_context_field(context, f_token->tok_string);
	else {
		/* Now search alternatives for the qualifier */

		symbol = HSH_lookup(q_token->tok_string);

		/* This caused gpre to dump core if there are lower case 
		   table aliases in a where clause used with dialect 2 or 3 

		   if ( (symbol == NULL)&& (sw_case || sw_sql_dialect == SQL_DIALECT_V5))
		   symbol = HSH_lookup2 (q_token->tok_string);
		 */

		/* So I replaced it with the following, don't know
		   why we don't do a HSH_lookup2 in any case, but so it may be.
		   FSG 16.Nov.2000
		 */
		if ((symbol == NULL))
			symbol = HSH_lookup2(q_token->tok_string);


		for (temp_symbol = symbol; temp_symbol;
			 temp_symbol = temp_symbol->sym_homonym) {
			if (temp_symbol->sym_type == SYM_context) {
				symbol = temp_symbol;
				break;
			}
			else if (temp_symbol->sym_type == SYM_relation) {
				symbol = temp_symbol;
				continue;
			}
			else if (temp_symbol->sym_type == SYM_procedure) {
				if (symbol->sym_type == SYM_relation)
					continue;
				else
					symbol = temp_symbol;
			}
		}


		field = NULL;
		if (symbol->sym_type == SYM_relation) {
			if ((GPRE_REL) symbol->sym_object == context->ctx_relation)
				field = MET_field(context->ctx_relation, f_token->tok_string);
		}
		else if (symbol->sym_type == SYM_procedure) {
			if ((GPRE_PRC) symbol->sym_object == context->ctx_procedure)
				field = MET_context_field(context, f_token->tok_string);
		}
		else if (symbol->sym_type == SYM_context
				 && symbol->sym_object == context) field =
				MET_context_field(context, f_token->tok_string);
	}


	if (field && found_context)
		*found_context = context;

//  Check for valid array field  
//  Check dimensions 
//  Set remaining fields for slice 
	if ((slice_req = (GPRE_REQ) node->nod_arg[2]) &&
		(slice = slice_req->req_slice) && slice_action) {
		slice = slice_req->req_slice;
		if (!(ary_info = field->fld_array_info))
			SYNTAX_ERROR("<array column>");
		if (ary_info->ary_dimension_count != slice->slc_dimensions)
			PAR_error("subscript count mismatch");
		slice->slc_field = field;
		slice->slc_parent_request = context->ctx_request;

		/* The action type maybe ACT_get_slice or ACT_put_slice 
		   set as a place holder */

		action = MAKE_ACTION(slice_req, ACT_get_slice);
		action->act_object = (REF) slice;
		*slice_action = action;
	}
	else if ((slice_req = (GPRE_REQ) node->nod_arg[2]) && slice_action) {
		/* The action type maybe ACT_get_slice or ACT_put_slice
		   set as a place holder */

		action = MAKE_ACTION(slice_req, ACT_get_slice);
		*slice_action = action;
	}

	return field;
}


//____________________________________________________________
//  
//		Attempt to resolve an asterisk in a context.
//		If successful, return the context.  Otherwise return NULL.
//  

static GPRE_CTX resolve_asterisk( TOK q_token, RSE rse)
{
	GPRE_CTX context;
	RSE rs_stream;
	SYM symbol;
	int i;

	for (i = 0; i < rse->rse_count; i++) {
		context = rse->rse_context[i];
		if (rs_stream = context->ctx_stream) {
			if (context = resolve_asterisk(q_token, rs_stream))
				return context;
			continue;
		}
		symbol = HSH_lookup(q_token->tok_string);
		for (; symbol; symbol = symbol->sym_homonym)
			if (symbol->sym_type == SYM_relation &&
				(GPRE_REL) symbol->sym_object == context->ctx_relation)
				return context;
			else if (symbol->sym_type == SYM_procedure &&
					 (GPRE_PRC) symbol->sym_object == context->ctx_procedure)
				return context;
			else if (symbol->sym_type == SYM_context &&
					 (GPRE_CTX) symbol->sym_object == context)
				return context;
	}

	return NULL;
}


//____________________________________________________________
//  
//		Set field reference for any host variables in expr to field_ref.
//  

static void set_ref( GPRE_NOD expr, GPRE_FLD field_ref)
{
	GPRE_NOD *ptr, *end;
	REF ref;

	assert_IS_NOD(expr);

	ref = (REF) expr->nod_arg[0];
	switch (expr->nod_type) {
	case nod_value:
		ref->ref_field = field_ref;
		break;

	case nod_agg_count:
	case nod_agg_max:
	case nod_agg_min:
	case nod_agg_total:
	case nod_agg_average:
	case nod_plus:
	case nod_minus:
	case nod_times:
	case nod_divide:
	case nod_negate:
	case nod_upcase:
	case nod_concatenate:
	case nod_cast:
		for (ptr = expr->nod_arg, end = ptr + expr->nod_count; ptr < end;
			 ptr++)
			set_ref(*ptr, field_ref);
		break;
// ** Begin date/time/timestamp support *
	case nod_extract:
		set_ref(expr->nod_arg[1], field_ref);
		break;
// ** End date/time/timestamp support *
	}
}


//____________________________________________________________
//  
//		Return the uppercase version of
//		the input string.
//  

static char *upcase_string( char *p)
{
	char c, *q, *s;
	USHORT l;

	l = 0;
	s = (char *) ALLOC(strlen(p) + 1);
	q = s;

	while ((c = *p++) && (++l <= NAME_SIZE)) {
		*q++ = UPPER7(c);
	}
	*q = 0;

	return s;
}


//____________________________________________________________
//  
//		validate that top level field references
//		in a select with a group by, real or imagined,
//		resolve to grouping fields.  Ignore constants
//		and aggregates.	  If there's no group_by list,
//		then it's an imaginary group by (a top level
//		aggregation, and nothing can be referenced
//		directly.	
//  

static BOOLEAN validate_references( GPRE_NOD fields, GPRE_NOD group_by)
{
	GPRE_NOD *ptr, *end, node;
	REF fref, gref;
	RSE any;
	BOOLEAN invalid, context_match;
	MEL element;

	assert_IS_NOD(fields);
	assert_IS_NOD(group_by);

	if (!fields)
		return FALSE;

	invalid = FALSE;
	if (fields->nod_type == nod_field) {
		if (!group_by)
			return TRUE;
		fref = (REF) fields->nod_arg[0];

		context_match = FALSE;
		for (ptr = group_by->nod_arg, end = ptr + group_by->nod_count;
			 ptr < end; ptr++) {
			gref = (REF) (*ptr)->nod_arg[0];
			if (gref->ref_context == fref->ref_context) {
				if (gref->ref_field == fref->ref_field)
					return FALSE;
				context_match = TRUE;
			}
		}
		return context_match;
	}

	if (fields->nod_type == nod_agg_count ||
		fields->nod_type == nod_agg_max ||
		fields->nod_type == nod_agg_min ||
		fields->nod_type == nod_agg_total ||
		fields->nod_type == nod_agg_average ||
		fields->nod_type == nod_aggregate) return FALSE;

	if (fields->nod_type == nod_any || fields->nod_type == nod_ansi_any ||
		fields->nod_type == nod_ansi_all) {
		any = (RSE) fields->nod_arg[0];
		return validate_references(any->rse_boolean, group_by);
	}

	if ((fields->nod_type == nod_gen_id) || (fields->nod_type == nod_udf))
		return validate_references(fields->nod_arg[0], group_by);

	for (ptr = fields->nod_arg, end = ptr + fields->nod_count; ptr < end;
		 ptr++)
		switch ((*ptr)->nod_type) {
		case nod_map_ref:
			element = (MEL) (*ptr)->nod_arg[0];
			node = element->mel_expr;
			if (node->nod_type != nod_agg_count &&
				node->nod_type != nod_agg_max &&
				node->nod_type != nod_agg_min &&
				node->nod_type != nod_agg_total &&
				node->nod_type != nod_agg_average &&
				node->nod_type != nod_aggregate)
					invalid |= validate_references(node, group_by);
			break;

		case nod_field:
		case nod_plus:
		case nod_minus:
		case nod_times:
		case nod_negate:
		case nod_divide:
		case nod_and:
		case nod_like:
		case nod_missing:
		case nod_not:
		case nod_or:
		case nod_eq:
		case nod_ne:
		case nod_gt:
		case nod_ge:
		case nod_le:
		case nod_lt:
		case nod_upcase:
		case nod_concatenate:
		case nod_cast:
			invalid |= validate_references(*ptr, group_by);
			break;
		}

	return invalid;
}


static void dialect1_bad_type(USHORT field_dtype)
{
	char buffer[200];
	char *s;

	switch (field_dtype) {
	case dtype_sql_date:
		s = "SQL DATE";
		break;
	case dtype_sql_time:
		s = "SQL TIME";
		break;
	case dtype_int64:
		s = "64-bit numeric";
		break;
	}
	sprintf(buffer,
			"Client SQL dialect 1 does not support reference to the %s datatype",
			s);
	PAR_error(buffer);
}


} // extern "C"
