//____________________________________________________________
//
//		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): ______________________________________.
//
//  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)
//
//  Mike Nordell		- Reduce compiler warnings
//  Stephen W. Boyd		- Added support for new features
//____________________________________________________________
//

#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"
#include "../common/utils_proto.h"


struct scope
{
	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
};

static bool compare_expr(const gpre_nod*, const gpre_nod*);
static gpre_nod* copy_fields(gpre_nod*, map*);
static gpre_nod* explode_asterisk(gpre_nod*, int, gpre_rse*);
static gpre_nod* explode_asterisk_all(gpre_nod*, int, gpre_rse*, bool);
static gpre_fld* get_ref(gpre_nod*);
static gpre_nod* implicit_any(gpre_req*, gpre_nod*, nod_t, nod_t);
static gpre_nod* merge(gpre_nod*, gpre_nod*);
static gpre_nod* merge_fields(gpre_nod*, gpre_nod*, int, bool);
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*, const TEXT*);
static gpre_nod* par_and(gpre_req*, USHORT *);
static gpre_rel* par_base_table(gpre_req*, const gpre_rel*, const TEXT*);
static gpre_nod* par_case(gpre_req*);
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*);
static gpre_ctx* par_join_clause(gpre_req*, gpre_ctx*);
static nod_t par_join_type();
static gpre_nod* par_multiply(gpre_req*, bool, USHORT *, bool *);
static gpre_nod* par_not(gpre_req*, USHORT *);
static gpre_nod* par_nullif(gpre_req*);
static void par_order(gpre_req*, gpre_rse*, bool, bool);
static gpre_nod* par_plan(gpre_req*);
static gpre_nod* par_plan_item(gpre_req*, bool, USHORT *, bool *);
static gpre_nod* par_primitive_value(gpre_req*, bool, USHORT *, bool *);
static gpre_nod* par_relational(gpre_req*, USHORT *);
static gpre_rse* par_rse(gpre_req*, gpre_nod*, bool);
static gpre_rse* par_select(gpre_req*, gpre_rse*);
static gpre_nod* par_stat(gpre_req*);
static gpre_nod* par_subscript(gpre_req*);
static gpre_nod* par_substring(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*, bool);
static gpre_nod* par_udf_or_field_with_collate(gpre_req*, bool, USHORT *, bool *);
static void par_update(gpre_rse*, bool, bool);
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*, scope*);
static void push_scope(gpre_req*, scope*);
static gpre_fld* resolve(gpre_nod*, gpre_ctx*, gpre_ctx**, act**);
static bool resolve_fields(gpre_nod*& fields, gpre_rse* selection);
static gpre_ctx* resolve_asterisk(const tok*, gpre_rse*);
static void set_ref(gpre_nod*, gpre_fld*);
static char* upcase_string(const char*);
static bool validate_references(const gpre_nod*, const gpre_nod*);




struct ops
{
	nod_t rel_op;
	kwwords_t rel_kw;
	nod_t rel_negation;
};

static const 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 const 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 const 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 const 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_equiv, nod_between, nod_like,
	nod_and, nod_or, nod_not, nod_ansi_any, nod_ansi_all, nod_nothing
};


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

gpre_nod* SQE_boolean( gpre_req* request, USHORT* paren_count)
{
	USHORT local_count;

	assert_IS_REQ(request);

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

	gpre_nod* expr1 = par_and(request, paren_count);

	if (!MSC_match(KW_OR) && !MSC_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)
{
	SCHAR r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];
	SCHAR s[ERROR_LENGTH];

	assert_IS_REQ(request);

	gpre_ctx* context = MSC_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
		gpre_prc* procedure = context->ctx_procedure =
			SQL_procedure(request, r_name, db_name, owner_name, false);
		if (procedure)
		{
			if (procedure->prc_inputs)
			{
				if (!MSC_match(KW_LEFT_PAREN))
					CPR_s_error("( <procedure input parameters> )");
				// parse input references
				context->ctx_prc_inputs = SQE_list(SQE_value, request, false);
				USHORT 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");
				}
				gpre_nod** input = context->ctx_prc_inputs->nod_arg;
				for (gpre_fld* 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.

	gpre_sym* symbol = gpreGlob.token_global.tok_symbol;
	if (symbol && symbol->sym_type == SYM_keyword)
	{
		if (!gpreGlob.token_global.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

	gpre_ctx* conflict;
	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, gpreGlob.token_global.tok_string) &&
			conflict->ctx_scope_level == request->req_scope_level)
		{
			break;
		}
	}

	if (conflict)
	{
		const char* error_type;
		switch (symbol->sym_type)
		{
		case SYM_relation:
			error_type = "table";
			break;
		case SYM_procedure:
			error_type = "procedure";
			break;
		default:
			error_type = "context";
		}

		fb_utils::snprintf(s, sizeof(s), "context %s conflicts with a %s in the same statement",
				gpreGlob.token_global.tok_string, error_type);
		PAR_error(s);
	}

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

	PAR_get_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, bool aster_ok)
{
	gpre_req* slice_req;

	assert_IS_REQ(request);

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

	if (aster_ok && MSC_match(KW_ASTERISK))
	{
		gpre_nod* node = MSC_node(nod_asterisk, 1);
		return node;
	}

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

	SQL_resolve_identifier("<column name>", NULL, NAME_SIZE);

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

	TEXT s[ERROR_LENGTH];

	act* action;
	if (request && request->req_type == REQ_ddl && (action = request->req_actions) &&
		(action->act_type == ACT_create_domain || action->act_type == ACT_alter_domain))
	{
		fb_utils::snprintf(s, sizeof(s), "Illegal use of identifier: %s in domain constraint",
				gpreGlob.token_global.tok_string);
		PAR_error(s);
	}


	// Note: We *always* want to make a deferred 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 deferred name block

	if (!request || !request->req_contexts || request->req_in_select_list)
	{
		gpre_nod* node = MSC_node(nod_deferred, 3);
		node->nod_count = 0;
		tok* f_token = (tok*) MSC_alloc(TOK_LEN);
		node->nod_arg[0] = (gpre_nod*) f_token;
		f_token->tok_length = gpreGlob.token_global.tok_length;
		SQL_resolve_identifier("<identifier>", f_token->tok_string, f_token->tok_length + 1);
		CPR_token();

		if (MSC_match(KW_DOT))
		{
			if (gpreGlob.token_global.tok_keyword == 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*) MSC_alloc(TOK_LEN);
				node->nod_arg[0] = (gpre_nod*) f_token;
				f_token->tok_length = gpreGlob.token_global.tok_length;
				SQL_resolve_identifier("<identifier>", f_token->tok_string, f_token->tok_length + 1);
			}
			CPR_token();
		}
		if (MSC_match(KW_L_BRCKET))
		{
			// We have a complete array or an array slice here

			if (!MSC_match(KW_R_BRCKET))
			{
				slice_req = MSC_request(REQ_slice);
				int count = 0;
				do {
					count++;
					gpre_nod* tail = par_subscript(slice_req);
					MSC_push(tail, &lower_dim);
					if (MSC_match(KW_COLON))
					{
						//if (!MSC_match (KW_DOT))
						//	CPR_s_error ("<period>");
						tail = par_subscript(slice_req);
						MSC_push(tail, &upper_dim);
					}
					else
						MSC_push(tail, &upper_dim);
				} while (MSC_match(KW_COMMA));

				if (!MSC_match(KW_R_BRCKET))
					CPR_s_error("<right bracket>");
				slc* slice = (slc*) MSC_alloc(SLC_LEN(count));
				slice_req->req_slice = slice;
				slc::slc_repeat* tail_ptr = &slice->slc_rpt[count];
				slice->slc_dimensions = count;
				slice->slc_parent_request = request;
				while (lower_dim)
				{
					--tail_ptr;
					tail_ptr->slc_lower = MSC_pop(&lower_dim);
					tail_ptr->slc_upper = MSC_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.cpp

				node->nod_count = 3;
			}
			else
			{
				slice_req = (gpre_req*) MSC_alloc(REQ_LEN);
				slice_req->req_type = REQ_slice;
				node->nod_arg[2] = (gpre_nod*) slice_req;
			}
		}

		return node;
	}

	gpre_ctx* context;
	ref* reference = (ref*) MSC_alloc(REF_LEN);
	gpre_nod* node = MSC_unary(nod_field, (gpre_nod*) reference);

	gpre_sym* symbol = gpreGlob.token_global.tok_symbol;
	if (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 (gpre_sym* temp_symbol = symbol; temp_symbol; temp_symbol = temp_symbol->sym_homonym)
			{
				if (temp_symbol->sym_type == SYM_context)
				{
					symbol = temp_symbol;
					break;
				}
				if (temp_symbol->sym_type == SYM_relation)
				{
					symbol = temp_symbol;
					continue;
				}
				if (temp_symbol->sym_type == SYM_procedure)
				{
					if (symbol->sym_type != SYM_relation)
						symbol = temp_symbol;

					continue;
				}
			}

			if (symbol->sym_type == SYM_context)
			{
				context = symbol->sym_object;
				CPR_token();
				if (!MSC_match(KW_DOT))
					CPR_s_error("<period> in qualified column");
				if (context->ctx_request != request)
					PAR_error("context not part of this request");
				SQL_resolve_identifier("<Column Name>", NULL, NAME_SIZE);
				if (!(reference->ref_field =
					MET_context_field(context, gpreGlob.token_global.tok_string)))
				{
					sprintf(s, "column \"%s\" not in context", gpreGlob.token_global.tok_string);
					PAR_error(s);
				}
				if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect)
				{
					const USHORT field_dtype = reference->ref_field->fld_dtype;
					if (dtype_sql_date == field_dtype ||
						dtype_sql_time == field_dtype ||
						dtype_int64 == field_dtype)
					{
						SQL_dialect1_bad_type(field_dtype);
					}
				}
				reference->ref_context = context;
				CPR_token();
				return node;
			}

			if (symbol->sym_type == SYM_relation)
			{
				const gpre_rel* 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 (!MSC_match(KW_DOT))
				{
					hold_token = gpreGlob.token_global;
					gpreGlob.token_global = gpreGlob.prior_token;
					gpreGlob.token_global.tok_symbol = 0;
				}
				else
				{
					// We've got the column name. resolve it
					SQL_resolve_identifier("<Columnn Name>", NULL, NAME_SIZE);
					for (context = request->req_contexts; context; context = context->ctx_next)
					{
						if (context->ctx_relation == relation &&
							(reference->ref_field =
								 MET_field(context->ctx_relation, gpreGlob.token_global.tok_string)))
						{
							if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect)
							{
								const USHORT field_dtype = reference->ref_field->fld_dtype;
								if (dtype_sql_date == field_dtype ||
									dtype_sql_time == field_dtype ||
									dtype_int64 == field_dtype)
								{
									SQL_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;
						}
					}

					fb_utils::snprintf(s, sizeof(s), "column \"%s\" not in context",
							gpreGlob.token_global.tok_string);
					PAR_error(s);
				}
			}
			else if (symbol->sym_type == SYM_procedure)
			{
				const gpre_prc* procedure = (gpre_prc*) symbol->sym_object;
				if (procedure->prc_database != request->req_database)
					PAR_error("procedure not in appropriate database");
				CPR_token();
				if (!MSC_match(KW_DOT))
					CPR_s_error("<period> in qualified column");
				SQL_resolve_identifier("<Column Name>", NULL, NAME_SIZE);
				for (context = request->req_contexts; context; context = context->ctx_next)
				{
					if (context->ctx_procedure == procedure &&
						(reference->ref_field =
							 MET_context_field(context, gpreGlob.token_global.tok_string)))
					{
						if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect)
						{
							const USHORT field_dtype = reference->ref_field->fld_dtype;
							if (dtype_sql_date == field_dtype ||
								dtype_sql_time == field_dtype ||
								dtype_int64 == field_dtype)
							{
								SQL_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;
					}
				}

				fb_utils::snprintf(s, sizeof(s),
					"column \"%s\" not in context", gpreGlob.token_global.tok_string);
				PAR_error(s);
			}
		}
	}

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

	SQL_resolve_identifier("<Column Name>", NULL, NAME_SIZE);
	for (context = request->req_contexts; context; context = context->ctx_next)
	{
		if (reference->ref_field = MET_context_field(context, gpreGlob.token_global.tok_string))
		{
			if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect)
			{
				const USHORT field_dtype = reference->ref_field->fld_dtype;
				if (dtype_sql_date == field_dtype ||
					dtype_sql_time == field_dtype ||
					dtype_int64 == field_dtype)
				{
					SQL_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)
			{
				gpreGlob.prior_token = gpreGlob.token_global;
				gpreGlob.token_global = 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;
		}
	}

	CPR_s_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, bool aster_ok)
{
	assert_IS_REQ(request);

	gpre_lls* stack = NULL;
	int count = 0;

	do {
		count++;
		MSC_push((*routine) (request, aster_ok, NULL, NULL), &stack);
	} while (MSC_match(KW_COMMA));

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

	while (stack)
		*--ptr = (gpre_nod*) MSC_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)
{
	ref* reference;
	SCHAR* string;

	assert_IS_REQ(request);

	if (gpreGlob.token_global.tok_type == tok_number)
	{
		reference = (ref*) MSC_alloc(REF_LEN);
		string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
		reference->ref_value = string;
		reference->ref_flags |= REF_literal;
		CPR_token();
		return reference;
	}

	if ((isQuoted(gpreGlob.token_global.tok_type) && gpreGlob.sw_sql_dialect == 1) ||
		gpreGlob.token_global.tok_type == tok_sglquoted)
	{
		// 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*) MSC_alloc(REF_LEN);
		string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 3);
		string[0] = '\"';
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string + 1);
		string[gpreGlob.token_global.tok_length + 1] = '\"';
		string[gpreGlob.token_global.tok_length + 2] = 0;
		reference->ref_value = string;
		reference->ref_flags |= REF_literal;
		CPR_token();
		return reference;
	}

	if (gpreGlob.token_global.tok_keyword == KW_PLUS || gpreGlob.token_global.tok_keyword == KW_MINUS)
	{
		int sign = 0;
		if (gpreGlob.token_global.tok_keyword == KW_MINUS)
			sign = 1;

		CPR_token();
		if (gpreGlob.token_global.tok_type != tok_number)
			CPR_s_error("<host variable> or <constant>");
		reference = (ref*) MSC_alloc(REF_LEN);
		char* s = string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 1 + sign);
		if (sign)
			*s++ = '-';
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, s);
		reference->ref_value = string;
		reference->ref_flags |= REF_literal;
		CPR_token();
		return reference;
	}

	if (!MSC_match(KW_COLON))
		CPR_s_error("<host variable> or <constant>");

	if (gpreGlob.token_global.tok_type != tok_ident)
		CPR_s_error("<host variable> or <constant>");

	reference = (ref*) MSC_alloc(REF_LEN);

	// This loop is a waste of time because the flag SYM_variable is never activated
	for (gpre_sym* symbol = gpreGlob.token_global.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);

	MSC_match(KW_INDICATOR);

	if (MSC_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)
{
	if (!input || !field)
		return;

	assert_IS_NOD(input);

	switch (input->nod_type)
	{
	case nod_value:
		{
			ref* 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_deferred:
	case nod_array:
		return;

	case nod_map_ref:
		{
			mel* element = (mel*) input->nod_arg[0];
			gpre_nod* node = element->mel_expr;
			SQE_post_field(node, field);
			return;
		}

	default:
		{
			gpre_nod** ptr = input->nod_arg;
			for (const gpre_nod* const* const 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*) MSC_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*) MSC_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.
//

bool SQE_resolve(gpre_nod** node_ptr, gpre_req* request, gpre_rse* selection)
{
	bool result = false;
	act* slice_action = 0;

	gpre_nod* node = *node_ptr;

	assert_IS_REQ(request);
	assert_IS_NOD(node);

	switch (node->nod_type)
	{
	case nod_plus:
	case nod_minus:
	case nod_times:
	case nod_divide:
	case nod_negate:
	case nod_and:
	case nod_or:
	case nod_not:
	case nod_eq:
	case nod_ne:
	case nod_ge:
	case nod_gt:
	case nod_le:
	case nod_lt:
	case nod_containing:
	case nod_like:
	case nod_between:
	case nod_starting:
	case nod_upcase:
	case nod_lowcase:
	case nod_concatenate:
	case nod_cast:
	case nod_case:
	case nod_case1:
	case nod_substring:
		{
			gpre_nod** ptr = node->nod_arg;
			const gpre_nod* const* const end = ptr + node->nod_count;
			for (; ptr < end; ptr++)
				result |= SQE_resolve(ptr, request, selection);
			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, selection);
			gpre_nod* node_arg = node->nod_arg[0];
			const ref* 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])
		{
			gpre_nod** ptr = node->nod_arg[0]->nod_arg;
			const gpre_nod* const* const end = ptr + node->nod_arg[0]->nod_count;
			for (; ptr < end; ptr++)
				result |= SQE_resolve(ptr, request, selection);
		}
		return result;

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

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

	case nod_coalesce:
		for (int i = 0; i < node->nod_count; i++)
			result |= SQE_resolve(&node->nod_arg[0]->nod_arg[i], request, selection);
		return result;

	case nod_deferred:
		break;

	default:
		return false;
	}

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

	gpre_fld* field = NULL;

	gpre_ctx* context = 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 (SSHORT i = 0; i < selection->rse_count; i++)
		{
			if (field = resolve(node, selection->rse_context[i], &context, &slice_action))
				break;
		}
	}

	if (!field)
	{
		SCHAR s[ERROR_LENGTH];
		if (q_token)
			fb_utils::snprintf(s, sizeof(s), "column \"%s.%s\" cannot be resolved",
					q_token->tok_string, f_token->tok_string);
		else
			fb_utils::snprintf(s, sizeof(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 == gpreGlob.sw_sql_dialect) &&
		(dtype_sql_date == field->fld_dtype || dtype_sql_time == field->fld_dtype ||
			dtype_int64 == field->fld_dtype))
	{
		SQL_dialect1_bad_type(field->fld_dtype);
	}

	ref* reference = (ref*) MSC_alloc(REF_LEN);
	reference->ref_field = field;
	reference->ref_context = context;
	reference->ref_slice = (slc*) slice_action;

	*node_ptr = MSC_unary(nod_field, (gpre_nod*) reference);

	return false;
}


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

gpre_rse* SQE_select(gpre_req* request, bool view_flag)
{
	gpre_lls* context_stack = NULL;
	gpre_ctx* context = 0;
	bool have_union = false;

	assert_IS_REQ(request);

	map* const old_map = request->req_map;

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

	gpre_rse* select = NULL;
	gpre_rse* rse1 = NULL;
	select = rse1 = par_select(request, NULL);

	// "Look for ... the UNION label ... "
	while (MSC_match(KW_UNION))
	{
		have_union = true;
		const bool union_all = MSC_match(KW_ALL);
		if (!MSC_match(KW_SELECT))
			CPR_s_error("SELECT");

		MSC_push((gpre_nod*) request->req_contexts, &context_stack);
		request->req_contexts = NULL;
		request->req_map = NULL;
		gpre_rse* 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 = (gpre_rse*) MSC_alloc(RSE_LEN(1));
		select->rse_context[0] = context = MSC_context(request);
		gpre_nod* node = MSC_node(nod_union, 2);
		select->rse_union = node;
		node->nod_arg[0] = (gpre_nod*) rse1;
		node->nod_arg[1] = (gpre_nod*) rse2;

		map* new_map = (map*) MSC_alloc(sizeof(map));
		rse1->rse_map = new_map;
		new_map->map_context = context;
		select->rse_fields = post_select_list(rse1->rse_fields, new_map);

		rse2->rse_map = new_map = (map*) MSC_alloc(sizeof(map));
		new_map->map_context = context;
		post_select_list(rse2->rse_fields, new_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*) MSC_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;
	par_update(select, have_union, view_flag);
	request->req_map = old_map;

	return select;
}


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

gpre_nod* SQE_value(gpre_req* request, bool aster_ok, USHORT* paren_count, bool* bool_flag)
{
	USHORT local_count;
	bool 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;
	}

	MSC_match(KW_PLUS);
	gpre_nod* 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;
	}

	nod_t nod_type;
	while (true)
	{
		if (MSC_match(KW_PLUS))
			nod_type = nod_plus;
		else if (MSC_match(KW_MINUS))
			nod_type = nod_minus;
		else if (MSC_match(KW_OR1))
			nod_type = nod_concatenate;
		else
		{
			par_terminating_parens(paren_count, &local_count);
			return node;
		}
		node = MSC_binary(nod_type, node, 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, bool aster_ok, USHORT* paren_count, bool* bool_flag)
{
	if (MSC_match(KW_NULL))
		return MSC_node(nod_null, 0);

	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".
//

gpre_nod* SQE_variable(gpre_req* request, bool /*aster_ok*/, USHORT* /*paren_count*/, bool* /*bool_flag*/)
{
	assert_IS_REQ(request);

	if (!MSC_match(KW_COLON))
		CPR_s_error("<colon>");

	if (isQuoted(gpreGlob.token_global.tok_type))
		CPR_s_error("<host variable>");

	ref* reference = (ref*) MSC_alloc(REF_LEN);

	// This loop is a waste of time because the flag SYM_variable is never activated
	for (gpre_sym* symbol = gpreGlob.token_global.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);

	MSC_match(KW_INDICATOR);

	if (MSC_match(KW_COLON))
		reference->ref_null_value = PAR_native_value(false, false);

	return (gpre_nod*) reference;
}


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

static bool compare_expr(const gpre_nod* node1, const gpre_nod* node2)
{
	assert_IS_NOD(node1);
	assert_IS_NOD(node2);

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

	switch (node1->nod_type)
	{
	case nod_field:
		{
			const ref* ref1 = (ref*) node1->nod_arg[0];
			const ref* ref2 = (ref*) node2->nod_arg[0];
			return ref1->ref_context == ref2->ref_context && ref1->ref_field == ref2->ref_field &&
				ref1->ref_master == ref2->ref_master;
		}

	case nod_map_ref:
		return node1->nod_arg[0] == node2->nod_arg[0];

	case nod_udf:
	case nod_gen_id:
		return node1->nod_arg[0] == node2->nod_arg[0] && node1->nod_arg[1] == node2->nod_arg[1];

	default:
		return false;
	}
}


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

static gpre_nod* copy_fields( gpre_nod* fields, map* fields_map)
{
	assert_IS_NOD(fields);

	gpre_nod* list = MSC_node(nod_list, fields->nod_count);

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

	return list;
}


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

static gpre_nod* explode_asterisk( gpre_nod* fields, int n, gpre_rse* selection)
{
	TEXT s[ERROR_LENGTH];

	assert_IS_NOD(fields);

	gpre_nod* node = fields->nod_arg[n];
	tok* q_token = (tok*) node->nod_arg[0];
	if (q_token)
	{
		// expand for single relation
		gpre_ctx* context = resolve_asterisk(q_token, selection);
		if (context)
			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, selection, true);
	}

	return fields;
}


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

static gpre_nod* explode_asterisk_all(gpre_nod* fields,
									 int n,
									 gpre_rse* selection,
									 bool replace)
{
	assert_IS_NOD(fields);

	for (int i = 0; i < selection->rse_count; i++)
	{
		gpre_ctx* context = selection->rse_context[i];
		const int 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)
{
	gpre_fld* field;

	assert_IS_NOD(expr);

	if (expr->nod_type == nod_via || expr->nod_type == nod_cast)
	{
		field = (gpre_fld*) MSC_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;
	}

	ref* reference;

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


	case nod_array:
		reference = (ref*) expr->nod_arg[0];
		return reference->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_lowcase:
	case nod_concatenate:
		{
			gpre_nod** ptr = expr->nod_arg;
			for (const gpre_nod* const* const 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:
		{
			mel* element = (mel*) expr->nod_arg[0];
			gpre_nod* 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,
							 nod_t comparison,
							 nod_t any_all)
{
	gpre_nod* node;
	scope previous_scope;

	assert_IS_REQ(request);
	assert_IS_NOD(value);

	gpre_ctx* 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
	const bool distinct = (!MSC_match(KW_ALL) && MSC_match(KW_DISTINCT));

	request->req_in_select_list++;
	gpre_nod* value2 = SQE_value(request, false, NULL, NULL);
	request->req_in_select_list--;

	gpre_nod* field_list = MSC_node(nod_list, 1);
	field_list->nod_arg[0] = value2;

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

	gpre_rse* sub = selection->rse_aggregate;
	if (sub)
	{
		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]);
			selection->rse_boolean = merge(selection->rse_boolean, node);
			if (any_all == nod_ansi_all)
				node = MSC_node(nod_ansi_all, 1);
			else
				node = MSC_node(nod_ansi_any, 1);
			node->nod_count = 0;
			node->nod_arg[0] = (gpre_nod*) selection;
		}
		else
		{
			gpre_nod* node2 = MSC_node(nod_via, 3);
			node2->nod_count = 0;
			node2->nod_arg[0] = (gpre_nod*) selection;
			node2->nod_arg[2] = MSC_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]);
		selection->rse_boolean = merge(selection->rse_boolean, node);
		if (any_all == nod_ansi_all)
			node = MSC_node(nod_ansi_all, 1);
		else
			node = MSC_node(nod_ansi_any, 1);
		node->nod_count = 0;
		node->nod_arg[0] = (gpre_nod*) selection;
	}

	EXP_rse_cleanup(selection);

	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,
							 bool replace)
{
	assert_IS_NOD(fields_1);
	assert_IS_NOD(fields_2);

	int count = fields_1->nod_count + fields_2->nod_count;
	if (replace)
		count--;
	gpre_nod* fields = MSC_node(nod_list, (SSHORT) count);

	count = n;
	if (!replace)
		count++;

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

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

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

	for (int 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)
{
	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");
	}

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

	if (!field)
		return;

	set_ref(expr1, field);

	if (!expr2)
		return;

	gpre_fld* temp = get_ref(expr1);
	if (temp)
		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)
{
	assert_IS_REQ(request);
	assert_IS_NOD(alias_list);

	gpre_nod** arg = alias_list->nod_arg;
	const gpre_nod* const* const 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

	gpre_rel* relation = 0; // unreliable test many lines below without initializing.
	gpre_ctx* context = par_alias(request, (const TEXT*) *arg);
	if (context)
	{
		if (alias_list->nod_count == 1)
			return context;
		relation = context->ctx_relation;
	}

	SCHAR error_string[ERROR_LENGTH];

	// 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, (const TEXT*) *arg))
			{
				break;
			}
		}

		if (!context)
		{
			fb_utils::snprintf(error_string, sizeof(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, (const TEXT*) *arg)))
			break;
	}

	if (!relation)
	{
		fb_utils::snprintf(error_string, sizeof(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

	gpre_ctx* new_context = (gpre_ctx*) MSC_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

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

	TEXT* alias = (TEXT*) MSC_alloc(alias_length);

	// CVC: Warning: Using space as separator may conflict with dialect 3's embedded blanks;
	// but gpre is not much worried about dialects... except in this file!
	TEXT* p = new_context->ctx_alias = alias;
	for (arg = alias_list->nod_arg; arg < end; arg++)
	{
		for (const TEXT* 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, const TEXT* alias)
{
	SCHAR error_string[ERROR_LENGTH];

	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

	gpre_ctx* relation_context = NULL;
	for (gpre_ctx* 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)
		{
			const TEXT* p = context->ctx_alias;
			const TEXT* q = alias;
			for (; *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)
			{
				fb_utils::snprintf(error_string, sizeof(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, const gpre_rel* relation, const TEXT* alias)
{

	assert_IS_REQ(request);

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

//____________________________________________________________
//
//		Parse a CASE clause
//
//		There are two types of CASE clauses.
//			1) CASE WHEN (cond1) THEN value1 ... ELSE else_value END which is returned as nod_case
//			   In this case nod_arg[0] = cond1, nod_arg[1] = value1, ... nod_arg[n] = else_value
//			2) CASE value0 WHEN test_value1 THEN match_value1 ... ELSE else_value END which is
//			   returned as nod_case1
//			   In this case nod_arg[0] = value0, nod_arg[1] = test_value1, nod_arg[2] = match_value1, ...,
//			   nod_arg[n] = else_value
//
static gpre_nod* par_case(gpre_req* request)
{
	gpre_lls* stack = NULL;
	int count = 0;
	nod_t nod_type;

	if (MSC_match(KW_WHEN))
	{
		// Case 1 - CASE WHEN cond1 THEN value1 ...
		nod_type = nod_case;
		count++;
		MSC_push(SQE_boolean(request, NULL), &stack);

		while (MSC_match(KW_THEN))
		{
			count++;
			MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack);
			if (MSC_match(KW_WHEN))
			{
				count++;
				MSC_push(SQE_boolean(request, NULL), &stack);
			}
		}

		// If we have an odd number of nodes then we are missing a THEN
		if ((count % 2) == 1)
		{
			CPR_s_error("THEN");
		}
	}
	else
	{
		// Case 2 - CASE value0 WHEN test_value1 THEN match_value1 ...
		nod_type = nod_case1;
		count++;
		MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack);

		while (MSC_match(KW_WHEN))
		{
			count++;
			MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack);
			if (MSC_match(KW_THEN))
			{
				count++;
				MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack);
			}
			else
			{
				CPR_s_error("THEN");
			}
		}
	}

	// ELSE else_value END
	if (MSC_match(KW_ELSE))
	{
		count++;
		MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack);
	}

	if (! MSC_match(KW_END))
	{
		CPR_s_error("END");
	}

	// Return a node containing all of the expressions parsed as part of CASE
	gpre_nod* node = MSC_node(nod_type, (SSHORT) count);
	gpre_nod** p = &node->nod_arg[count - 1];
	while (stack)
	{
		*p-- = (gpre_nod*) MSC_pop(&stack);
	}

	return node;
}
//____________________________________________________________
//
//		Parse an AND boolean expression.
//

static gpre_nod* par_and( gpre_req* request, USHORT* paren_count)
{
	assert_IS_REQ(request);

	gpre_nod* expr1 = par_not(request, paren_count);

	if (!MSC_match(KW_AND))
		return expr1;

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


//____________________________________________________________
//
//

static gpre_nod* par_collate( gpre_req* request, gpre_nod* arg)
{
	assert_IS_REQ(request);
	assert_IS_NOD(arg);

	gpre_nod* node = MSC_node(nod_cast, 2);
	node->nod_count = 1;
	node->nod_arg[0] = arg;
	gpre_fld* field = (gpre_fld*) MSC_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* node;
	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 (MSC_match(KW_SELECT))
		node = implicit_any(request, value, nod_eq, nod_ansi_any);
	else
	{
		node = NULL;
		while (true)
		{
			gpre_nod* value2 = par_primitive_value(request, false, 0, NULL);
			if (value2->nod_type == nod_value)
			{
				ref* ref2 = (ref*) value2->nod_arg[0];
				if (value->nod_type == nod_field)
				{
					ref* ref1 = (ref*) value->nod_arg[0];
					ref2->ref_field = ref1->ref_field;
				}
				else
				{
					fb_utils::snprintf(s, sizeof(s), "datatype of %s can not be determined",
							gpreGlob.token_global.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 (!MSC_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* context1;

	assert_IS_REQ(request);

	if (MSC_match(KW_LEFT_PAREN))
	{
		context1 = par_joined_relation(request);
		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)
{
	assert_IS_REQ(request);

	const nod_t join_type = par_join_type();
	if (join_type == nod_nothing)
		return context1;

	gpre_ctx* context2 = par_joined_relation(request);
	if (!context2)
		CPR_s_error("<joined table clause>");

	if (!MSC_match(KW_ON))
		CPR_s_error("ON");

	gpre_nod* node = SQE_boolean(request, NULL);

	gpre_rse* selection = (gpre_rse*) MSC_alloc(RSE_LEN(2));
	selection->rse_count = 2;
	selection->rse_context[0] = context1;
	selection->rse_context[1] = context2;
	selection->rse_boolean = node;
	selection->rse_join_type = join_type;

	context1 = MSC_context(request);
	context1->ctx_stream = selection;

	return par_join_clause(request, context1);
}


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

static nod_t par_join_type()
{
	nod_t nod_type;

	if (MSC_match(KW_INNER))
		nod_type = nod_join_inner;
	else if (MSC_match(KW_LEFT))
		nod_type = nod_join_left;
	else if (MSC_match(KW_RIGHT))
		nod_type = nod_join_right;
	else if (MSC_match(KW_FULL))
		nod_type = nod_join_full;
	else if (MSC_match(KW_JOIN))
		return nod_join_inner;
	else
		return nod_nothing;

	if (nod_type != nod_join_inner)
		MSC_match(KW_OUTER);

	if (!MSC_match(KW_JOIN))
		CPR_s_error("JOIN");

	return nod_type;
}


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

static gpre_nod* par_multiply(gpre_req* request, bool aster_ok, USHORT* paren_count, bool* bool_flag)
{
	assert_IS_REQ(request);
	gpre_nod* node = par_primitive_value(request, aster_ok, paren_count, bool_flag);
	if (node->nod_type == nod_asterisk)
		return node;

	if (gpreGlob.token_global.tok_keyword == KW_COLLATE)
		return par_collate(request, node);

	nod_t nod_type;
	while (true)
	{
		if (MSC_match(KW_ASTERISK))
			nod_type = nod_times;
		else if (MSC_match(KW_SLASH))
			nod_type = nod_divide;
		else
			return node;

		node = MSC_binary(nod_type, node, 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)
{
	assert_IS_REQ(request);

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

	nod_t type = nod_nothing;

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

		EXP_left_paren(0);
		if (!MSC_match(KW_SELECT))
			CPR_s_error("SELECT");

		request->req_in_select_list++;
		gpre_nod* field;
		if (MSC_match(KW_ASTERISK))
			field = NULL;
		else if (!(field = par_udf(request)))
			field = SQE_field(request, false);
		request->req_in_select_list--;

		gpre_nod* node = MSC_node(type, 1);
		node->nod_count = 0;
		gpre_rse* selection = par_rse(request, 0, false);
		node->nod_arg[0] = (gpre_nod*) selection;
		if (field)
		{
			SQE_resolve(&field, 0, selection);
			gpre_nod* expr = MSC_unary(nod_missing, field);
			selection->rse_boolean = merge(negate(expr), selection->rse_boolean);
		}
		EXP_rse_cleanup((gpre_rse*) node->nod_arg[0]);
		pop_scope(request, &saved_scope);
		EXP_match_paren();
		return node;
	}

	return par_relational(request, paren_count);
}

//____________________________________________________________
//
//		Parse NULLIF built-in function.
//
//		NULLIF(exp1, exp2) is really just a shortcut for
//		CASE exp1 WHEN exp2 THEN NULL ELSE exp1 END, so
//		we generate a nod_case1 node.

static gpre_nod* par_nullif(gpre_req* request)
{
	gpre_nod* node = MSC_node(nod_case1, 4);
	EXP_left_paren(0);
	node->nod_arg[0] = SQE_value(request, false, NULL, NULL);
	if (! MSC_match(KW_COMMA))
		CPR_s_error("comma");
	node->nod_arg[1] = SQE_value(request, false, NULL, NULL);
	node->nod_arg[2] = MSC_node(nod_null, 0);
	node->nod_arg[3] = node->nod_arg[0];
	USHORT local_count = 1;
	par_terminating_parens(&local_count, &local_count);
	return node;
}

//____________________________________________________________
//
//		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, gpre_rse* select, bool union_f, bool view_flag)
{
	gpre_nod* sort;
	USHORT i;

	assert_IS_REQ(request);

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

	MSC_match(KW_BY);
	gpre_lls* items = NULL;
	gpre_lls* directions = NULL;
	int count = 0;
	bool direction = false;
	gpre_nod* values = select->rse_fields;

	while (true)
	{
		direction = false;
		if (gpreGlob.token_global.tok_type == tok_number)
		{
			i = EXP_USHORT_ordinal(false);
			if (i < 1 || i > values->nod_count)
				CPR_s_error("<ordinal column position>");
			sort = values->nod_arg[i - 1];
			PAR_get_token();
			if (gpreGlob.token_global.tok_keyword == KW_COLLATE)
				sort = par_collate(request, sort);
		}
		else
		{
			if (union_f)
				CPR_s_error("<column position in union>");
			sort = SQE_value(request, false, NULL, NULL);
			map* request_map;
			if (request && (request_map = request->req_map))
				sort = post_map(sort, request_map);
		}
		if (MSC_match(KW_ASCENDING))
			direction = false;
		else if (MSC_match(KW_DESCENDING))
			direction = true;
		count++;
		MSC_push((gpre_nod*)(IPTR) direction, &directions);
		MSC_push(sort, &items);
		if (!MSC_match(KW_COMMA))
			break;
	}

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

	while (items)
	{
		*--ptr = (gpre_nod*) MSC_pop(&items);
		*--ptr = (gpre_nod*) MSC_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)
{
	assert_IS_REQ(request);

	// parse the join type

	nod_t nod_type;
	if (MSC_match(KW_JOIN))
		nod_type = nod_join;
	else if (MSC_match(KW_MERGE))
		nod_type = nod_merge;
	else if (MSC_match(KW_SORT) && MSC_match(KW_MERGE))
		nod_type = nod_merge;
	else
		nod_type = nod_join;

	// make up the plan expression node

	gpre_nod* plan_expression = MSC_node(nod_plan_expr, 2);

	if (nod_type != nod_join)
		plan_expression->nod_arg[0] = MSC_node(nod_type, 0);

	// parse the plan items at this level

	EXP_left_paren(0);

	plan_expression->nod_arg[1] = SQE_list(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, bool /*aster_ok*/, USHORT* /*paren_count*/, bool* /*bool_flag*/)
{
	assert_IS_REQ(request);

	// check for a plan expression

	tok& token = gpreGlob.token_global;

	switch (token.tok_keyword)
	{
	case KW_JOIN:
	case KW_SORT:
	case KW_MERGE:
	case 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)

	int count;
	gpre_lls* stack = NULL;
	for (count = 0; token.tok_type == tok_ident; count++)
	{
		if (token.tok_keyword == KW_NATURAL || token.tok_keyword == KW_ORDER ||
			token.tok_keyword == KW_INDEX)
		{
			break;
		}

		MSC_push((gpre_nod*) upcase_string(token.tok_string), &stack);
		PAR_get_token();
	}

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

	gpre_nod** ptr;

	gpre_nod* alias_list = MSC_node(nod_list, (SSHORT) count);
	for (ptr = &alias_list->nod_arg[count]; stack;)
		*--ptr = (gpre_nod*) MSC_pop(&stack);

	// lookup the contexts for the aliases

	gpre_nod* index_list = NULL;
	gpre_ctx* context = par_alias_list(request, alias_list);

	// parse the access type

	gpre_nod* access_type = NULL;
	switch (token.tok_keyword)
	{
	case KW_NATURAL:
		access_type = MSC_node(nod_natural, 0);
		PAR_get_token();
		break;
	case KW_ORDER:
		access_type = MSC_node(nod_index_order, 1);
		access_type->nod_count = 0;
		PAR_get_token();

		if (token.tok_type != tok_ident)
			CPR_s_error("<index name>");
		access_type->nod_arg[0] = (gpre_nod*) upcase_string(token.tok_string);
		PAR_get_token();
		break;
	case KW_INDEX:
		access_type = MSC_node(nod_index, 1);
		access_type->nod_count = 0;
		PAR_get_token();

		EXP_left_paren(0);

		stack = NULL;
		for (count = 0; token.tok_type == tok_ident;)
		{
			MSC_push((gpre_nod*) upcase_string(token.tok_string), &stack);
			PAR_get_token();

			count++;

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

		index_list = MSC_node(nod_list, (SSHORT) count);
		access_type->nod_arg[0] = index_list;
		for (ptr = &index_list->nod_arg[count]; stack;)
			*--ptr = (gpre_nod*) MSC_pop(&stack);

		if (!EXP_match_paren())
			return NULL;
		break;
	default:
		CPR_s_error("NATURAL, ORDER, or INDEX");
	}


	// generate the plan item node

	gpre_nod* plan_item = MSC_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, bool aster_ok,
									 USHORT* paren_count, bool* bool_flag)
{
	USHORT local_count;

	assert_IS_REQ(request);

	gpre_nod* node = 0;

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

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

	if (MSC_match(KW_MINUS))
		return MSC_unary(nod_negate, par_primitive_value(request, false, paren_count, NULL));

	MSC_match(KW_PLUS);

	if (MSC_match(KW_USER)) {
		return MSC_node(nod_user_name, 0);
	}

	if (MSC_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");
		const act* 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 MSC_node(nod_dom_value, 0);
	}

	if (MSC_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.

	map* tmp_map = NULL;
	for (const ops* op = stat_ops; op->rel_kw != KW_none; op++)
	{
		MSC_match(KW_ALL);
		if (MSC_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 = MSC_node(op->rel_op, 2);
			node->nod_count = 1;
			EXP_left_paren("left parenthesis in statistical function");
			const bool distinct = MSC_match(KW_DISTINCT);
			if (request)
			{
				tmp_map = request->req_map;
				request->req_map = NULL;
				++request->req_in_aggregate;
			}
			if (node->nod_type == nod_agg_count && MSC_match(KW_ASTERISK))
				node->nod_count = 0;
			else
			{
				node->nod_arg[0] = SQE_value(request, false, NULL, NULL);
				// Disallow arrays as arguments to aggregate functions
				const gpre_nod* 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 (tmp_map)
					node = post_map(node, tmp_map);
				request->req_map = tmp_map;
				--request->req_in_aggregate;
			}
			return node;
		}
	}

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

	if (gpreGlob.token_global.tok_type == tok_number ||
		(isQuoted(gpreGlob.token_global.tok_type) && gpreGlob.sw_sql_dialect == 1) ||
		gpreGlob.token_global.tok_type == tok_sglquoted)
	{
		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 (gpreGlob.token_global.tok_keyword == 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;
		}
		ref* reference = (ref*) SQE_variable(request, false, NULL, NULL);
		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);

	// 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
	if (!node)
	{
		// Begin date/time/timestamp support
		const kwwords_t kw_word = gpreGlob.token_global.tok_keyword;

		if (MSC_match(KW_DATE) || MSC_match(KW_TIME) || MSC_match(KW_TIMESTAMP))
		{
			gpreGlob.token_global.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)
{
	assert_IS_REQ(request);

	bool local_flag = true;
	gpre_nod* expr1 = SQE_value(request, false, paren_count, &local_flag);
	if (gpreGlob.token_global.tok_keyword == KW_RIGHT_PAREN)
		return expr1;
	if (gpreGlob.token_global.tok_keyword == KW_SEMI_COLON)
	{
		for (const nod_t* relational_ops = relationals; *relational_ops != nod_nothing;
			relational_ops++)
		{
			if (expr1->nod_type == *relational_ops)
				return expr1;
		}
	}

	bool negation = false;
	if (MSC_match(KW_NOT))
		negation = true;

	// Check for one of the binary operators

	gpre_nod* node;
	if (MSC_match(KW_IN))
		node = par_in(request, expr1);
	else if (MSC_match(KW_BETWEEN))
	{
		node = MSC_node(nod_between, 3);
		node->nod_arg[0] = expr1;
		node->nod_arg[1] = SQE_value(request, false, NULL, NULL);
		MSC_match(KW_AND);
		node->nod_arg[2] = SQE_value(request, false, NULL, NULL);
		pair(node->nod_arg[0], node->nod_arg[1]);
		pair(node->nod_arg[0], node->nod_arg[2]);
	}
	else if (MSC_match(KW_LIKE))
	{
		node = MSC_node(nod_like, 3);
		node->nod_arg[0] = expr1;
		node->nod_arg[1] = SQE_value(request, false, NULL, NULL);
		pair(node->nod_arg[0], node->nod_arg[1]);
		if (MSC_match(KW_ESCAPE))
		{
			gpre_nod* expr2 = SQE_value(request, false, NULL, NULL);
			node->nod_arg[2] = expr2;
			if (expr2->nod_type == nod_value)
			{
				ref* 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 (MSC_match(KW_IS))
	{
		if (MSC_match(KW_NOT))
			negation = !negation;
		if (MSC_match(KW_NULL))
		{
			if (expr1->nod_type == nod_array)
				expr1->nod_type = nod_field;
			node = MSC_unary(nod_missing, expr1);
		}
		else if (MSC_match(KW_DISTINCT))
		{
			if (!MSC_match(KW_FROM))
				CPR_s_error("FROM");
			node = MSC_binary(nod_equiv, expr1, SQE_value(request, false, NULL, NULL));
			pair(node->nod_arg[0], node->nod_arg[1]);
		}
		else
			CPR_s_error("NULL or DISTINCT");
	}
	else
	{
		node = NULL;
		const ops* op;
		for (op = rel_ops; op->rel_kw != KW_none; op++)
		{
			if (MSC_match(op->rel_kw))
				break;
		}
		if (op->rel_kw == KW_none)
		{
			for (const nod_t* relational_ops = relationals;
				*relational_ops != nod_nothing; relational_ops++)
			{
				if (expr1->nod_type == *relational_ops)
					return expr1;
			}
			CPR_s_error("<relational operator>");
		}
		if (op->rel_kw == KW_STARTING)
			MSC_match(KW_WITH);
		if (MSC_match(KW_ANY))
		{
			if (!MSC_match(KW_LEFT_PAREN) || !MSC_match(KW_SELECT))
				CPR_s_error("<select clause> for ANY");
			node = implicit_any(request, expr1, op->rel_op, nod_any);
			EXP_match_paren();
		}
		else if (MSC_match(KW_ALL))
		{
			if (!MSC_match(KW_LEFT_PAREN) || !MSC_match(KW_SELECT))
				CPR_s_error("<select clause> for ALL");
			if (op->rel_negation == nod_any || op->rel_negation == nod_ansi_any ||
				op->rel_negation == nod_ansi_all)
			{
				CPR_s_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, NULL, NULL));
			pair(node->nod_arg[0], node->nod_arg[1]);
		}
	}

	return negation ? negate(node) : node;
}


// Out of alphabetical order.
static bool resolve_fields(gpre_nod*& fields, gpre_rse* selection)
{
	bool aggregate = false;

	gpre_nod** ptr = fields->nod_arg;
	int count = fields->nod_count;

	for (int i = 0; i < count; i++)
	{
		gpre_nod*& node = ptr[i];

		if (node->nod_type == nod_asterisk)
		{
			const int old_count = count;
			fields = explode_asterisk(fields, i, selection);
			count = fields->nod_count;
			i += count - old_count;
			ptr = fields->nod_arg;
		}
		else
		{
			aggregate |= SQE_resolve(&node, NULL, selection);
			pair(node, 0);
		}
	}

	return aggregate;
}


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

static gpre_rse* par_rse(gpre_req* request, gpre_nod* fields, bool distinct)
{
	gpre_lls* stack = NULL;

	assert_IS_REQ(request);
	assert_IS_NOD(fields);

	// Get list and count of relations

	if (!MSC_match(KW_FROM))
		CPR_s_error("FROM");

	int count = 0;
	gpre_ctx* context;
	do {
		if (context = par_joined_relation(request))
		{
			MSC_push((gpre_nod*) context, &stack);
			count++;
		}
		else
			return NULL;
	} while (MSC_match(KW_COMMA));

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

	gpre_rse* select = (gpre_rse*) MSC_alloc(RSE_LEN(count));
	select->rse_count = count;

	while (count--)
		select->rse_context[count] = (gpre_ctx*) MSC_pop(&stack);

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

	bool aggregate = false;

	if (fields)
		aggregate = resolve_fields(fields, select);

	select->rse_fields = fields;
	if (distinct)
		select->rse_reduced = fields;

	// Handle a boolean, if present

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

	if (MSC_match(KW_GROUP))
	{
		MSC_match(KW_BY);
		select->rse_group_by = SQE_list(par_udf_or_field_with_collate, request, false);
		gpre_nod** ptr = select->rse_group_by->nod_arg;
		for (const gpre_nod* const* const 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");
		gpre_rse* sub_rse = select;
		map* subselect_map = (map*) MSC_alloc(sizeof(map));
		sub_rse->rse_map = subselect_map;
		if (select->rse_group_by)
			request->req_map = subselect_map;
		subselect_map->map_context = MSC_context(request);
		select = (gpre_rse*) MSC_alloc(RSE_LEN(0));
		select->rse_aggregate = sub_rse;

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

		if (MSC_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 (MSC_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 gpre_rse* par_select( gpre_req* request, gpre_rse* union_rse)
{
	assert_IS_REQ(request);

	// Handle FIRST and SKIP clauses
	gpre_nod* rse_first = NULL;
	if (MSC_match(KW_FIRST))
	{
		rse_first = MSC_node(nod_list, 1);
		rse_first->nod_arg[0] = SQE_value(request, false, NULL, NULL);
	}

	gpre_nod* rse_skip = NULL;
	if (MSC_match(KW_SKIP))
	{
		rse_skip = MSC_node(nod_list, 1);
		rse_skip->nod_arg[0] = SQE_value(request, false, NULL, NULL);
	}

	// Handle the ALL and DISTINCT options

	const bool distinct = (!MSC_match(KW_ALL) && MSC_match(KW_DISTINCT));

	// Make select list out of select items

	++request->req_in_select_list;
	gpre_nod* 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.

	gpre_nod* into_list = NULL;
	if (!(request->req_flags & REQ_sql_declare_cursor))
	{
		into_list = MSC_match(KW_INTO) ? SQE_list(SQE_variable, request, false) : NULL;
	}

	gpre_rse* select = par_rse(request, s_list, distinct);

	if (rse_first)
		resolve_fields(rse_first, select);
	select->rse_sqlfirst = rse_first;

	if (rse_skip)
		resolve_fields(rse_skip, select);
	select->rse_sqlskip = rse_skip;

	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)
{
	assert_IS_REQ(request);

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

	const bool distinct = (!MSC_match(KW_ALL) && MSC_match(KW_DISTINCT));

	request->req_in_select_list++;
	gpre_nod* item = par_udf(request);
	if (!item)
		item = SQE_value(request, false, NULL, NULL);
	request->req_in_select_list--;

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

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

	gpre_nod* node = MSC_node(nod_via, 3);
	node->nod_count = 0;
	node->nod_arg[0] = (gpre_nod*) select;
	node->nod_arg[2] = MSC_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)
{
	assert_IS_REQ(request);

	ref* reference = (ref*) MSC_alloc(REF_LEN);
	gpre_nod* node = MSC_unary(nod_value, (gpre_nod*) reference);

	// Special case literals

	if (gpreGlob.token_global.tok_type == tok_number)
	{
		node->nod_type = nod_literal;
		char* string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		reference->ref_value = string;
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
		PAR_get_token();
		return node;
	}

	if (!MSC_match(KW_COLON))
		CPR_s_error("<colon>");

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

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

	return node;
}

//____________________________________________________________
//
//		Parse the SUBSTRING built-in function
//
static gpre_nod* par_substring(gpre_req* request)
{
	gpre_nod* node = MSC_node(nod_substring, 3);
	EXP_left_paren(0);
	node->nod_arg[0] = SQE_value(request, false, NULL, NULL);
	if (! MSC_match(KW_FROM))
		CPR_s_error("FROM");
	node->nod_arg[1] = EXP_literal();
	if (! node->nod_arg[1])
		CPR_s_error("numeric literal");

	const ref* reference = (ref*) node->nod_arg[1]->nod_arg[0];
	const TEXT* string = reference->ref_value;

	if (*string == '"' || *string == '\'')
		CPR_s_error("numeric literal");

	if (MSC_match(KW_FOR))
	{
		node->nod_arg[2] = EXP_literal();
		if (! node->nod_arg[2])
			CPR_s_error("numeric literal");
		reference = (ref*) node->nod_arg[2]->nod_arg[0];
		string = reference->ref_value;
		if ((*string == '"') || (*string == '\''))
			CPR_s_error("numeric literal");
	}
	else
	{
		// No FOR clause given, fake up a numeric literal of 32767 (the max length
		// of a VarChar) to force copying to end of string.
		ref* reference1 = (ref*) MSC_alloc(REF_LEN);
		node->nod_arg[2] = MSC_unary(nod_literal, (gpre_nod*) reference1);
		string = (TEXT*) MSC_alloc(6);
		MSC_copy("32767", 5, (char*) string);
		reference1->ref_value = string;
	}
	USHORT local_count = 1;
	par_terminating_parens(&local_count, &local_count);
	return node;
}

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

static void par_terminating_parens(USHORT* paren_count, USHORT* local_count)
{
	// Suspicious condition.
	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)
{
	if (!request)
		return NULL;

	assert_IS_REQ(request);

	// Check for user defined functions
	// resolve only if an identifier
	if ((isQuoted(gpreGlob.token_global.tok_type)) || gpreGlob.token_global.tok_type == tok_ident)
		SQL_resolve_identifier("<Udf Name>", NULL, NAME_SIZE);

	gpre_nod* node;
	USHORT local_count;

	udf* an_udf;
	if (request->req_database)
		an_udf = MET_get_udf(request->req_database, gpreGlob.token_global.tok_string);
	else
	{
		// no database was specified, check the metadata for all the databases
		// for the existence of the udf

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

	if (an_udf)
	{
		if ((SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) &&
			(dtype_sql_date == an_udf->udf_dtype ||
			 dtype_sql_time == an_udf->udf_dtype ||
			 dtype_int64 == an_udf->udf_dtype))
		{
			SQL_dialect1_bad_type(an_udf->udf_dtype);
		}

		node = MSC_node(nod_udf, 2);
		node->nod_count = 1;
		node->nod_arg[1] = (gpre_nod*) an_udf;
		PAR_get_token();
		EXP_left_paren(0);
		if (gpreGlob.token_global.tok_keyword != KW_RIGHT_PAREN)
		{
			// parse udf parameter references
			node->nod_arg[0] = SQE_list(SQE_value, request, false);

			if (an_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
			gpre_nod** input = node->nod_arg[0]->nod_arg;
			for (gpre_fld* field = an_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 (MSC_match(KW_GEN_ID))
	{
		TEXT* gen_name = (TEXT *) MSC_alloc(NAME_SIZE);
		node = MSC_node(nod_gen_id, 2);
		node->nod_count = 1;
		EXP_left_paren(0);
		SQL_resolve_identifier("<Generator Name>", gen_name, NAME_SIZE);
		node->nod_arg[1] = (gpre_nod*) gen_name;
		PAR_get_token();
		if (!MSC_match(KW_COMMA))
			CPR_s_error("<comma>");
		node->nod_arg[0] = SQE_value(request, false, NULL, NULL);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	// Check for context variables

	// Begin date/time/timestamp

	if (MSC_match(KW_CURRENT_DATE))
		return MSC_node(nod_current_date, 0);

	if (MSC_match(KW_CURRENT_TIME))
		return MSC_node(nod_current_time, 0);

	if (MSC_match(KW_CURRENT_TIMESTAMP))
		return MSC_node(nod_current_timestamp, 0);

	// End date/time/timestamp

	if (MSC_match(KW_CURRENT_CONNECTION))
		return MSC_node(nod_current_connection, 0);

	if (MSC_match(KW_CURRENT_ROLE))
		return MSC_node(nod_current_role, 0);

	if (MSC_match(KW_CURRENT_TRANSACTION))
		return MSC_node(nod_current_transaction, 0);

	if (MSC_match(KW_CURRENT_USER))
		return MSC_node(nod_user_name, 0);

	// End context variables

	// Check for SQL II defined functions

	// Begin date/time/timestamp

	if (MSC_match(KW_EXTRACT))
	{
		node = MSC_node(nod_extract, 2);
		EXP_left_paren(0);
		const kwwords_t kw_word = gpreGlob.token_global.tok_keyword;
		if (MSC_match(KW_YEAR) || MSC_match(KW_MONTH) || MSC_match(KW_DAY) ||
			MSC_match(KW_HOUR) || MSC_match(KW_MINUTE) || MSC_match(KW_SECOND) ||
			MSC_match(KW_WEEKDAY) || MSC_match(KW_YEARDAY))
		{
			node->nod_arg[0] = (gpre_nod*) kw_word;
			if (!MSC_match(KW_FROM))
				CPR_s_error("FROM");
		}
		else
			CPR_s_error("valid extract part");
		node->nod_arg[1] = SQE_value(request, false, NULL, NULL);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	// End date/time/timestamp

	if (MSC_match(KW_UPPER))
	{
		node = MSC_node(nod_upcase, 1);
		EXP_left_paren(0);
		node->nod_arg[0] = SQE_value(request, false, NULL, NULL);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	if (MSC_match(KW_LOWER))
	{
		node = MSC_node(nod_lowcase, 1);
		EXP_left_paren(0);
		node->nod_arg[0] = SQE_value(request, false, NULL, NULL);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	if (MSC_match(KW_CAST))
	{
		node = MSC_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 (!MSC_match(KW_AS))
			CPR_s_error("AS");
		gpre_fld* field = (gpre_fld*) MSC_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;
	}

	if (MSC_match(KW_COALESCE))
	{
		node = MSC_node(nod_coalesce, 1);
		EXP_left_paren(0);
		node->nod_arg[0] = SQE_list(SQE_value, request, false);
		local_count = 1;
		par_terminating_parens(&local_count, &local_count);
		return node;
	}

	if (MSC_match(KW_CASE))
	{
		node = par_case(request);
		return node;
	}

	if (MSC_match(KW_NULLIF))
	{
		node = par_nullif(request);
		return node;
	}

	if (MSC_match(KW_SUBSTRING))
	{
		node = par_substring(request);
		return node;
	}

	return NULL;
}


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

static gpre_nod* par_udf_or_field(gpre_req* request, bool aster_ok)
{
	assert_IS_REQ(request);

	gpre_nod* node = par_udf(request);
	if (!node)
		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,
											  bool aster_ok, USHORT* /*paren_count*/, bool* /*bool_flag*/)
{
	assert_IS_REQ(request);

	gpre_nod* node = par_udf_or_field(request, aster_ok);
	if (gpreGlob.token_global.tok_keyword == KW_COLLATE)
		node = par_collate(request, node);

	return node;
}


//____________________________________________________________
//
//		Parse FOR UPDATE WITH LOCK clause
//

static void par_update(gpre_rse* select, bool have_union, bool view_flag)
{
	// Parse FOR UPDATE if present
	if (MSC_match(KW_FOR))
	{
		if (! MSC_match(KW_UPDATE))
		{
			CPR_s_error("UPDATE");
			return;
		}
		if (MSC_match(KW_OF))
		{
			do {
				CPR_token();
			} while (MSC_match(KW_COMMA));
		}
		select->rse_flags |= RSE_for_update;
	}

	// Parse WITH LOCK if present
	if (MSC_match(KW_WITH))
	{
		if (! MSC_match(KW_LOCK))
		{
			CPR_s_error("LOCK");
			return;
		}
		if (have_union)
		{
			PAR_error("WITH LOCK in UNION");
			return;
		}
		if (view_flag)
		{
			PAR_error("WITH LOCK in VIEW");
			return;
		}
		select->rse_flags |= RSE_with_lock;
	}
}

//____________________________________________________________
//
//		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* to_map)
{
	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, to_map);

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

	case nod_list:
	case nod_upcase:
	case nod_lowcase:
	case nod_concatenate:
	case nod_cast:
	case nod_plus:
	case nod_minus:
	case nod_times:
	case nod_divide:
	case nod_negate:
		{
			gpre_nod** ptr = node->nod_arg;
			for (const gpre_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++)
			{
				*ptr = post_fields(*ptr, to_map);
			}
			break;
		}
	// Begin date/time/timestamp support
	case nod_extract:
		node->nod_arg[1] = post_fields(node->nod_arg[1], to_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* to_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 == to_map->map_context)
			return node;
	}

	for (element = to_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*) MSC_alloc(sizeof(mel));
	element->mel_next = to_map->map_elements;
	to_map->map_elements = element;
	element->mel_position = to_map->map_count++;
	element->mel_expr = node;
	element->mel_context = to_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* to_map)
{
	assert_IS_NOD(fields);

	gpre_nod* list = MSC_node(nod_list, fields->nod_count);

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

	return list;
}


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

static void pop_scope(gpre_req* request, 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, 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)
{
	gpre_fld* field;

	assert_IS_NOD(node);

	gpre_rse* rs_stream = context->ctx_stream;
	if (rs_stream)
	{
		for (SSHORT 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;
	}

	tok* f_token = (tok*) node->nod_arg[0];
	tok* 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

		gpre_sym* 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 || gpreGlob.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 (gpre_sym* temp_symbol = symbol; temp_symbol; temp_symbol = temp_symbol->sym_homonym)
		{
			if (temp_symbol->sym_type == SYM_context)
			{
				symbol = temp_symbol;
				break;
			}
			if (temp_symbol->sym_type == SYM_relation)
			{
				symbol = temp_symbol;
				continue;
			}
			if (temp_symbol->sym_type == SYM_procedure)
			{
				if (symbol->sym_type == SYM_relation)
					continue;

				symbol = temp_symbol;
			}
		}


		field = NULL;
		switch (symbol->sym_type)
		{
		case SYM_relation:
			if ((gpre_rel*) symbol->sym_object == context->ctx_relation)
				field = MET_field(context->ctx_relation, f_token->tok_string);
			break;
		case SYM_procedure:
			if ((gpre_prc*) symbol->sym_object == context->ctx_procedure)
				field = MET_context_field(context, f_token->tok_string);
			break;
		case SYM_context:
			if (symbol->sym_object == context)
				field = MET_context_field(context, f_token->tok_string);
			break;
		}
	}


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

	// Check for valid array field
	// Check dimensions
	// Set remaining fields for slice
	slc* slice;
	gpre_req* slice_req = (gpre_req*) node->nod_arg[2];
	if (slice_req && (slice = slice_req->req_slice) && slice_action)
	{
		slice = slice_req->req_slice;
		ary* ary_info = field->fld_array_info;
		if (!ary_info)
			CPR_s_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

		act* action = MSC_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

		act* action = MSC_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( const tok* q_token, gpre_rse* selection)
{
	for (int i = 0; i < selection->rse_count; i++)
	{
		gpre_ctx* context = selection->rse_context[i];
		gpre_rse* rs_stream = context->ctx_stream;
		if (rs_stream)
		{
			if (context = resolve_asterisk(q_token, rs_stream))
				return context;
			continue;
		}
		gpre_sym* 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;
			}
			if (symbol->sym_type == SYM_procedure &&
				(gpre_prc*) symbol->sym_object == context->ctx_procedure)
			{
				return context;
			}
			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)
{
	assert_IS_NOD(expr);

	ref* re = (ref*) expr->nod_arg[0];
	switch (expr->nod_type)
	{
	case nod_value:
		re->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_lowcase:
	case nod_concatenate:
	case nod_cast:
		{
            gpre_nod** ptr = expr->nod_arg;
			for (const gpre_nod* const* const 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(const char* p)
{
	char* const s = (char *) MSC_alloc(static_cast<int>(strlen(p) + 1));
	char* q = s;

	USHORT l = 0;
	char c;
	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 bool validate_references(const gpre_nod* fields, const gpre_nod* group_by)
{
	assert_IS_NOD(fields);
	assert_IS_NOD(group_by);

	if (!fields)
		return false;

	if (fields->nod_type == nod_field)
	{
		if (!group_by)
			return true;
		const ref* fref = (ref*) fields->nod_arg[0];

		bool context_match = false;
		const gpre_nod* const* ptr = group_by->nod_arg;
		for (const gpre_nod* const* const end = ptr + group_by->nod_count; ptr < end; ptr++)
		{
			const ref* 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;
	}

	switch (fields->nod_type)
	{
	case nod_agg_count:
	case nod_agg_max:
	case nod_agg_min:
	case nod_agg_total:
	case nod_agg_average:
	case nod_aggregate:
		return false;
	}

	if (fields->nod_type == nod_any || fields->nod_type == nod_ansi_any ||
		fields->nod_type == nod_ansi_all)
	{
		const gpre_rse* any = (gpre_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);

	bool invalid = false;
	const gpre_nod* const* ptr = fields->nod_arg;
	for (const gpre_nod* const* const end = ptr + fields->nod_count; ptr < end; ptr++)
	{
		switch ((*ptr)->nod_type)
		{
		case nod_map_ref:
			{
				const mel* element = (mel*) (*ptr)->nod_arg[0];
				const gpre_nod* 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_lowcase:
		case nod_concatenate:
		case nod_cast:
			invalid |= validate_references(*ptr, group_by);
			break;
		}
	}

	return invalid;
}

