//____________________________________________________________
//  
//		PROGRAM:	C Preprocessor
//		MODULE:		sql.cpp
//		DESCRIPTION:	SQL 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): ______________________________________.
//  TMN (Mike Nordell) 11.APR.2001 - Reduce compiler warnings
//  
//
//____________________________________________________________
//
//

#include "firebird.h"
#include <stdlib.h>
#include <string.h>
#include "../gpre/gpre.h"
#include "../jrd/ibase.h"
#include "../jrd/intl.h"
#include "../jrd/constants.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"


#ifdef FLINT_CACHE
const int MIN_CACHE_BUFFERS	= 250;
const int DEF_CACHE_BUFFERS	= 1000;
#endif
const int DEFAULT_BLOB_SEGMENT_LENGTH	= 80;	// bytes 

static act* act_alter(void);
static act* act_alter_database(void);
static act* act_alter_domain(void);
static act* act_alter_index(void);
static act* act_alter_table(void);
static act* act_comment(void);
static act* act_connect(void);
static act* act_create(void);
static act* act_create_database(void);
static act* act_create_domain(void);
static act* act_create_generator(void);
static act* act_create_index(bool, bool);
static act* act_create_shadow(void);
static act* act_create_table(void);
static act* act_create_view(void);
static act* act_d_section(enum act_t);
static act* act_declare(void);
static act* act_declare_filter(void);
static act* act_declare_table(gpre_sym*, DBB);
static act* act_declare_udf(void);
static act* act_delete(void);
static act* act_describe(void);
static act* act_disconnect(void);
static act* act_drop(void);
static act* act_event(void);
static act* act_execute(void);
static act* act_fetch(void);
static act* act_grant_revoke(enum act_t);
static act* act_include(void);
static act* act_insert(void);
static act* act_insert_blob(const TEXT *);
static act* act_lock(void);
static act* act_openclose(enum act_t);
static act* act_open_blob(ACT_T, gpre_sym*);
static act* act_prepare(void);
static act* act_procedure(void);
static act* act_select(void);
static act* act_set(const TEXT*);
static act* act_set_dialect(void);
static act* act_set_generator(void);
static act* act_set_names(void);
static act* act_set_statistics(void);
static act* act_set_transaction(void);
static act* act_transaction(enum act_t);
static act* act_update(void);
static act* act_whenever(void);

static bool		check_filename(const TEXT *);
static void		connect_opts(const TEXT**, const TEXT**, const TEXT**,
	const TEXT**, USHORT*);
#ifdef FLINT_CACHE
static FIL		define_cache(void);
#endif
static FIL		define_file(void);
static FIL		define_log_file(bool);
static dbb*		dup_dbb(const dbb*);
static void		error(const TEXT *, const TEXT *);
static TEXT*	extract_string(bool);
static swe*		gen_whenever(void);
static void		into(gpre_req*, GPRE_NOD, GPRE_NOD);
static gpre_fld*	make_field(gpre_rel*);
static IND		make_index(gpre_req*, const TEXT*);
static gpre_rel* make_relation(gpre_req*, const TEXT *);
static void		pair(GPRE_NOD, GPRE_NOD);
static void		par_array(gpre_fld*);
static SSHORT	par_char_set(void);
static void		par_computed(gpre_req*, gpre_fld*);
static gpre_req* par_cursor(gpre_sym**);
static DYN		par_dynamic_cursor(void);
static gpre_fld* par_field(gpre_req*, gpre_rel*);
static CNSTRT	par_field_constraint(gpre_req*, gpre_fld*, gpre_rel*);
static void		par_fkey_extension(CNSTRT);
static bool		par_into(DYN);
static void		par_options(const TEXT**);
static int		par_page_size(void);
static gpre_rel* par_relation(gpre_req*);
static DYN		par_statement(void);
static CNSTRT	par_table_constraint(gpre_req*, gpre_rel*);
static bool		par_transaction_modes(gpre_tra*, bool);
static bool		par_using(DYN);
static USHORT	resolve_dtypes(KWWORDS, bool);
static bool		tail_database(enum act_t, DBB);
static void		to_upcase(const TEXT*, TEXT*, int);

static swe* global_whenever[SWE_max];
static swe* global_whenever_list;

static inline bool end_of_command(void)
{
	return ((gpreGlob.sw_language != lang_cobol) &&
		((int) gpreGlob.token_global.tok_keyword == (int) KW_SEMI_COLON)) ||
		 ((gpreGlob.sw_language == lang_cobol) &&
		 ((int) gpreGlob.token_global.tok_keyword == (int) KW_END_EXEC));
}

static inline bool range_short_integer(const SLONG x)
{
	return (x < 32768 && x >= -32768);
}

static inline bool range_positive_short_integer(const SLONG x)
{
	return (x < 32768 && x >= 0);
}

//____________________________________________________________
//  
//		Parse and return a sequel action.
//  

act* SQL_action(const TEXT* base_directory)
{
	act* action;
	const enum kwwords keyword = gpreGlob.token_global.tok_keyword;
	
	switch (keyword) {
	case KW_ALTER:
	case KW_COMMENT:
	case KW_CONNECT:
	case KW_CREATE:
	case KW_DROP:
	case KW_EVENT:
	case KW_GRANT:
	case KW_REVOKE:
	case KW_BEGIN:
	case KW_CLOSE:
	case KW_COMMIT:
	case KW_DECLARE:
	case KW_DELETE:
	case KW_DESCRIBE:
	case KW_DISCONNECT:
	case KW_END:
	case KW_EXECUTE:
	case KW_FETCH:
	case KW_INCLUDE:
	case KW_INSERT:
	case KW_LOCK:
	case KW_OPEN:
	case KW_PREPARE:
	case KW_ROLLBACK:
	case KW_SELECT:
	case KW_SET:
	case KW_UPDATE:
	case KW_WHENEVER:
	case KW_DATABASE:
		PAR_get_token();
		break;

	default:
		CPR_s_error("SQL operation");
	}

	switch (keyword) {
	case KW_ALTER:
		action = act_alter();
		break;

	case KW_BEGIN:
		action = act_d_section(ACT_b_declare);
		break;

	case KW_CLOSE:
		action = act_openclose(ACT_close);
		break;

	case KW_CONNECT:
		action = act_connect();
		break;

	case KW_COMMENT:
		action = act_comment();
		break;

	case KW_COMMIT:
		action = act_transaction(ACT_commit);
		break;

	case KW_CREATE:
		action = act_create();
		break;

	case KW_DATABASE:
		action = PAR_database(true, base_directory);
		break;

	case KW_DROP:
		action = act_drop();
		break;

	case KW_DECLARE:
		action = act_declare();
		break;

	case KW_DELETE:
		action = act_delete();
		break;

	case KW_DESCRIBE:
		action = act_describe();
		break;

	case KW_DISCONNECT:
		action = act_disconnect();
		break;

	case KW_EVENT:
		action = act_event();
		break;

	case KW_END:
		action = act_d_section(ACT_e_declare);
		break;

	case KW_EXECUTE:
		action = act_execute();
		break;

	case KW_FETCH:
		action = act_fetch();
		break;

	case KW_GRANT:
		action = act_grant_revoke(ACT_dyn_grant);
		break;

	case KW_INCLUDE:
		action = act_include();
		break;

	case KW_INSERT:
		action = act_insert();
		break;

	case KW_LOCK:
		action = act_lock();
		break;

	case KW_OPEN:
		action = act_openclose(ACT_open);
		break;

	case KW_PREPARE:
		action = act_prepare();
		break;

	case KW_REVOKE:
		action = act_grant_revoke(ACT_dyn_revoke);
		break;

	case KW_ROLLBACK:
		action = act_transaction(ACT_rollback);
		break;

	case KW_SELECT:
		action = act_select();
		break;

	case KW_SET:
		action = act_set(base_directory);
		break;

	case KW_UPDATE:
		action = act_update();
		break;

	case KW_WHENEVER:
		action = act_whenever();
		break;
	}

	MSC_match(KW_END_EXEC);
	PAR_end();
	action->act_flags |= ACT_sql;

	return action;
}


//____________________________________________________________
//  
//		Given a field datatype, remap it as needed to
//		a user datatype, and set the length field.
//  

void SQL_adjust_field_dtype( gpre_fld* field)
{
	if (field->fld_dtype <= dtype_any_text) {
		ULONG field_length;
		// Adjust the string data types and their lengths 
		if (field->fld_collate) {
			if (field->fld_char_length)
				field_length = (ULONG) field->fld_char_length *
					field->fld_collate->intlsym_bytes_per_char;
			else
				field_length = field->fld_length;
			field->fld_collate_id = field->fld_collate->intlsym_collate_id;
			field->fld_charset_id = field->fld_collate->intlsym_charset_id;
			field->fld_ttype = field->fld_collate->intlsym_ttype;
		}
		else if (field->fld_character_set) {
			if (field->fld_char_length)
				field_length = (ULONG) field->fld_char_length *
					field->fld_character_set->intlsym_bytes_per_char;
			else
				field_length = field->fld_length;
			field->fld_collate_id =
				field->fld_character_set->intlsym_collate_id;
			field->fld_charset_id =
				field->fld_character_set->intlsym_charset_id;
			field->fld_ttype = field->fld_character_set->intlsym_ttype;
		}
		else {
			if (field->fld_char_length)
				field_length = (ULONG) field->fld_char_length * 1;
			else
				field_length = field->fld_length;
			field->fld_collate_id = 0;
			field->fld_charset_id = 0;
			field->fld_ttype = 0;
		}

		if (!(field->fld_flags & FLD_meta)) {	/* field for meta operation? */
			/* Field isn't for meta-data operation, so adjust it's
			 * type definition for local use
			 */
			if (field->fld_dtype != dtype_cstring)
				field->fld_dtype = (gpreGlob.sw_cstring
									&& field->fld_sub_type != dsc_text_type_fixed) ?
					dtype_cstring : dtype_text;
			if (field->fld_dtype == dtype_cstring)
				field_length++;
			field->fld_length = (USHORT) field_length;
		}
		else {
			field->fld_length = (USHORT) field_length;
			if (field->fld_dtype == dtype_varying)
				field_length += sizeof(USHORT);

			if (field_length > MAX_COLUMN_SIZE)
				error("Size of column %s exceeds implementation limit",
					  field->fld_symbol->sym_string);
		}
	}
	else
		switch (field->fld_dtype) {
		case dtype_short:
			field->fld_length = sizeof(SSHORT);
			break;

		case dtype_long:
			field->fld_length = sizeof(SLONG);
			break;

		case dtype_real:
			field->fld_length = sizeof(float);
			break;

		case dtype_double:
			field->fld_length = sizeof(double);
			break;

// ** Begin sql/date/time/timestamp *
		case dtype_sql_date:
			field->fld_length = sizeof(ISC_DATE);
			break;

		case dtype_sql_time:
			field->fld_length = sizeof(ISC_TIME);
			break;

		case dtype_timestamp:
			field->fld_length = sizeof(ISC_TIMESTAMP);
			break;
// ** End sql/date/time/timestamp *

		case dtype_int64:
			field->fld_length = sizeof(ISC_INT64);
			break;

		case dtype_blob:
			field->fld_length = sizeof(ISC_QUAD);
			field->fld_flags |= FLD_blob;
			if (field->fld_character_set) {
				field->fld_charset_id =
					field->fld_character_set->intlsym_charset_id;
				field->fld_ttype = field->fld_character_set->intlsym_ttype;
			}
			break;

		default:
			CPR_bugcheck("datatype not recognized");
			break;
		}
}


//____________________________________________________________
//  
//		Initialize (or re-initialize) to process a module.
//  

void SQL_init(void)
{
	global_whenever_list = NULL;

	for (int i = 0; i < SWE_max; i++)
		global_whenever[i] = NULL;

	gpreGlob.module_lc_ctype = gpreGlob.default_lc_ctype;
}


//____________________________________________________________
//  
//  

void SQL_par_field_collate( gpre_req* request, gpre_fld* field)
{
	if (MSC_match(KW_COLLATE)) {

		if ((field->fld_dtype != dtype_text) &&
			(field->fld_dtype != dtype_cstring) &&
			(field->fld_dtype != dtype_varying))
		{
			PAR_error("COLLATE applies only to character columns");
		}
		if (gpreGlob.token_global.tok_type != tok_ident)
			CPR_s_error("<collation name>");
		gpre_sym* symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_collate);
		if (!symbol)
			PAR_error("The named COLLATION was not found");
		field->fld_collate = (INTLSYM) symbol->sym_object;

		/* Is the collation valid for declared character set? 
		 * The character set is either declared (fld_character_set) or inferered
		 * from the global domain (fld_global & fld_charset_id)
		 */

		if ((field->fld_character_set &&
			 (field->fld_character_set->intlsym_charset_id !=
			  field->fld_collate->intlsym_charset_id))
			|| (field->fld_global &&
				(field->fld_charset_id != field->fld_collate->intlsym_charset_id)))
		{
			PAR_error
				("Specified COLLATION is incompatible with column CHARACTER SET");
		}
		PAR_get_token();
	}
}


//____________________________________________________________
//  
//		Handle an SQL field datatype definition for
//		field CREATE, DECLARE or ALTER TABLE statement.
//		Also for CAST statement
//  

void SQL_par_field_dtype(gpre_req* request,
						 gpre_fld* field,
						 bool is_udf)
{
	bool sql_date = false;

	enum kwwords keyword = gpreGlob.token_global.tok_keyword;
	switch (keyword) {
	case KW_SMALLINT:
	case KW_INT:
	case KW_INTEGER:
	case KW_FLOAT:
	case KW_REAL:
	case KW_DOUBLE:
	case KW_LONG:
// ** Begin sql/date/time/timestamp *
	case KW_TIMESTAMP:
// ** End sql/date/time/timestamp *
	case KW_CHAR:
	case KW_NCHAR:
	case KW_VARCHAR:
	case KW_DECIMAL:
	case KW_NUMERIC:
	case KW_BLOB:
	case KW_NATIONAL:
		PAR_get_token();
		break;

	case KW_DATE:
		if (gpreGlob.sw_sql_dialect == 2)
			PAR_error
				("DATE is ambiguous in dialect 2 use SQL DATE or TIMESTAMP");
		PAR_get_token();
		break;

	case KW_TIME:
		if (gpreGlob.sw_sql_dialect == 1)
			CPR_s_error("<data type>");
		PAR_get_token();
		break;

	case KW_SQL:
		if (gpreGlob.sw_sql_dialect == 1)
			CPR_s_error("<data type>");
		PAR_get_token();
		if (gpreGlob.token_global.tok_keyword == KW_DATE)
			PAR_get_token();
		else
			CPR_s_error("<data type>");
		keyword = KW_DATE;
		sql_date = true;
		break;

	case KW_COMPUTED:
		if (is_udf)
			CPR_s_error("<data type>");
		// just return - actual parse is done later 
		return;

	default:
		if (is_udf) {
			if (keyword == KW_CSTRING)
				PAR_get_token();
			else
				CPR_s_error("<data type>");
		}
		else {
			char s[NAME_SIZE];
			SQL_resolve_identifier("<domain name>", s, NAME_SIZE);
			gpre_sym* symbol =
				MSC_symbol(SYM_field, s, (USHORT) strlen(s), (gpre_ctx*) field);
			field->fld_global = symbol;
			if (!MET_domain_lookup(request, field, s))
				PAR_error("Specified DOMAIN or source column not found");
			PAR_get_token();
			return;
		}
	}

	switch (keyword) {
	case KW_SMALLINT:
		field->fld_dtype = dtype_short;
		break;

	case KW_INT:
	case KW_INTEGER:
		field->fld_dtype = dtype_long;
		break;

	case KW_REAL:
		field->fld_dtype = dtype_real;
		break;

	case KW_FLOAT:
		if (MSC_match(KW_LEFT_PAREN)) {
			const int l = EXP_USHORT_ordinal(true);
			EXP_match_paren();
			if (l < 17)
				field->fld_dtype = dtype_real;
			else
				field->fld_dtype = dtype_double;
		}
		else
			field->fld_dtype = dtype_real;
		break;

	case KW_LONG:
		if (!(gpreGlob.token_global.tok_keyword == KW_FLOAT))
			CPR_s_error("FLOAT");
		PAR_get_token();
		field->fld_dtype = dtype_double;
		break;

	case KW_DOUBLE:
		if (!(gpreGlob.token_global.tok_keyword == KW_PRECISION))
			CPR_s_error("PRECISION");
		PAR_get_token();
		field->fld_dtype = dtype_double;
		break;

// ** Begin sql/date/time/timestamp *
	case KW_DATE:
	case KW_TIME:
	case KW_TIMESTAMP:
		field->fld_dtype = resolve_dtypes(keyword, sql_date);
		break;
// ** End sql/date/time/timestamp *

	case KW_NCHAR:
		field->fld_flags |= FLD_national;
		field->fld_dtype = dtype_text;
		if (MSC_match(KW_LEFT_PAREN)) {
			field->fld_char_length = EXP_pos_USHORT_ordinal(true);
			EXP_match_paren();
		}
		else
			field->fld_char_length = 1;
		break;

	case KW_NATIONAL:
		if (!(gpreGlob.token_global.tok_keyword == KW_CHAR))
			CPR_s_error("CHARACTER");
		PAR_get_token();
		field->fld_flags |= FLD_national;
		// Fall into KW_CHAR 
	case KW_CHAR:
		if (MSC_match(KW_VARYING)) {
			field->fld_dtype = dtype_varying;
			EXP_left_paren(0);
			field->fld_char_length = EXP_pos_USHORT_ordinal(true);
			EXP_match_paren();
			break;
		}
	case KW_CSTRING:
		if (keyword == KW_CSTRING) {
			field->fld_dtype = dtype_cstring;
			field->fld_flags |= FLD_meta_cstring;
		}
		else
			field->fld_dtype = dtype_text;
		if (MSC_match(KW_LEFT_PAREN)) {
			field->fld_char_length = EXP_pos_USHORT_ordinal(true);
			EXP_match_paren();
		}
		else
			field->fld_char_length = 1;
		break;

	case KW_VARCHAR:
		field->fld_dtype = dtype_varying;
		EXP_left_paren(0);
		field->fld_char_length = EXP_pos_USHORT_ordinal(true);
		EXP_match_paren();
		break;


	case KW_NUMERIC:
	case KW_DECIMAL:
		field->fld_dtype = dtype_long;
		field->fld_scale = 0;
		field->fld_precision = 9;
		field->fld_sub_type = (keyword == KW_NUMERIC) ? 1 : 2;
		if (MSC_match(KW_LEFT_PAREN)) {
			const int p = EXP_USHORT_ordinal(true);
			if ((p <= 0) || (p > 18))
				PAR_error("Precision must be from 1 to 18");

			if ((keyword == KW_NUMERIC) && (p < 5))
				field->fld_dtype = dtype_short;
			else if (p > 9) {
				if (gpreGlob.sw_sql_dialect == SQL_DIALECT_V5)
					field->fld_dtype = dtype_double;
				else
					field->fld_dtype = dtype_int64;
			}

			if (MSC_match(KW_COMMA)) {
				const int q = EXP_USHORT_ordinal(true);

				if (q > p)
					PAR_error("Scale can not be greater than precision");
				field->fld_scale = -q;
			}
			field->fld_precision = p;
			EXP_match_paren();
		}
		break;

	case KW_BLOB:
		field->fld_dtype = dtype_blob;
		field->fld_seg_length = DEFAULT_BLOB_SEGMENT_LENGTH;
		if (MSC_match(KW_LEFT_PAREN)) {
			if (MSC_match(KW_COMMA))
				field->fld_sub_type = EXP_SSHORT_ordinal(true);
			else {
				field->fld_seg_length = EXP_USHORT_ordinal(true);
				if (MSC_match(KW_COMMA))
					field->fld_sub_type = EXP_SSHORT_ordinal(true);
			}
			EXP_match_paren();
		}
		else {
			if (MSC_match(KW_SUB_TYPE)) {
				field->fld_sub_type = PAR_blob_subtype(request->req_database);
			}
			if (MSC_match(KW_SEGMENT)) {
				MSC_match(KW_SIZE);
				field->fld_seg_length = EXP_USHORT_ordinal(true);
			}
		}
		break;

	default:
		CPR_s_error("<data type>");
	}

//   Check for array declaration 

	if ((keyword != KW_BLOB) && !is_udf && (MSC_match(KW_L_BRCKET))) {
		field->fld_array_info = (ary*) MSC_alloc(sizeof(ary));
		par_array(field);
	}

	if (MSC_match(KW_CHAR)) {
		if ((field->fld_dtype != dtype_text) &&
			(field->fld_dtype != dtype_cstring) &&
			(field->fld_dtype != dtype_varying) &&
			(field->fld_dtype != dtype_blob))
		{
			PAR_error("CHARACTER SET applies only to character columns");
		}

		if (field->fld_dtype == dtype_blob && field->fld_sub_type == isc_blob_untyped)
			field->fld_sub_type = isc_blob_text;

		if (field->fld_dtype == dtype_blob && field->fld_sub_type != isc_blob_text)
			PAR_error("CHARACTER SET applies only to character columns");

		if (field->fld_flags & FLD_national)
			PAR_error("cannot specify CHARACTER SET with NATIONAL");

		if (!MSC_match(KW_SET))
			CPR_s_error("SET");
		if (gpreGlob.token_global.tok_type != tok_ident)
			CPR_s_error("<character set name>");
			
		gpre_sym* symbol2 = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_charset);
		if (!symbol2)
			PAR_error("The named CHARACTER SET was not found");
		field->fld_character_set = (INTLSYM) symbol2->sym_object;
		PAR_get_token();
	}

	if (field->fld_flags & FLD_national) {
        gpre_sym* symbol = MSC_find_symbol(HSH_lookup(DEFAULT_CHARACTER_SET_NAME),
										SYM_charset);
		if (!symbol)
		{
			PAR_error("NATIONAL character set missing");
		}
		field->fld_character_set = (INTLSYM) symbol->sym_object;
	}
	else if ((field->fld_dtype <= dtype_any_text ||
			  (field->fld_dtype == dtype_blob
			   && field->fld_sub_type == isc_blob_text))
			 && !field->fld_character_set && !field->fld_collate && request
			 && request->req_database
			 && request->req_database->dbb_def_charset)
	{
		// Use database default character set 
		gpre_sym* symbol = MSC_find_symbol(HSH_lookup
							(request->req_database->dbb_def_charset),
							SYM_charset);
		if (symbol)
		{
			field->fld_character_set = (INTLSYM) symbol->sym_object;
		}
		else
			PAR_error("Could not find database default character set");
	}
}


//____________________________________________________________
//  
//		Find procedure for request.  If request already has a database,
//		find the procedure in that database only.
//  

gpre_prc* SQL_procedure(gpre_req* request,
					   const TEXT* prc_string,
					   const TEXT* db_string,
					   const TEXT* owner_string,
					   bool err_flag)
{
	SCHAR s[ERROR_LENGTH];

	if (db_string && db_string[0]) {
		// a database was specified for the procedure
		// search the known symbols for the database name

		gpre_sym* symbol = MSC_find_symbol(HSH_lookup(db_string), SYM_database);
		if (!symbol)
			PAR_error("Unknown database specifier.");
		if (request->req_database) {
			if ((DBB) symbol->sym_object != request->req_database)
				PAR_error("Inconsistent database specifier");
		}
		else
			request->req_database = (DBB) symbol->sym_object;
	}

	gpre_prc* procedure = NULL;
	
	if (request->req_database)
		procedure = MET_get_procedure(request->req_database, prc_string,
							  owner_string);
	else {
		// no database was specified, check the metadata for all the databases
		//   for the existence of the procedure

		procedure = NULL; // redundant
		for (DBB db = gpreGlob.isc_databases; db; db = db->dbb_next)
		{
			gpre_prc* tmp_procedure = MET_get_procedure(db, prc_string,
														owner_string);
			if (tmp_procedure) {
				if (procedure) {
					// relation was found in more than one database

					sprintf(s, "PROCEDURE %s is ambiguous", prc_string);
					PAR_error(s);
				}
				else {
					procedure = tmp_procedure;
					request->req_database = db;
				}
			}
		}
	}

	if (!procedure) {
		if (!err_flag)
			return NULL;
		if (owner_string[0])
			sprintf(s, "PROCEDURE %s.%s not defined", owner_string,
					prc_string);
		else
			sprintf(s, "PROCEDURE %s not defined", prc_string);
		PAR_error(s);
	}

	return procedure;
}


//____________________________________________________________
//  
//		Find relation for request.  If request already has a database,
//		find the relation in that database only.
//  

gpre_rel* SQL_relation(gpre_req* request,
					  const TEXT* rel_string,
					  const TEXT* db_string,
					  const TEXT* owner_string,
					  bool err_flag)
{
	if (db_string && db_string[0]) {
		/* a database was specified for the relation,
		   search the known symbols for the database name */

		gpre_sym* symbol = MSC_find_symbol(HSH_lookup(db_string), SYM_database);
		if (!symbol)
			PAR_error("Unknown database specifier.");
		if (request->req_database) {
			if ((DBB) symbol->sym_object != request->req_database)
				PAR_error("Inconsistent database specifier");
		}
		else
			request->req_database = (DBB) symbol->sym_object;
	}

	SCHAR s[ERROR_LENGTH];
	gpre_rel* relation = NULL;
	
	if (request->req_database)
		relation = MET_get_relation(request->req_database, rel_string, 
									owner_string);
	else {
		/* no database was specified, check the metadata for all the databases
		   for the existence of the relation */

		relation = NULL; // redundant
		for (DBB db = gpreGlob.isc_databases; db; db = db->dbb_next)
		{
			gpre_rel* tmp_relation = MET_get_relation(db, rel_string, owner_string);
			if (tmp_relation) {
				if (relation) {
					// relation was found in more than one database 

					sprintf(s, "TABLE %s is ambiguous", rel_string);
					PAR_error(s);
				}
				else {
					relation = tmp_relation;
					request->req_database = db;
				}
			}
		}
	}

	if (!relation) {
		if (!err_flag)
			return (NULL);
		if (owner_string[0])
			sprintf(s, "TABLE %s.%s not defined", owner_string, rel_string);
		else
			sprintf(s, "TABLE %s not defined", rel_string);
		PAR_error(s);
	}

	return relation;
}


//____________________________________________________________
//  
//		Get a relation name (checking for database specifier)
//  

void SQL_relation_name(TEXT* r_name,
					   TEXT* db_name,
					   TEXT* owner_name)
{
	db_name[0] = 0;
	owner_name[0] = 0;

	SQL_resolve_identifier("<Database name>", NULL, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Database alias too long");

	gpre_sym* symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_database);
	if (symbol) {
		if (strlen(symbol->sym_name) >= NAME_SIZE)
			PAR_error("Database alias too long");
			
		strcpy(db_name, symbol->sym_name); // this is the alias, not the path
		PAR_get_token();
		if (!MSC_match(KW_DOT))
			CPR_s_error(". (period)");
	}

	SQL_resolve_identifier("<Table name>", NULL, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Table or owner name too long");

	strcpy(r_name, gpreGlob.token_global.tok_string);
	PAR_get_token();

	if (MSC_match(KW_DOT)) {
		// the table name was really a owner specifier 
		strcpy(owner_name, r_name);
		SQL_resolve_identifier("<Table name>", NULL, NAME_SIZE);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("TABLE name too long");
			
		strcpy(r_name, gpreGlob.token_global.tok_string);
		PAR_get_token();
	}
}


//____________________________________________________________
//  
//		Extract SQL var
//  

TEXT* SQL_var_or_string(bool string_only)
{

	if ((gpreGlob.token_global.tok_type != tok_sglquoted && gpreGlob.sw_sql_dialect == 3) ||
		(!isQuoted(gpreGlob.token_global.tok_type) && gpreGlob.sw_sql_dialect == 1))
	{
		if (string_only)
			CPR_s_error("<quoted string>");
		if (!MSC_match(KW_COLON))
			CPR_s_error("<colon> or <quoted string>");
	}
	return PAR_native_value(false, false);
}


//____________________________________________________________
//  
//		Handle an SQL alter statement.
//  

static act* act_alter(void)
{

	switch (gpreGlob.token_global.tok_keyword) {

	case KW_DATABASE:
	case KW_SCHEMA:
		return act_alter_database();

	case KW_DOMAIN:
		return act_alter_domain();

	case KW_INDEX:
		PAR_get_token();
		return act_alter_index();

	case KW_STOGROUP:
		PAR_error("ALTER STOGROUP not supported");

	case KW_TABLE:
		PAR_get_token();
		return act_alter_table();

	case KW_TABLESPACE:
		PAR_error("ALTER TABLESPACE not supported");

	default:
		PAR_error("Invalid ALTER request");
	}
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Handle an SQL alter database statement
//  

static act* act_alter_database(void)
{
	gpre_req* request = MSC_request(REQ_ddl);
	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can only alter database in context of single database");

	PAR_get_token();

	//  create action block 
	act* action = MSC_action(request, ACT_alter_database);
	action->act_whenever = gen_whenever();
	DBB database = (DBB) MSC_alloc(DBB_LEN);
	action->act_object = (REF) database;

	bool logdefined = false;	// this var was undefined

	while (true) {
		if (MSC_match(KW_DROP)) {
			if (MSC_match(KW_LOG_FILE))
				; // ignore DROP LOG database->dbb_flags |= DBB_drop_log;
			else if (MSC_match(KW_CASCADE))
				database->dbb_flags |= DBB_cascade;
#ifdef FLINT_CACHE
			else if (MSC_match(KW_CACHE))
				database->dbb_flags |= DBB_drop_cache;
			else
				PAR_error("only log or cache can be dropped");
#else
			else
				PAR_error("only log file can be dropped");
#endif // FLINT_CACHE 
		}
		else if (MSC_match(KW_ADD)) {
			if (MSC_match(KW_FILE)) {
				do {
					FIL file = define_file();
					file->fil_next = database->dbb_files;
					database->dbb_files = file;
				}
				while (MSC_match(KW_FILE));
			}
			else if (MSC_match(KW_LOG_FILE)) {
				if (logdefined)
					PAR_error("log redefinition");
				logdefined = true;
				if (MSC_match(KW_LEFT_PAREN)) {
					while (true) {
						FIL logfile = define_log_file(false);
						logfile->fil_next = database->dbb_logfiles;
						database->dbb_logfiles = logfile;
						if (!MSC_match(KW_COMMA)) {
							EXP_match_paren();
							break;
						}
					}

					if (MSC_match(KW_OVERFLOW))
						define_log_file(true); // skip
					else
						PAR_error
							("Overflow log specification required for this configuration");

				}
				else if (MSC_match(KW_BASE_NAME)) {
					database->dbb_flags |= DBB_log_serial;
					database->dbb_logfiles = define_log_file(true);
				}
			}
#ifdef FLINT_CACHE
			else if (MSC_match(KW_CACHE))
				database->dbb_cache_file = define_cache();
#endif // FLINT_CACHE 
		}
		else if (MSC_match(KW_SET)) {
			while (true) {
				if (MSC_match(KW_CHECK_POINT_LEN)) {
					MSC_match(KW_EQUALS);
					EXP_ULONG_ordinal(true); // skip
					MSC_match(KW_COMMA);
				}
				else if (MSC_match(KW_NUM_LOG_BUFS)) {
					MSC_match(KW_EQUALS);
					EXP_USHORT_ordinal(true); // skip
					MSC_match(KW_COMMA);
				}
				else if (MSC_match(KW_LOG_BUF_SIZE)) {
					MSC_match(KW_EQUALS);
					EXP_USHORT_ordinal(true); // skip
					MSC_match(KW_COMMA);
				}
				else if (MSC_match(KW_GROUP_COMMIT_WAIT)) {
					MSC_match(KW_EQUALS);
					EXP_ULONG_ordinal(true); // skip
					MSC_match(KW_COMMA);
				}
				else
					break;
			}
		}
		else
			break;
	}
	return action;
}


//____________________________________________________________
//  
//		Handle altering of a domain (global field). 
//  

static act* act_alter_domain(void)
{
//  create request block 

	gpre_req* request = MSC_request(REQ_ddl);
	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can only ALTER a domain in context of single database");

//  create action block 

	act* action = MSC_action(request, ACT_alter_domain);

	PAR_get_token();
	gpre_fld* field = make_field(0);
	cnstrt** cnstrt_ptr = &field->fld_constraints;

//  Check if default value was specified 

	while (!end_of_command()) {
		if (MSC_match(KW_SET)) {
			if (gpreGlob.token_global.tok_keyword == KW_DEFAULT) {
				field->fld_default_source = CPR_start_text();
				PAR_get_token();
			}
			else
				CPR_s_error("DEFAULT");

			if (MSC_match(KW_USER))
				field->fld_default_value = MSC_node(nod_user_name, 0);
			else if (MSC_match(KW_NULL))
				field->fld_default_value = MSC_node(nod_null, 0);
			else {
				if (MSC_match(KW_MINUS)) {
					if (gpreGlob.token_global.tok_type != tok_number)
						CPR_s_error("<number>");

					GPRE_NOD literal_node = EXP_literal();
					field->fld_default_value = MSC_unary(nod_negate,
														 literal_node);
				}
				else if ((field->fld_default_value = EXP_literal()) == NULL)
					CPR_s_error("<constant>");
			}
			CPR_end_text(field->fld_default_source);
		}
		else if (MSC_match(KW_ADD)) {
			MSC_match(KW_CONSTRAINT);
			if (gpreGlob.token_global.tok_keyword == KW_CHECK) {
				cnstrt* cnstrt_str = par_field_constraint(request, field, 0);
				*cnstrt_ptr = cnstrt_str;
				cnstrt_ptr = &cnstrt_str->cnstrt_next;
			}
			else
				PAR_error("Invalid constraint.");
		}
		else if (MSC_match(KW_DROP)) {
			if (MSC_match(KW_CONSTRAINT)) {
				cnstrt* cnstrt_str = (cnstrt*) MSC_alloc(CNSTRT_LEN);
				cnstrt_str->cnstrt_flags |= CNSTRT_delete;
				*cnstrt_ptr = cnstrt_str;
				cnstrt_ptr = &cnstrt_str->cnstrt_next;
			}
			else if (MSC_match(KW_DEFAULT)) {
				field->fld_default_value = MSC_node(nod_erase, 0);
			}
			else
				PAR_error("Invalid attribute for DROP");
		}
		else
			CPR_s_error("SET, ADD, or DROP");

	}

	action->act_whenever = gen_whenever();
	action->act_object = (REF) field;

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL alter index statement.
//  

static act* act_alter_index(void)
{
//  create request block

	gpre_req* request = MSC_request(REQ_ddl);

	char i_name[NAME_SIZE + 1];
	SQL_resolve_identifier("<index name>", i_name, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Index name too long");
		
	PAR_get_token();

	IND index = make_index(request, i_name);

	if (MSC_match(KW_ACTIVE))
		index->ind_flags |= IND_active;
	else if (MSC_match(KW_INACTIVE))
		index->ind_flags |= IND_inactive;
	else
		PAR_error("Unsupported ALTER INDEX option");

	act* action = MSC_action(request, ACT_alter_index);
	action->act_object = (REF) index;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL alter table statement.
//  

static act* act_alter_table(void)
{
//  create request block

	gpre_req* request = MSC_request(REQ_ddl);

//  get table name and create relation block 

	gpre_rel* relation = par_relation(request);

//  CHECK Constraints require the context to be set to the
//  current relation 

	gpre_ctx* context = MSC_context(request);
	request->req_contexts = context;
	context->ctx_relation = relation;

//  Reserve context 1 for relation on which constraint is
//  being defined 

	context->ctx_internal++;
	request->req_internal++;

//  create action block 

	act* action = MSC_action(request, ACT_alter_table);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) relation;

//  parse action list and create corresponding field blocks 

	gpre_fld** ptr = &relation->rel_fields;
	cnstrt** cnstrt_ptr = &relation->rel_constraints;
	cnstrt* cnstrt_str;
	gpre_fld* field;

	while (!end_of_command()) {
		if (MSC_match(KW_ADD)) {
			switch (gpreGlob.token_global.tok_keyword) {
			case KW_CONSTRAINT:
			case KW_PRIMARY:
			case KW_UNIQUE:
			case KW_FOREIGN:
			case KW_CHECK:
				cnstrt_str = par_table_constraint(request, relation);
				*cnstrt_ptr = cnstrt_str;
				cnstrt_ptr = &cnstrt_str->cnstrt_next;
				break;

			default:
				field = par_field(request, relation);
				*ptr = field;
				ptr = &field->fld_next;
			}
		}
		else if (MSC_match(KW_DROP)) {
			if (gpreGlob.token_global.tok_keyword == KW_CONSTRAINT) {
				PAR_get_token();
				cnstrt_str = (CNSTRT) MSC_alloc(CNSTRT_LEN);
				cnstrt_str->cnstrt_flags |= CNSTRT_delete;
				cnstrt_str->cnstrt_name = (STR) MSC_alloc(NAME_SIZE + 1);
				SQL_resolve_identifier("<constraint name>",
									   cnstrt_str->cnstrt_name->str_string, NAME_SIZE + 1);
				if (gpreGlob.token_global.tok_length >= NAME_SIZE)
					PAR_error("Constraint name too long");
					
				*cnstrt_ptr = cnstrt_str;
				cnstrt_ptr = &cnstrt_str->cnstrt_next;
				PAR_get_token();
			}
			else {
				field = make_field(relation);
				field->fld_flags |= FLD_delete;
				*ptr = field;
				ptr = &field->fld_next;
				/* Fix for bug 8054:

				   [CASCADE | RESTRICT] syntax is available in IB4.5, but not
				   required until v5.0.

				   Option CASCADE causes an error :
				   unsupported construct

				   Option RESTRICT is default behaviour.
				 */
				if (gpreGlob.token_global.tok_keyword == KW_CASCADE) {
					PAR_error("Unsupported construct CASCADE");
					PAR_get_token();
				}
				else if (gpreGlob.token_global.tok_keyword == KW_RESTRICT) {
					PAR_get_token();
				}
			}
		}
		else
			CPR_s_error("ADD or DROP");
		if (!MSC_match(KW_COMMA))
			break;
	}

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL comment statement.
//			Reject
//  

static act* act_comment(void)
{

	PAR_error("SQL COMMENT ON request not allowed");
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Parse a CONNECT statement.
//  

static act* act_connect(void)
{
	act* action = MSC_action(0, ACT_ready);
	action->act_whenever = gen_whenever();
	bool need_handle = false;
	const USHORT default_buffers = 0; // useless?

	MSC_match(KW_TO);

	if (!MSC_match(KW_ALL) && !MSC_match(KW_DEFAULT))
		while (true) {
			rdy* ready = (rdy*) MSC_alloc(RDY_LEN);
			ready->rdy_next = (rdy*) action->act_object;
			action->act_object = (REF) ready;

			gpre_sym* symbol = gpreGlob.token_global.tok_symbol;
			if (!symbol || symbol->sym_type != SYM_database) {
				ready->rdy_filename = SQL_var_or_string(false);
				if (MSC_match(KW_AS))
					need_handle = true;
			}

			symbol = gpreGlob.token_global.tok_symbol;
			if (!symbol || symbol->sym_type != SYM_database) {
				if (!gpreGlob.isc_databases || gpreGlob.isc_databases->dbb_next || need_handle) {
					need_handle = false;
					CPR_s_error("<database handle>");
				}
				ready->rdy_database = dup_dbb(gpreGlob.isc_databases);
			}

			need_handle = false;
			if (!ready->rdy_database) {
				ready->rdy_database = dup_dbb((DBB) symbol->sym_object);
				PAR_get_token();
			}

			// pick up the possible parameters, in any order 

			USHORT buffers = 0;
			dbb* db = ready->rdy_database;
			connect_opts(&db->dbb_r_user, &db->dbb_r_password,
						 &db->dbb_r_sql_role, &db->dbb_r_lc_messages,
						 &buffers);

			gpre_req* request = NULL;
			if (buffers)
				request = PAR_set_up_dpb_info(ready, action, buffers);

			/* if there are any options that take host variables as arguments, 
			   make sure that we generate variables for the request so that the 
			   dpb can be extended at runtime */

			if (db->dbb_r_user || db->dbb_r_password || db->dbb_r_sql_role
				|| db->dbb_r_lc_messages || db->dbb_r_lc_ctype)
			{
				if (!request)
					request =
						PAR_set_up_dpb_info(ready, action, default_buffers);
				request->req_flags |= REQ_extend_dpb;
			}

			/* ...and if there are compile time user or password specified,
			   make sure there will be a dpb generated for them */

			if (!request && (db->dbb_c_user || db->dbb_c_password ||
							 db->dbb_c_sql_role ||
							 db->dbb_c_lc_messages || db->dbb_c_lc_ctype))
			{
				request =
					PAR_set_up_dpb_info(ready, action, default_buffers);
			}

			if (!MSC_match(KW_COMMA))
				break;
		}

	if (action->act_object)
		return action;

//  No explicit databases -- pick up all known 

	const TEXT* lc_messages = NULL;
	const TEXT *user = NULL, *password = NULL, *sql_role = NULL;
	// The original logic suggests that "buffers" should be shared between the
	// two sections of code. However, if the loop above executes, action->act_object
	// is not null and therefore, the function returns before this point.
	// Furthermore, connect_opts() below refills the values.
	USHORT buffers = 0;

	connect_opts(&user, &password, &sql_role, &lc_messages, &buffers);

	for (const dbb* db_iter = gpreGlob.isc_databases; db_iter; db_iter = db_iter->dbb_next)
		if (db_iter->dbb_runtime || !(db_iter->dbb_flags & DBB_sqlca)) {
			rdy* ready = (rdy*) MSC_alloc(RDY_LEN);
			ready->rdy_next = (rdy*) action->act_object;
			action->act_object = (REF) ready;
			ready->rdy_database = dup_dbb(db_iter);
		}

	if (!action->act_object)
		PAR_error("no database available to CONNECT");
	else
		for (rdy* ready = (rdy*) action->act_object; ready; ready = ready->rdy_next)
		{
		    gpre_req* request;
			if (buffers)
				request = PAR_set_up_dpb_info(ready, action, buffers);
			else
				request = ready->rdy_request;

			/* if there are any options that take host variables as arguments, 
			   make sure that we generate variables for the request so that the 
			   dpb can be extended at runtime */

			dbb* db = ready->rdy_database;
			if (user || password || lc_messages || db->dbb_r_lc_ctype) {
				db->dbb_r_user = user;
				db->dbb_r_password = password;
				db->dbb_r_lc_messages = lc_messages;
				if (!request)
					request =
						PAR_set_up_dpb_info(ready, action, default_buffers);
				request->req_flags |= REQ_extend_dpb;
			}

			/* ...and if there are compile time user or password specified,
			   make sure there will be a dpb generated for them */

			if (!request && (db->dbb_c_user || db->dbb_c_password ||
							 db->dbb_c_sql_role ||
							 db->dbb_c_lc_ctype || db->dbb_c_lc_messages))
			{
				request =
					PAR_set_up_dpb_info(ready, action, default_buffers);
			}
		}

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL create statement.
//  

static act* act_create(void)
{
	if (MSC_match(KW_DATABASE) || MSC_match(KW_SCHEMA))
		return act_create_database();

	if (MSC_match(KW_DOMAIN))
		return act_create_domain();

	if (MSC_match(KW_GENERATOR))
		return act_create_generator();

	if (MSC_match(KW_SHADOW))
		return act_create_shadow();

	if (MSC_match(KW_STOGROUP))
		PAR_error("CREATE STOGROUP not supported");

	if (MSC_match(KW_SYNONYM))
		PAR_error("CREATE SYNONYM not supported");

	if (MSC_match(KW_TABLE))
		return act_create_table();

	if (MSC_match(KW_TABLESPACE))
		PAR_error("CREATE TABLESPACE not supported");

	if (MSC_match(KW_VIEW))
		return (act_create_view());

	if (gpreGlob.token_global.tok_keyword == KW_UNIQUE || gpreGlob.token_global.tok_keyword == KW_ASCENDING ||
		gpreGlob.token_global.tok_keyword == KW_DESCENDING || gpreGlob.token_global.tok_keyword == KW_INDEX)
	{
		bool descending = false;
		bool unique = false;
		while (true) {
			if (MSC_match(KW_UNIQUE))
				unique = true;
			else if (MSC_match(KW_ASCENDING))
				descending = false;
			else if (MSC_match(KW_DESCENDING))
				descending = true;
			else if (MSC_match(KW_INDEX))
				return act_create_index(unique, descending);
			else
				break;
		}
	}

	PAR_error("Invalid CREATE request");
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Handle an SQLish create database statement.
//  

static act* act_create_database(void)
{
	bool dummy;

	if (gpreGlob.isc_databases && gpreGlob.isc_databases->dbb_next)
		PAR_error
			("CREATE DATABASE only allowed in context of a single database");

	if (!gpreGlob.isc_databases) {
		// generate a dummy db 

		dummy = true;
		gpreGlob.isc_databases = (DBB) MSC_alloc_permanent(DBB_LEN);

		// allocate symbol block 

		gpre_sym* symbol = (gpre_sym*) MSC_alloc_permanent(SYM_LEN);

		// make it the default database 

		symbol->sym_type = SYM_database;
		symbol->sym_object = (gpre_ctx*) gpreGlob.isc_databases;
		symbol->sym_string = gpreGlob.database_name;

		// database block points to the symbol block 

		gpreGlob.isc_databases->dbb_name = symbol;
		gpreGlob.isc_databases->dbb_filename = NULL;
		gpreGlob.isc_databases->dbb_c_lc_ctype = gpreGlob.module_lc_ctype;
	}
	else
		dummy = false;

//  get database name 

	DBB db = NULL;
	if (isQuoted(gpreGlob.token_global.tok_type)) {
		db = dup_dbb(gpreGlob.isc_databases);
		TEXT* string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		db->dbb_filename = string;
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
		if (!gpreGlob.isc_databases->dbb_filename)
			gpreGlob.isc_databases->dbb_filename = string;
		PAR_get_token();
	}
	else
		CPR_s_error("<quoted database name>");

//  Create a request for generating DYN to add files to database. 

//  gpre_req* request = MSC_request (REQ_create_database);
	gpre_req* request = MSC_request(REQ_ddl);
	request->req_flags |= REQ_sql_database_dyn;
	request->req_database = db;

//  create action block 

	act* action = MSC_action(request, ACT_create_database);

	mdbb* mdb = (mdbb*) MSC_alloc(sizeof(mdbb));
	mdb->mdbb_database = db;
	action->act_object = (REF) mdb;
	action->act_whenever = gen_whenever();

	if (dummy) {
		// Create a ACT_database action 

		action->act_rest = MSC_action(0, ACT_database);
		action->act_rest->act_flags |= ACT_mark;
	}

//  Get optional specifications 

	bool extend_dpb = tail_database(ACT_create_database, db);

//  Create a request to generate dpb 

	gpreGlob.ada_flags |= gpreGlob.ADA_create_database;

	request = MSC_request(REQ_create_database);
	request->req_actions = action;
	mdb->mdbb_dpb_request = request;
	request->req_database = db;
	if (extend_dpb)
		request->req_flags |= REQ_extend_dpb;

	return action;
}


//____________________________________________________________
//  
//		Handle creation of a domain (global field). 
//  

static act* act_create_domain(void)
{
//  create request block 

	gpre_req* request = MSC_request(REQ_ddl);
	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can only CREATE DOMAIN in context of single database");

//  create action block 

	act* action = MSC_action(request, ACT_create_domain);

	gpre_fld* field = make_field(0);
	MSC_match(KW_AS);
	SQL_par_field_dtype(request, field, false);

//  Check if default value was specified 

	if (gpreGlob.token_global.tok_keyword == KW_DEFAULT) {
		field->fld_default_source = CPR_start_text();
		PAR_get_token();

		if (MSC_match(KW_USER))
			field->fld_default_value = MSC_node(nod_user_name, 0);
		else if (MSC_match(KW_NULL))
			field->fld_default_value = MSC_node(nod_null, 0);
		else {
			if (MSC_match(KW_MINUS)) {
				if (gpreGlob.token_global.tok_type != tok_number)
					CPR_s_error("<number>");

				GPRE_NOD literal_node = EXP_literal();
				field->fld_default_value = MSC_unary(nod_negate,
													 literal_node);
			}
			else if ((field->fld_default_value = EXP_literal()) == NULL)
				CPR_s_error("<constant>");
		}
		CPR_end_text(field->fld_default_source);
	}


//  Check for any column level constraints 

	cnstrt** cnstrt_ptr = &field->fld_constraints;
	bool in_constraints = true;

	while (in_constraints) {
		switch (gpreGlob.token_global.tok_keyword) {
		case KW_CONSTRAINT:
		case KW_CHECK:
		case KW_NOT:
			*cnstrt_ptr = par_field_constraint(request, field, 0);
			cnstrt_ptr = &(*cnstrt_ptr)->cnstrt_next;
			break;

		default:
			in_constraints = false;
			break;
		}
	}

	SQL_par_field_collate(request, field);
	SQL_adjust_field_dtype(field);

	action->act_whenever = gen_whenever();
	action->act_object = (REF) field;

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL create generator statement
//  

static act* act_create_generator(void)
{
	TEXT* generator_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
	SQL_resolve_identifier("<generator>", generator_name, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Generator name too long");
		
	gpre_req* request = MSC_request(REQ_ddl);
	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can only CREATE GENERATOR in context of single database");

//  create action block
	act* action = MSC_action(request, ACT_create_generator);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) generator_name;

	PAR_get_token();
	return action;
}


//____________________________________________________________
//  
//		Handle an SQL create index statement.
//  

static act* act_create_index(bool dups,
							bool descending)
{
//  create request block 

	gpre_req* request = MSC_request(REQ_ddl);

//  get index and table names and create index and relation blocks 

	SCHAR i_name[NAME_SIZE + 1];
	SQL_resolve_identifier("<index name>", i_name, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Index name too long");

	PAR_get_token();

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

	gpre_rel* relation = par_relation(request);

	IND index = make_index(request, i_name);
	index->ind_relation = relation;
	index->ind_flags |= dups ? IND_dup_flag : 0;
	index->ind_flags |= descending ? IND_descend : 0;

//  create action block 

	act* action = MSC_action(request, ACT_create_index);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) index;

//  parse field list and create corresponding field blocks 

	EXP_left_paren(0);
	gpre_fld** ptr = &index->ind_fields;

	for (;;) {
		*ptr = make_field(relation);
		ptr = &(*ptr)->fld_next;
		if (!MSC_match(KW_COMMA))
			break;
	}

	EXP_match_paren();

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL create shadow statement
//  

static act* act_create_shadow(void)
{
	gpre_req* request = MSC_request(REQ_ddl);
	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can only CREATE SHADOW in context of single database");

	act* action = MSC_action(request, ACT_create_shadow);
	action->act_whenever = gen_whenever();
	SLONG shadow_number = EXP_USHORT_ordinal(true);

	USHORT file_flags = 0;

	if (!range_positive_short_integer(shadow_number))
		PAR_error("Shadow number out of range");

	if (MSC_match(KW_MANUAL))
		file_flags |= FIL_manual;
	else
		MSC_match(KW_AUTO);

	if (MSC_match(KW_CONDITIONAL))
		file_flags |= FIL_conditional;

	FIL file_list, file;
	file_list = file = define_file();
	if (file->fil_start)
		PAR_error("Can not specify file start for first file");

	SLONG length = file->fil_length;
	file->fil_flags = file_flags;
	file->fil_shadow_number = (SSHORT) shadow_number;

	while (MSC_match(KW_FILE)) {
		file = define_file();
		if (!length && !file->fil_start) {
			TEXT err_string[1024];
			sprintf(err_string,
					"Preceding file did not specify length, so %s must include starting page number",
					file->fil_name);
			PAR_error(err_string);
		}
		length = file->fil_length;
		file->fil_flags = file_flags;
		file->fil_next = file_list;
		file_list = file;
	}
	action->act_object = (REF) file_list;
	return action;
}


//____________________________________________________________
//  
//		Handle an SQL create table statement.
//  

static act* act_create_table(void)
{
	gpre_req* request = MSC_request(REQ_ddl);
	gpre_rel* relation = par_relation(request);

	if (MSC_match(KW_EXTERNAL)) {
		TEXT* string = NULL;
		MSC_match(KW_FILE);
		if (isQuoted(gpreGlob.token_global.tok_type)) {
			string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
			relation->rel_ext_file = string;
			MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
			PAR_get_token();
		}
		else
			CPR_s_error("<quoted filename>");

		if (!check_filename(string))
			PAR_error("node name not permitted");	// a node name is not permitted in external file name 
	}

//  CHECK Constraints require the context to be set to the
//  current relation 

	gpre_ctx* context = MSC_context(request);
	request->req_contexts = context;
	context->ctx_relation = relation;

//  Reserve context 1 for relation on which constraint is
//  being defined 
	context->ctx_internal++;
	request->req_internal++;

//  create action block 

	act* action = MSC_action(request, ACT_create_table);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) relation;

	EXP_left_paren(0);
	gpre_fld** ptr = &relation->rel_fields;
	cnstrt** cnstrt_ptr = &relation->rel_constraints;

	for (;;) {
		switch (gpreGlob.token_global.tok_keyword) {
		case KW_CONSTRAINT:
		case KW_PRIMARY:
		case KW_UNIQUE:
		case KW_FOREIGN:
		case KW_CHECK:
			*cnstrt_ptr = par_table_constraint(request, relation);
			cnstrt_ptr = &(*cnstrt_ptr)->cnstrt_next;
			break;

		default:
			// parse field list and create corresponding field blocks 

			*ptr = par_field(request, relation);
			ptr = &(*ptr)->fld_next;
		}
		if (!MSC_match(KW_COMMA))
			break;
	}
	EXP_match_paren();

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL create view statement.
//  

static act* act_create_view(void)
{
	gpre_req* request = MSC_request(REQ_ddl);
	gpre_rel* relation = par_relation(request);

//  create action block 

	act* action = MSC_action(request, ACT_create_view);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) relation;

//  if field list is present parse it and create corresponding field blocks 

	if (MSC_match(KW_LEFT_PAREN)) {
		gpre_fld** ptr = &relation->rel_fields;
		for (;;) {
			*ptr = make_field(relation);
			ptr = &(*ptr)->fld_next;
			if (MSC_match(KW_RIGHT_PAREN))
				break;
			MSC_match(KW_COMMA);
		}
	}

//  skip 'AS SELECT' 

	if (!MSC_match(KW_AS))
		CPR_s_error("AS");

	relation->rel_view_text = CPR_start_text();
	if (!MSC_match(KW_SELECT))
		CPR_s_error("SELECT");

//  reserve context variable 0 for view 

	request->req_internal++;

//  parse the view SELECT 

	gpre_rse* select = SQE_select(request, true);
	relation->rel_view_rse = select;
	EXP_rse_cleanup(select);

	if (MSC_match(KW_WITH)) {
		if (!MSC_match(KW_CHECK))
			CPR_s_error("CHECK");
		if (!MSC_match(KW_OPTION))
			CPR_s_error("OPTION");
		relation->rel_flags |= REL_view_check;
	}

	CPR_end_text(relation->rel_view_text);

	return action;
}


//____________________________________________________________
//  
//		Recognize BEGIN/END DECLARE SECTION, 
//     and mark it as a good place to put miscellaneous
//     global routine stuff.
//  

static act* act_d_section( enum act_t type)
{
	if (!MSC_match(KW_DECLARE))
		CPR_s_error("DECLARE SECTION");

	if (!MSC_match(KW_SECTION))
		CPR_s_error("SECTION");

	MSC_match(KW_SEMI_COLON);

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = type;

	if (type == ACT_b_declare)
		gpreGlob.cur_routine = action; // Hmm, global var.

	if (!gpreGlob.isc_databases) {
		// allocate database block and link to db chain 

		gpreGlob.isc_databases = (DBB) MSC_alloc_permanent(DBB_LEN);

		// allocate symbol block 

		gpre_sym* symbol = (gpre_sym*) MSC_alloc_permanent(SYM_LEN);

		// make it a database, specifically this one 

		symbol->sym_type = SYM_database;
		symbol->sym_object = (gpre_ctx*) gpreGlob.isc_databases;
		symbol->sym_string = gpreGlob.database_name;

		// database block points to the symbol block 

		gpreGlob.isc_databases->dbb_name = symbol;
		gpreGlob.isc_databases->dbb_filename = NULL;
		gpreGlob.isc_databases->dbb_flags = DBB_sqlca;
		gpreGlob.isc_databases->dbb_c_lc_ctype = gpreGlob.module_lc_ctype;
		if (gpreGlob.sw_external)
			gpreGlob.isc_databases->dbb_scope = DBB_EXTERN;
	}
	else {
		/* Load up the symbol (hash) table with relation names from this database */

		MET_load_hash_table(gpreGlob.isc_databases);
	}

	HSH_insert(gpreGlob.isc_databases->dbb_name);

	if (MSC_match(KW_SQL)) {
		if (!MSC_match(KW_NAMES))
			CPR_s_error("NAMES ARE");
		if (!MSC_match(KW_ARE))
			CPR_s_error("NAMES ARE");
		gpre_sym* symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_charset);
		if (!symbol)
			PAR_error("The named CHARACTER SET was not found");
		if (gpreGlob.module_lc_ctype && !strcmp(gpreGlob.module_lc_ctype, symbol->sym_string))
			PAR_error("Duplicate specification of module CHARACTER SET.");
		gpreGlob.module_lc_ctype = symbol->sym_string;
		gpreGlob.isc_databases->dbb_c_lc_ctype = symbol->sym_string;
		CPR_token();
	}

	return action;
}


//____________________________________________________________
//  
//		Parse the SQL cursor declaration.
//  

static act* act_declare(void)
{
	DBB db = NULL;

	if (gpreGlob.token_global.tok_symbol && (gpreGlob.token_global.tok_symbol->sym_type == SYM_database)) {
		// must be a database specifier in a DECLARE TABLE statement 

		db = (DBB) gpreGlob.token_global.tok_symbol->sym_object;
		PAR_get_token();
		if (!MSC_match(KW_DOT))
			CPR_s_error(". (period)");
		gpre_sym* symbol = PAR_symbol(SYM_dummy);
		if (gpreGlob.token_global.tok_keyword != KW_TABLE)
			CPR_s_error("TABLE");
		return (act_declare_table(symbol, db));
	}

	switch (gpreGlob.token_global.tok_keyword) {
	case KW_FILTER:
		return (act_declare_filter());
		break;

	case KW_EXTERNAL:
		PAR_get_token();
		if (MSC_match(KW_FUNCTION))
			return (act_declare_udf());
		else
			CPR_s_error("FUNCTION");
		break;
	}

	bool delimited = false;

	{ // Scope
		TEXT t_str[MAX_CURSOR_SIZE];
		SQL_resolve_identifier("<Cursor Name>", t_str, MAX_CURSOR_SIZE);
		gpre_sym* symb = NULL;
		if (gpreGlob.token_global.tok_type == tok_dblquoted)
		{
			delimited = true;
			symb = HSH_lookup(t_str);
		}
		else {
			symb = HSH_lookup2(t_str);
		}
		if (symb &&
			(symb->sym_type == SYM_cursor ||
			 symb->sym_type == SYM_delimited_cursor))
		{
			char s[ERROR_LENGTH];
			fb_utils::snprintf(s, sizeof(s), "symbol %s is already in use", t_str);
			PAR_error(s);
		}
	} // end scope

#ifdef SCROLLABLE_CURSORS
	bool scroll = false;
#endif
	act* action = NULL;
	gpre_sym* symbol = PAR_symbol(SYM_cursor);

	switch (gpreGlob.token_global.tok_keyword) {
	case KW_TABLE:
		return (act_declare_table(symbol, 0));

#ifdef SCROLLABLE_CURSORS
	case KW_SCROLL:
		PAR_get_token();
		scroll = true;
		if (gpreGlob.token_global.tok_keyword != KW_CURSOR)
			CPR_s_error("CURSOR");
#endif
	case KW_CURSOR:
		PAR_get_token();
		if (!MSC_match(KW_FOR))
			CPR_s_error("FOR");
		if (MSC_match(KW_SELECT)) {
			gpre_req* request = MSC_request(REQ_cursor);
			request->req_flags |= REQ_sql_cursor | REQ_sql_declare_cursor;
#ifdef SCROLLABLE_CURSORS
			if (scroll)
				request->req_flags |= REQ_scroll;
#endif
			symbol->sym_object = (gpre_ctx*) request;
			action = MSC_action(request, ACT_cursor);
			action->act_object = (REF) symbol;
			symbol->sym_type =
				(delimited) ? SYM_delimited_cursor : SYM_cursor;
			request->req_rse = SQE_select(request, false);
			if (MSC_match(KW_FOR)) {
				if (!MSC_match(KW_UPDATE))
					CPR_s_error("UPDATE");
				if (!MSC_match(KW_OF))
					CPR_s_error("OF");

				do {
					CPR_token();
				} while (MSC_match(KW_COMMA));
			}
			EXP_rse_cleanup(request->req_rse);
		}
		else if (MSC_match(KW_READ)) {
			action = act_open_blob(ACT_blob_open, symbol);
			symbol->sym_type =
				(delimited) ? SYM_delimited_cursor : SYM_cursor;
		}
		else if (MSC_match(KW_INSERT)) {
			action = act_open_blob(ACT_blob_create, symbol);
			symbol->sym_type =
				(delimited) ? SYM_delimited_cursor : SYM_cursor;
		}
		else {
			DYN statement = par_statement();
			symbol->sym_object = (gpre_ctx*) statement;
			if (MSC_match(KW_FOR)) {
				if (!MSC_match(KW_UPDATE))
					CPR_s_error("UPDATE");
				if (!MSC_match(KW_OF))
					CPR_s_error("OF");

				do {
					CPR_token();
				} while (MSC_match(KW_COMMA));
			}
			symbol->sym_type = SYM_dyn_cursor;
			statement->dyn_cursor_name = symbol;
			action = (act*) MSC_alloc(ACT_LEN);
			action->act_type = ACT_dyn_cursor;
			action->act_object = (REF) statement;
			action->act_whenever = gen_whenever();
		}
		HSH_insert(symbol);
		return action;

	default:
		while (MSC_match(KW_COMMA))
			; // empty loop body
		if (MSC_match(KW_STATEMENT)) {
			action = (act*) MSC_alloc(ACT_LEN);
			action->act_type = ACT_dyn_statement;
			return action;
		}
		CPR_s_error("CURSOR, STATEMENT or TABLE");
	}
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Handle an SQL declare filter statement
//  

static act* act_declare_filter(void)
{
	gpre_req* request = MSC_request(REQ_ddl);

	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can only DECLARE FILTER in context of single database");

	PAR_get_token();
	FLTR filter = (FLTR) MSC_alloc(FLTR_LEN);
	filter->fltr_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
	SQL_resolve_identifier("<identifier>", filter->fltr_name, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Filter name too long");

	PAR_get_token();

//  create action block 
	act* action = MSC_action(request, ACT_declare_filter);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) filter;

	SLONG input_type;
	if (MSC_match(KW_INPUT_TYPE))
		input_type = EXP_SSHORT_ordinal(true);
	else
		CPR_s_error("INPUT_TYPE");

	SLONG output_type;
	if (MSC_match(KW_OUTPUT_TYPE))
		output_type = EXP_SSHORT_ordinal(true);
	else
		CPR_s_error("OUTPUT_TYPE");

	if (!range_short_integer(input_type) || !range_short_integer(output_type))
		PAR_error("Blob sub_type out of range");

	filter->fltr_input_type = (SSHORT) input_type;
	filter->fltr_output_type = (SSHORT) output_type;

	if (MSC_match(KW_ENTRY_POINT))
		filter->fltr_entry_point = extract_string(true);
	else
		CPR_s_error("ENTRY_POINT");

	if (MSC_match(KW_MODULE_NAME))
		filter->fltr_module_name = extract_string(true);
	else
		CPR_s_error("MODULE_NAME");

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL declare table statement.
//  

static act* act_declare_table( gpre_sym* symbol, DBB db)
{
//  create a local request block

	gpre_req* request = (gpre_req*) MSC_alloc(REQ_LEN);
	request->req_type = REQ_ddl;

//  create relation block 

	gpre_rel* relation = make_relation(0, symbol->sym_string);

	if (!db)
		db = relation->rel_database;

	request->req_database = db;

	relation->rel_next = db->dbb_relations;
	db->dbb_relations = relation;
	gpre_fld* dbkey = MET_make_field("rdb$db_key", dtype_text, 8, false);
	relation->rel_dbkey = dbkey;
	dbkey->fld_flags |= FLD_dbkey | FLD_text | FLD_charset;
	dbkey->fld_ttype = ttype_binary;

//  if relation name already in incore metadata, remove it & its fields 

	gpre_sym* old_symbol = HSH_lookup(relation->rel_symbol->sym_string);

	while (old_symbol) 
	{
		if (old_symbol->sym_type == SYM_relation) {
			gpre_rel* tmp_relation = (gpre_rel*) old_symbol->sym_object;
			if (tmp_relation->rel_meta)
				PAR_error("Multiple DECLARE TABLE statements for table");
			gpre_sym* tmp_symbol = old_symbol->sym_homonym;
			HSH_remove(old_symbol);
			for (gpre_fld* field = tmp_relation->rel_fields; field;
				field = field->fld_next)
			{
				 HSH_remove(field->fld_symbol);
			}
			old_symbol = tmp_symbol;
		}
		else
			old_symbol = old_symbol->sym_homonym;
	}

//  add new symbol to incore metadata 

	HSH_insert(relation->rel_symbol);

//  create action block 

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_noop;
	action->act_object = (REF) relation;

//  parse field list and create corresponding field blocks 
//  include size information for message length calculations 

	PAR_get_token();
	EXP_left_paren(0);
	USHORT count = 0;
	gpre_fld** ptr = &relation->rel_fields;

	for (;;) {
		gpre_fld* field = par_field(request, relation);
		*ptr = field;
		ptr = &field->fld_next;
		HSH_insert(field->fld_symbol);
		field->fld_position = count++;
		field->fld_flags &= ~FLD_meta;
		SQL_adjust_field_dtype(field);

		if (!MSC_match(KW_COMMA))
			break;
	}

	EXP_match_paren();

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL declare external statement
//  

static act* act_declare_udf(void)
{
	gpre_req* request = MSC_request(REQ_ddl);

	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error
			("Can only DECLARE EXTERNAL FUNCTION in context of single database");

	decl_udf* udf_declaration = (decl_udf*) MSC_alloc(DECL_UDF_LEN);
	TEXT* udf_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
	SQL_resolve_identifier("<identifier>", udf_name, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("External function name too long");
		
	udf_declaration->decl_udf_name = udf_name;
	PAR_get_token();

//  create action block 
	act* action = MSC_action(request, ACT_declare_udf);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) udf_declaration;

	gpre_fld** ptr = &udf_declaration->decl_udf_arg_list;
	while (true) {
		if (MSC_match(KW_RETURNS)) {
			if (MSC_match(KW_PARAMETER)) {
				const SLONG return_parameter = EXP_pos_USHORT_ordinal(true);
				if (return_parameter > 10)
					PAR_error("return parameter not in range");
				fb_assert(return_parameter <= MAX_SSHORT);
				udf_declaration->decl_udf_return_parameter = (SSHORT) return_parameter;
			}
			else {
				gpre_fld* field = (gpre_fld*) MSC_alloc(FLD_LEN);
				field->fld_flags |= (FLD_meta | FLD_meta_cstring);
				SQL_par_field_dtype(request, field, true);
				SQL_adjust_field_dtype(field);
				udf_declaration->decl_udf_return_type = field;
				MSC_match(KW_BY);
				if (MSC_match(KW_VALUE))
					udf_declaration->decl_udf_return_mode = FUN_value;
				else
					udf_declaration->decl_udf_return_mode = FUN_reference;
			}
			break;
		}
		else {
			gpre_fld* field = (gpre_fld*) MSC_alloc(FLD_LEN);
			field->fld_flags |= (FLD_meta | FLD_meta_cstring);
			SQL_par_field_dtype(request, field, true);
			SQL_adjust_field_dtype(field);
			*ptr = field;
			ptr = &(field->fld_next);
			MSC_match(KW_COMMA);
		}
	}

	if (MSC_match(KW_ENTRY_POINT))
		udf_declaration->decl_udf_entry_point = extract_string(true);
	else
		CPR_s_error("ENTRY_POINT");

	if (MSC_match(KW_MODULE_NAME))
		udf_declaration->decl_udf_module_name = extract_string(true);
	else
		CPR_s_error("MODULE_NAME");

	return action;
}


//____________________________________________________________
//  
//		Parse an update action.  This is a little more complicated
//		because SQL confuses the update of a cursor with a mass update.
//		The syntax, and therefor the code, I fear, is a mess.
//  

static act* act_delete(void)
{
	const TEXT* transaction;

	par_options(&transaction);

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

//  First comes the relation.  Unfortunately, there is no way to identify
//  its database until the cursor is known.  Sigh.  Save the token. 

	TEXT r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];
	SQL_relation_name(r_name, db_name, owner_name);

//  Parse the optional alias (context variable) 

	gpre_sym* alias = (gpreGlob.token_global.tok_symbol) ? NULL : PAR_symbol(SYM_dummy);

//  Now the moment of truth.  If the next few tokens are WHERE CURRENT OF
//  then this is a sub-action of an existing request.  If not, then it is
//  a free standing request 

	gpre_req* request = MSC_request(REQ_mass_update);
	upd* update = (upd*) MSC_alloc(UPD_LEN);

	const bool where = MSC_match(KW_WITH);
	if (where && MSC_match(KW_CURRENT)) {
		if (!MSC_match(KW_OF))
			CPR_s_error("OF <cursor>");
		gpreGlob.requests = request->req_next;
		gpreGlob.cur_routine->act_object = (REF) request->req_routine; // Beware global var
		MSC_free((UCHAR *) request);
		request = par_cursor(NULL);
		if ((transaction || request->req_trans) &&
			(!transaction || !request->req_trans ||
			 strcmp(transaction, request->req_trans)))
		{
			if (transaction)
				PAR_error("different transaction for select and delete");
			else {				// does not specify transaction clause in      
				//   "delete ... where cuurent of cursor" stmt 
				SSHORT trans_nm_len = strlen(request->req_trans);
				SCHAR* str_2 = (SCHAR*) MSC_alloc(trans_nm_len + 1);
				transaction = str_2;
				const SCHAR* str_1 = request->req_trans;
				do {
					*str_2++ = *str_1++;
				} while (--trans_nm_len);
			}
		}
		request->req_trans = transaction;
		gpre_rel* relation = SQL_relation(request, r_name, db_name, owner_name, true);
		gpre_rse* selection = request->req_rse;
		gpre_ctx* context = NULL;
		SSHORT i;
		for (i = 0; i < selection->rse_count; i++) {
			context = selection->rse_context[i];
			if (context->ctx_relation == relation)
				break;
		}
		if (i == selection->rse_count)
			PAR_error("table not in request");
		update->upd_request = request;
		update->upd_source = context;

		act* action = MSC_action(request, ACT_erase);
		action->act_object = (REF) update;
		action->act_whenever = gen_whenever();
		return action;
	}

	request->req_trans = transaction;

//  How amusing.  After all that work, it wasn't a sub-action at all.
//  Neat.  Take the pieces and build a complete request.  Start by
//  figuring out what database is involved.  

	gpre_rel* relation = SQL_relation(request, r_name, db_name, owner_name, true);

	gpre_rse* selection = (gpre_rse*) MSC_alloc(RSE_LEN(1));
	request->req_rse = selection;
	selection->rse_count = 1;
	gpre_ctx* context = MSC_context(request);
	selection->rse_context[0] = context;
	context->ctx_relation = relation;

	bool hsh_rm = false;
	if (alias && !gpreGlob.token_global.tok_symbol) {
		alias->sym_type = SYM_context;
		alias->sym_object = context;
		context->ctx_symbol = alias;
		gpreGlob.token_global.tok_symbol = alias;
		HSH_insert(alias);
		hsh_rm = true;
	}

	if (where)
		selection->rse_boolean = SQE_boolean(request, 0);

	request->req_node = MSC_node(nod_erase, 0);

	act* action = MSC_action(request, ACT_loop);
	action->act_whenever = gen_whenever();

	if (hsh_rm)
		HSH_remove(alias);

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL describe statement.
//			Reject
//  

static act* act_describe(void)
{
	bool in_sqlda;

	if (MSC_match(KW_INPUT))
		in_sqlda = true;
	else {
		MSC_match(KW_OUTPUT);
		in_sqlda = false;
	}

	DYN statement = par_statement();

	if (!MSC_match(KW_INTO)) {
		/* check for SQL2 syntax 
		   "USING SQL DESCRIPTOR sqlda" */

		if (!MSC_match(KW_USING) || !MSC_match(KW_SQL) || !MSC_match(KW_DESCRIPTOR))
			CPR_s_error("INTO or USING SQL DESCRIPTOR sqlda");
	}
	else if (MSC_match(KW_SQL) && !MSC_match(KW_DESCRIPTOR))
		CPR_s_error("INTO SQL DESCRIPTOR sqlda");

	statement->dyn_sqlda = PAR_native_value(false, false);
	act* action = (act*) MSC_alloc(ACT_LEN);
	if (in_sqlda)
		action->act_type = ACT_dyn_describe_input;
	else
		action->act_type = ACT_dyn_describe;
	action->act_object = (REF) statement;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Parse a FINISH statement.
//  

static act* act_disconnect(void)
{
	act* action = MSC_action(0, ACT_disconnect);
	action->act_whenever = gen_whenever();
	bool all = MSC_match(KW_ALL) || MSC_match(KW_DEFAULT);

	if (!all) {
		if (MSC_match(KW_CURRENT))
			PAR_error("DISCONNECT CURRENT not supported");
		gpre_sym* test_symbol = gpreGlob.token_global.tok_symbol;
		if (!(test_symbol && test_symbol->sym_type == SYM_database))
		{
			CPR_s_error("ALL, DEFAULT, or <database handle>");
		}
		while (true) {
			gpre_sym* symbol = gpreGlob.token_global.tok_symbol;
			if (symbol && symbol->sym_type == SYM_database) {
				rdy* ready = (rdy*) MSC_alloc(RDY_LEN);
				ready->rdy_next = (rdy*) action->act_object;
				action->act_object = (REF) ready;
				ready->rdy_database = (DBB) symbol->sym_object;
				PAR_get_token();
			}
			else
				CPR_s_error("<database handle>");
			if (!MSC_match(KW_COMMA))
				break;
		}
	}

	if (gpreGlob.sw_language == lang_ada)
		MSC_match(KW_SEMI_COLON);
	return action;
}


//____________________________________________________________
//  
//		Handle an SQL drop statement.
//  

static act* act_drop(void)
{
	act* action = NULL;
	dbb* db = NULL;
	gpre_req* request = NULL;
	gpre_rel* relation = NULL;
	TEXT* identifier_name;

	switch (gpreGlob.token_global.tok_keyword) {
	case KW_DATABASE:
		{
		PAR_error("DROP DATABASE not supported");

		request = MSC_request(REQ_ddl);
		PAR_get_token();
		if (!isQuoted(gpreGlob.token_global.tok_type))
			CPR_s_error("<quoted database name>");
		db = (DBB) MSC_alloc(DBB_LEN);
		SCHAR* db_string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		db->dbb_filename = db_string;
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, db_string);
		gpre_sym* symbol = PAR_symbol(SYM_dummy);
		db->dbb_name = symbol;
		symbol->sym_type = SYM_database;
		symbol->sym_object = (gpre_ctx*) db;
		action = MSC_action(request, ACT_drop_database);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) db;
		PAR_get_token();
		}
		return action;

	case KW_DOMAIN:
		request = MSC_request(REQ_ddl);
		if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
			request->req_database = gpreGlob.isc_databases;
		else
			PAR_error("Can only DROP DOMAIN in context of single database");
		PAR_get_token();
		identifier_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
		SQL_resolve_identifier("<identifier>", identifier_name, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("Domain name too long");
			
		action = MSC_action(request, ACT_drop_domain);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) identifier_name;
		PAR_get_token();
		return action;

	case KW_FILTER:
		request = MSC_request(REQ_ddl);
		if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
			request->req_database = gpreGlob.isc_databases;
		else
			PAR_error("Can only DROP FILTER in context of single database");
		PAR_get_token();
		identifier_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
		SQL_resolve_identifier("<identifier>", identifier_name, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("Filter name too long");
			
		action = MSC_action(request, ACT_drop_filter);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) identifier_name;
		PAR_get_token();
		return action;

	case KW_EXTERNAL:

		PAR_get_token();
		if (!MSC_match(KW_FUNCTION))
			CPR_s_error("FUNCTION");
		request = MSC_request(REQ_ddl);
		if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
			request->req_database = gpreGlob.isc_databases;
		else
			PAR_error
				("Can only DROP EXTERNAL FUNCTION in context of a single database");

		identifier_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
		SQL_resolve_identifier("<identifier>", identifier_name, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("External function name too long");
			
		action = MSC_action(request, ACT_drop_udf);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) identifier_name;
		PAR_get_token();
		return action;

	case KW_INDEX:
		{
		request = MSC_request(REQ_ddl);
		PAR_get_token();
		SQL_resolve_identifier("<index name>", NULL, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("Index name too long");
			
		IND index = make_index(request, gpreGlob.token_global.tok_string);
		action = MSC_action(request, ACT_drop_index);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) index;
		PAR_get_token();
		}
		return action;

	case KW_STOGROUP:
		PAR_error("DROP STOGROUP not supported");

	case KW_TABLESPACE:
		PAR_error("DROP TABLESPACE not supported");

	case KW_SYNONYM:
		PAR_error("DROP SYNONYM request not allowed");

	case KW_SHADOW:
		{
		request = MSC_request(REQ_ddl);
		if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
			request->req_database = gpreGlob.isc_databases;
		else
			PAR_error("Can only DROP SHADOW in context of a single database");
		PAR_get_token();
		action = MSC_action(request, ACT_drop_shadow);
		action->act_whenever = gen_whenever();
		SLONG num = EXP_USHORT_ordinal(true);
		if (!range_positive_short_integer(num))
			PAR_error("Shadow number out of range");
		action->act_object = (REF)(IPTR)num;
		}
		return action;

	case KW_TABLE:
		request = MSC_request(REQ_ddl);
		PAR_get_token();
		relation = par_relation(request);
		action = MSC_action(request, ACT_drop_table);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) relation;
		return action;

	case KW_VIEW:
		request = MSC_request(REQ_ddl);
		PAR_get_token();
		relation = par_relation(request);
		action = MSC_action(request, ACT_drop_view);
		action->act_whenever = gen_whenever();
		action->act_object = (REF) relation;
		return action;

	default:
		PAR_error("Invalid DROP request");
	}
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Handle an SQL event statement
//  

static act* act_event(void)
{
	act* action = NULL;

	if (MSC_match(KW_INIT))
		action = PAR_event_init(true);
	else if (MSC_match(KW_WAIT))
		action = PAR_event_wait(true);
	else
		CPR_s_error("INIT or WAIT");
	action->act_whenever = gen_whenever();
	return action;
}


//____________________________________________________________
//  
//		Handle an SQL execute statement.
//			Reject
//  

static act* act_execute(void)
{
	if (MSC_match(KW_PROCEDURE))
		return act_procedure();

//  EXECUTE IMMEDIATE is a different sort of duck 

	if (MSC_match(KW_IMMEDIATE)) {
		if (gpreGlob.isc_databases && gpreGlob.isc_databases->dbb_next) {
			TEXT s[ERROR_LENGTH];
			sprintf(s,
					"Executing dynamic SQL statement in context of database %s",
					gpreGlob.isc_databases->dbb_name->sym_string);
			CPR_warn(s);
		}
		DYN statement = (DYN) MSC_alloc(DYN_LEN);
		par_options(&statement->dyn_trans);

		switch (gpreGlob.sw_sql_dialect) {
		case 1:
			if ((!isQuoted(gpreGlob.token_global.tok_type)) && (!MSC_match(KW_COLON)))
				CPR_s_error(": <string expression>");
			break;

		default:
			if (gpreGlob.token_global.tok_type != tok_sglquoted && (!MSC_match(KW_COLON)))
				CPR_s_error(": <string expression>");
			break;
		}
		statement->dyn_string = PAR_native_value(false, false);
		par_using(statement);
		if (statement->dyn_using)
			PAR_error("Using host-variable list not supported.");
		par_into(statement);
		statement->dyn_database = gpreGlob.isc_databases;

		act* action = (act*) MSC_alloc(ACT_LEN);
		action->act_type = ACT_dyn_immediate;
		action->act_object = (REF) statement;
		action->act_whenever = gen_whenever();
		return action;
	}

//  Ordinary form of EXECUTE 

	const TEXT* transaction;
	par_options(&transaction);
	DYN statement = par_statement();
	statement->dyn_trans = transaction;

	par_using(statement);
	if (statement->dyn_using)
		PAR_error("Using host-variable list not supported.");
	par_into(statement);

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_dyn_execute;
	action->act_object = (REF) statement;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Parse the SQL fetch statement.
//  

static act* act_fetch(void)
{
//  Handle dynamic SQL statement, if appropriate 

	DYN cursor = par_dynamic_cursor();
	if (cursor) {
		DYN statement = (DYN) MSC_alloc(DYN_LEN);
		statement->dyn_statement_name = cursor->dyn_statement_name;
		statement->dyn_cursor_name = cursor->dyn_cursor_name;
		if (MSC_match(KW_USING) || MSC_match(KW_INTO)) {
			MSC_match(KW_SQL);		// optional for backward compatibility

			if (MSC_match(KW_DESCRIPTOR))
				statement->dyn_sqlda = PAR_native_value(false, false);
			else
				statement->dyn_using = (GPRE_NOD) SQE_list(SQE_variable, NULL, false);
			if (statement->dyn_using)
				PAR_error("Using host-variable list not supported.");
		}
		act* action = (act*) MSC_alloc(ACT_LEN);
		action->act_type = ACT_dyn_fetch;
		action->act_object = (REF) statement;
		action->act_whenever = gen_whenever();
		return action;
	}

//  Statement is static SQL 

#ifdef SCROLLABLE_CURSORS
//  parse the fetch orientation 

	USHORT direction = blr_forward;
	const TEXT* direction_string = NULL;
	GPRE_NOD offset_node = NULL;

	if (!MSC_match(KW_NEXT)) {
		if (MSC_match(KW_PRIOR)) {
			direction = blr_backward;
			direction_string = "1";
		}
		else if (MSC_match(KW_FIRST)) {
			direction = blr_bof_forward;
			direction_string = "2";
		}
		else if (MSC_match(KW_LAST)) {
			direction = blr_eof_backward;
			direction_string = "3";
		}
		else if (MSC_match(KW_RELATIVE)) {
			direction = blr_forward;
			direction_string = "0";
			offset_node = SQE_value(0, false, NULL, NULL);
			PAR_get_token();
		}
		else if (MSC_match(KW_ABSOLUTE)) {
			direction = blr_bof_forward;
			direction_string = "2";
			offset_node = SQE_value(0, false, NULL, NULL);
			PAR_get_token();
		}
	}

	MSC_match(KW_FROM);
#endif

	gpre_req* request = par_cursor(NULL);

#ifdef SCROLLABLE_CURSORS
//  if scrolling is required, set up the offset and direction parameters 
//  to be passed to the running request via the asynchronous message-- 
//  there could be multiple FETCH statements, so we need to store multiple 
//  value blocks, one for each FETCH statement 

	if (direction != blr_forward) {
		if (!(request->req_flags & REQ_scroll))
			PAR_error
				("Must use SCROLL modifier for DECLARE CURSOR to enable scrolling.");

		// create a literal for the direction parameter

		REF reference = request->req_avalues;
		if (!reference)
			reference = request->req_avalues = (REF) MSC_alloc(REF_LEN);

		VAL value = reference->ref_values;
		if (!value)
			reference->ref_values = value = (VAL) MSC_alloc(VAL_LEN);
		else {
			while (value->val_next) {
				value = value->val_next;
			}
			value->val_next = (VAL) MSC_alloc(VAL_LEN);
			value = value->val_next;
		}

		TEXT* string = (TEXT*) MSC_alloc(2);
		value->val_value = string;
		MSC_copy(direction_string, 1, string);

		// create a reference to the offset variable or literal

		if (!reference->ref_next)
			reference->ref_next = (REF) MSC_alloc(REF_LEN);
		reference = reference->ref_next;

		value = reference->ref_values;
		if (!value)
			reference->ref_values = value = (VAL) MSC_alloc(VAL_LEN);
		else {
			while (value->val_next) {
				value = value->val_next;
			}
			value->val_next = (VAL) MSC_alloc(VAL_LEN);
			value = value->val_next;
		}

		if (offset_node)
			value->val_value = ((REF) offset_node->nod_arg[0])->ref_value;
		else {
			const TEXT* offset_string = "1";
			string = (TEXT*) MSC_alloc(2);
			value->val_value = string;
			MSC_copy(offset_string, 1, string);
		}
	}
#endif

	if (request->req_flags & REQ_sql_blob_create)
		PAR_error("Fetching from a blob being created is not allowed.");

	act* action = MSC_action(request, ACT_fetch);
	action->act_whenever = gen_whenever();

	if (request->req_flags & REQ_sql_blob_open) {
		if (!MSC_match(KW_INTO))
			CPR_s_error("INTO");
		action->act_object = (REF) SQE_variable(NULL, false, NULL, NULL);
		action->act_type = ACT_get_segment;
	}
	else if (MSC_match(KW_INTO)) {
		action->act_object = (REF) SQE_list(SQE_variable, request, false);
		gpre_rse* select = request->req_rse;
		into(request, select->rse_fields, (GPRE_NOD) action->act_object);
	}

	return action;
}


//____________________________________________________________
//  
//		Parse an SQL grant or revoke statement.  Set up grant/revoke
//		blocks, fill in all of the privilege information, and
//		attach them to an action block of type GRANT or REVOKE.
//  

static act* act_grant_revoke( enum act_t type)
{
	gpre_req* request = MSC_request(REQ_ddl);
	PRV priv_block = MSC_privilege_block();

//  if it is revoke action, parse the optional grant option for 

	if (type == ACT_dyn_revoke) {
		if (MSC_match(KW_GRANT)) {
			if (!MSC_match(KW_OPTION))
				CPR_s_error("OPTION");
			if (!MSC_match(KW_FOR))
				CPR_s_error("FOR");
			priv_block->prv_privileges |= PRV_grant_option;
		}
	}

	bool execute_priv = false;

	if (MSC_match(KW_ALL)) {
		MSC_match(KW_PRIVILEGES);	//  Keyword 'privileges' is optional  
		priv_block->prv_privileges = PRV_all;
	}
	else if (MSC_match(KW_EXECUTE)) {
		priv_block->prv_privileges |= PRV_execute;
		execute_priv = true;
	}
	else {
		gpre_lls** fields = &priv_block->prv_fields;
		while (true) {
			if (MSC_match(KW_SELECT))
				priv_block->prv_privileges |= PRV_select;
			else if (MSC_match(KW_INSERT))
				priv_block->prv_privileges |= PRV_insert;
			else if (MSC_match(KW_DELETE))
				priv_block->prv_privileges |= PRV_delete;
			else if (MSC_match(KW_UPDATE)) {
				priv_block->prv_privileges |= PRV_update;
				if (MSC_match(KW_LEFT_PAREN)) {
					SCHAR col_name[NAME_SIZE + 1];
					do {
						SQL_resolve_identifier("<column name>", col_name, NAME_SIZE + 1);
						if (gpreGlob.token_global.tok_length >= NAME_SIZE)
							PAR_error("Field name too long");
							
						STR field_name = (STR) MSC_string(col_name);
						MSC_push((GPRE_NOD) field_name, fields);
						fields = &(*fields)->lls_next;
						CPR_token();
					} while (MSC_match(KW_COMMA));

					if (!MSC_match(KW_RIGHT_PAREN))
						CPR_s_error("<right parenthesis>");
				}
			}

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

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

	STR relation_name = NULL;
	SCHAR r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];

	if (execute_priv) {
		if (!MSC_match(KW_PROCEDURE))
			CPR_s_error("PROCEDURE");
		SQL_relation_name(r_name, db_name, owner_name);
		gpre_prc* procedure = SQL_procedure(request, r_name, db_name, owner_name, true);
		relation_name = (STR) MSC_string(r_name);
		priv_block->prv_relation = relation_name;
		priv_block->prv_object_dyn = isc_dyn_prc_name;
	}
	else {
		MSC_match(KW_TABLE);		// filler word 
		SQL_relation_name(r_name, db_name, owner_name);
		gpre_rel* relation = SQL_relation(request, r_name, db_name, owner_name, true);
		relation_name = (STR) MSC_string(r_name);
		priv_block->prv_relation = relation_name;
		priv_block->prv_object_dyn = isc_dyn_rel_name;
	}

	if (type == ACT_dyn_grant) {
		if (!MSC_match(KW_TO))
			CPR_s_error("TO");
	}
	else {						/*  type == ACT_dyn_revoke  */

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

	bool grant_option_legal = true;
	gpre_usn* usernames = 0;
	gpre_usn* user = 0;
	USHORT user_dyn = 0;
	SCHAR s[ERROR_LENGTH];

	while (true) {
		if (MSC_match(KW_PROCEDURE)) {
			SQL_relation_name(r_name, db_name, owner_name);
			gpre_prc* procedure =
				SQL_procedure(request, r_name, db_name, owner_name, true);
			user_dyn = isc_dyn_grant_proc;
			grant_option_legal = false;
		}
		else if (MSC_match(KW_TRIGGER)) {
			SQL_relation_name(r_name, db_name, owner_name);
			if (!MET_trigger_exists(request->req_database, r_name)) {
				sprintf(s, "TRIGGER %s not defined", r_name);
				PAR_error(s);
			}
			user_dyn = isc_dyn_grant_trig;
			grant_option_legal = false;
		}
		else if (MSC_match(KW_VIEW)) {
			SQL_relation_name(r_name, db_name, owner_name);
			if (!MET_get_view_relation
				(request, r_name, relation_name->str_string, 0))
			{
				sprintf(s, "VIEW %s not defined on table %s", r_name,
						relation_name->str_string);
				PAR_error(s);
			}
			user_dyn = isc_dyn_grant_view;
			grant_option_legal = false;
		}
		else if (MSC_match(KW_PUBLIC)) {
			strcpy(r_name, "PUBLIC");
			user_dyn = isc_dyn_grant_user;
		}
		else {
			MSC_match(KW_USER);
			if (gpreGlob.token_global.tok_type != tok_ident)
				CPR_s_error("<user name identifier>");
			else
				to_upcase(gpreGlob.token_global.tok_string, r_name, sizeof(r_name));
			user_dyn = isc_dyn_grant_user;
			CPR_token();
		}

		if (!usernames) {
			usernames = MSC_username(r_name, user_dyn);
			user = usernames;
		}
		else {
			user->usn_next = MSC_username(r_name, user_dyn);
			user = user->usn_next;
		}

		if (!MSC_match(KW_COMMA))
			break;
	}

//  If this is a grant, do we have the optional WITH GRANT OPTION specification? 

	if ((type == ACT_dyn_grant) && grant_option_legal)
		if (MSC_match(KW_WITH)) {
			if (!MSC_match(KW_GRANT))
				CPR_s_error("GRANT");
			if (!MSC_match(KW_OPTION))
				CPR_s_error("OPTION");
			priv_block->prv_privileges |= PRV_grant_option;
		}

//  create action block 

	act* action = MSC_action(request, type);
	action->act_next = 0;

	PRV last_priv_block = priv_block;
	bool first = true;

	for (user = usernames; user; user = user->usn_next) {
		// create and fill privilege block 

		priv_block = MSC_privilege_block();
		priv_block->prv_username = user->usn_name;
		priv_block->prv_user_dyn = user->usn_dyn;
		priv_block->prv_privileges = last_priv_block->prv_privileges;
		priv_block->prv_relation = last_priv_block->prv_relation;
		priv_block->prv_object_dyn = last_priv_block->prv_object_dyn;
		priv_block->prv_fields = last_priv_block->prv_fields;
		if (first) {
			action->act_object = (REF) priv_block;
			first = false;
		}
		else
			last_priv_block->prv_next = priv_block;
		last_priv_block = priv_block;
	}
	action->act_request = request;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//  

static act* act_include(void)
{
	PAR_get_token();
	MSC_match(KW_SEMI_COLON);

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_b_declare;
	gpreGlob.cur_routine = action; // Hmm, global var

	if (!gpreGlob.isc_databases) {
		// allocate database block and link to db chain 

		gpreGlob.isc_databases = (DBB) MSC_alloc_permanent(DBB_LEN);

		// allocate symbol block 

		gpre_sym* symbol = (gpre_sym*) MSC_alloc_permanent(SYM_LEN);

		// make it a database, specifically this one 

		symbol->sym_type = SYM_database;
		symbol->sym_object = (gpre_ctx*) gpreGlob.isc_databases;
		symbol->sym_string = gpreGlob.database_name;

		// database block points to the symbol block 

		gpreGlob.isc_databases->dbb_name = symbol;
		gpreGlob.isc_databases->dbb_filename = NULL;
		gpreGlob.isc_databases->dbb_flags = DBB_sqlca;
		gpreGlob.isc_databases->dbb_c_lc_ctype = gpreGlob.module_lc_ctype;
		if (gpreGlob.sw_external)
			gpreGlob.isc_databases->dbb_scope = DBB_EXTERN;
	}
	else
		/* Load the symbol (hash) table with relation names from this database. */
		MET_load_hash_table(gpreGlob.isc_databases);

	HSH_insert(gpreGlob.isc_databases->dbb_name);

	return action;
}


//____________________________________________________________
//  
//		Process SQL INSERT statement.
//  

static act* act_insert(void)
{
	const TEXT* transaction = NULL;

	par_options(&transaction);

	if (MSC_match(KW_CURSOR))
		return act_insert_blob(transaction);

	if (!MSC_match(KW_INTO))
		CPR_s_error("INTO");

	gpre_req* request = MSC_request(REQ_insert);
	request->req_trans = transaction;
	gpre_ctx* context = SQE_context(request);

	int count = 0, count2 = 0;
	gpre_lls* fields = NULL;

//  Pick up a field list 

	if (!MSC_match(KW_LEFT_PAREN)) {
		GPRE_NOD list = MET_fields(context);
		count = list->nod_count;
		for (int i = 0; i < count; i++)
			MSC_push(list->nod_arg[i], &fields);
	}
	else {
		do {
			GPRE_NOD node = SQE_field(request, false);
			if (node->nod_type == nod_array) {
				node->nod_type = nod_field;

				// Make sure no subscripts are specified  

				if (node->nod_arg[1]) {
					PAR_error("Partial insert of arrays not permitted");
				}
			}

			// Dialect 1 program may not insert new datatypes 
			if ((SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) &&
				(nod_field == node->nod_type))
			{
				const USHORT field_dtype =
					((REF) (node->nod_arg[0]))->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);
				}
			}

			MSC_push(node, &fields);
			count++;
		} while (MSC_match(KW_COMMA));

		EXP_match_paren();
	}

	gpre_lls* values = NULL;
	if (MSC_match(KW_VALUES)) {
		// Now pick up a value list 

		EXP_left_paren(0);
		for (;;) {
			if (MSC_match(KW_NULL))
				MSC_push(MSC_node(nod_null, 0), &values);
			else
				MSC_push(SQE_value(request, false, NULL, NULL), &values);
			count2++;
			if (!(MSC_match(KW_COMMA)))
				break;
		}
		EXP_match_paren();

		// Make an assignment list 

		if (count != count2)
			PAR_error("count of values doesn't match count of columns");

		GPRE_NOD vlist = MSC_node(nod_list, (SSHORT) count);
		request->req_node = vlist;
		GPRE_NOD* ptr = &vlist->nod_arg[count];

		while (values) {
			GPRE_NOD assignment = MSC_node(nod_assignment, 2);
			assignment->nod_arg[0] = (GPRE_NOD) MSC_pop(&values);
			assignment->nod_arg[1] = (GPRE_NOD) MSC_pop(&fields);
			pair(assignment->nod_arg[0], assignment->nod_arg[1]);
			*--ptr = assignment;
		}

		if (context->ctx_symbol)
			HSH_remove(context->ctx_symbol);
		act* action = MSC_action(request, ACT_insert);
		action->act_whenever = gen_whenever();
		return action;
	}

	if (!MSC_match(KW_SELECT))
		CPR_s_error("VALUES or SELECT");

//  OK, we've got a mass insert on our hands.  Start by picking
//  up the select statement.  First, however, remove the INSERT
//  context to avoid resolving SELECT fields to the insert relation. 

	request->req_type = REQ_mass_update;
	request->req_contexts = NULL;
	request->req_update = context;
	gpre_rse* select = SQE_select(request, false);
	request->req_rse = select;
	context->ctx_next = request->req_contexts;
	request->req_contexts = context;

//  Build an assignment list from select expressions into target list 

	GPRE_NOD select_list = select->rse_fields;

	if (count != select_list->nod_count)
		PAR_error("count of values doesn't match count of columns");

	GPRE_NOD alist = MSC_node(nod_list, (SSHORT) count);
	request->req_node = alist;
	GPRE_NOD* ptr = &alist->nod_arg[count];
	GPRE_NOD* ptr2 = &select_list->nod_arg[count];

	while (fields) {
		GPRE_NOD assignment = MSC_node(nod_assignment, 2);
		assignment->nod_arg[0] = *--ptr2;
		assignment->nod_arg[1] = (GPRE_NOD) MSC_pop(&fields);
		pair(assignment->nod_arg[0], assignment->nod_arg[1]);
		*--ptr = assignment;
	}

	GPRE_NOD store = MSC_binary(nod_store, (GPRE_NOD) context, alist);
	request->req_node = store;
	EXP_rse_cleanup(select);
	if (context->ctx_symbol)
		HSH_remove(context->ctx_symbol);

	act* action = MSC_action(request, ACT_loop);
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Process SQL INSERT statement.
//  

// Do I miss anything here? The parameter is not used!
// TEXT is supposedly to be transaction.
static act* act_insert_blob(const TEXT* transaction)
{
//  Handle dynamic SQL statement, if appropriate 

	DYN cursor = par_dynamic_cursor();
	if (cursor) {
		DYN statement = (DYN) MSC_alloc(DYN_LEN);
		statement->dyn_statement_name = cursor->dyn_statement_name;
		statement->dyn_cursor_name = cursor->dyn_cursor_name;
		par_using(statement);
		if (statement->dyn_using)
			PAR_error("Using host-variable list not supported.");

		act* action = (act*) MSC_alloc(ACT_LEN);
		action->act_type = ACT_dyn_insert;
		action->act_object = (REF) statement;
		action->act_whenever = gen_whenever();
		return action;
	}

//  Statement is static SQL 

	gpre_req* request = par_cursor(NULL);
	if (request->req_flags & REQ_sql_blob_open)
		PAR_error("Inserting into a blob being opened is not allowed.");

	act* action = MSC_action(request, ACT_put_segment);
	action->act_whenever = gen_whenever();

	if (!MSC_match(KW_VALUES))
		CPR_s_error("VALUES");

	EXP_left_paren(0);
	action->act_object = (REF) SQE_variable(NULL, false, NULL, NULL);
	if (!action->act_object->ref_null_value)
		PAR_error("A segment length is required.");
	EXP_match_paren();

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL lock statement.
//			Reject
//  

static act* act_lock(void)
{

	PAR_error("SQL LOCK TABLE request not allowed");
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Handle the SQL actions OPEN and CLOSE cursors.
//  

static act* act_openclose( enum act_t type)
{
	const TEXT* transaction = 0;

	if (type == ACT_open)
		par_options(&transaction);

//  Handle dynamic SQL statement, if appropriate 

	DYN cursor = par_dynamic_cursor();
	if (cursor) {
		DYN statement = (DYN) MSC_alloc(DYN_LEN);
		statement->dyn_statement_name = cursor->dyn_statement_name;
		statement->dyn_cursor_name = cursor->dyn_cursor_name;
		act* action = (act*) MSC_alloc(ACT_LEN);
		action->act_object = (REF) statement;
		action->act_whenever = gen_whenever();
		if (type == ACT_open) {
			action->act_type = ACT_dyn_open;
			statement->dyn_trans = transaction;
			par_using(statement);
			if (statement->dyn_using)
				PAR_error("Using host-variable list not supported.");
			par_into(statement);
		}
		else
			action->act_type = ACT_dyn_close;
		return action;
	}

//  Statement is static SQL 

	gpre_sym* symbol = NULL;
	gpre_req* request = par_cursor(&symbol);

	act* action = MSC_action(request, type);
	if (type == ACT_open) {
		open_cursor* open = (open_cursor*) MSC_alloc(OPN_LEN);
		open->opn_trans = transaction;
		open->opn_cursor = symbol;
		action->act_object = (REF) open;
		if (transaction != NULL)
			request->req_trans = transaction;
		if (request->req_flags & (REQ_sql_blob_open | REQ_sql_blob_create)) {
			if (request->req_flags & REQ_sql_blob_open) {
				if (!MSC_match(KW_USING))
					CPR_s_error("USING");
			}
			else if (!MSC_match(KW_INTO))
				CPR_s_error("INTO");
			REF opn_using = (REF) SQE_variable(NULL, false, NULL, NULL);
			open->opn_using = opn_using;
			opn_using->ref_next = request->req_blobs->blb_reference;
			request->req_blobs->blb_reference = opn_using;
			opn_using->ref_context = request->req_contexts;
			opn_using->ref_field = request->req_references->ref_field;
		}
	}
	else
		action->act_object = (REF) symbol;
	action->act_whenever = gen_whenever();

	if (request->req_flags & (REQ_sql_blob_open | REQ_sql_blob_create))
		action->act_type = (type == ACT_close) ?
			ACT_blob_close : (request->req_flags & REQ_sql_blob_open) ?
				ACT_blob_open : ACT_blob_create;

	return action;
}


//____________________________________________________________
//  
//		Parse an "open blob" type statement.
//		These include READ BLOB and INSERT BLOB.
//  

static act* act_open_blob( ACT_T act_op, gpre_sym* symbol)
{
	if (!MSC_match(KW_BLOB))
		CPR_s_error("BLOB");

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

	TOK f_token = (TOK) MSC_alloc(TOK_LEN);
	f_token->tok_length = gpreGlob.token_global.tok_length;

	// Funny, as if we can have relation names up to MAX_SYM_SIZE.
	SQL_resolve_identifier("<column_name>", f_token->tok_string, f_token->tok_length + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Field name too long");
		
	CPR_token();

	if (act_op == ACT_blob_open) {
		if (!MSC_match(KW_FROM))
			CPR_s_error("FROM");
	}
	else if (!MSC_match(KW_INTO))
		CPR_s_error("INTO");

	gpre_req* request = MSC_request(REQ_cursor);
	request->req_flags =
		(act_op == ACT_blob_open) ? REQ_sql_blob_open : REQ_sql_blob_create;

	SCHAR r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];
	SQL_relation_name(r_name, db_name, owner_name);


	SCHAR s[ERROR_LENGTH];
	gpre_rel* relation = SQL_relation(request, r_name, db_name, owner_name, true);
	gpre_fld* field = MET_field(relation, f_token->tok_string);
	if (!field) {
		fb_utils::snprintf(s, sizeof(s),
			"column \"%s\" not in context", f_token->tok_string);
		PAR_error(s);
	}

	if (!(field->fld_flags & FLD_blob)) {
		fb_utils::snprintf(s, sizeof(s),
			"column %s is not a BLOB", field->fld_symbol->sym_string);
		PAR_error(s);
	}

	if (field->fld_array_info) {
		fb_utils::snprintf(s, sizeof(s),
			"column %s is an array and can not be opened as a BLOB",
			field->fld_symbol->sym_string);
		PAR_error(s);
	}

	REF reference = MSC_reference(0);
	reference->ref_field = field;

	gpre_ctx* context = MSC_context(request);
	context->ctx_relation = relation;

	blb* blob = (blb*) MSC_alloc(BLB_LEN);
	blob->blb_symbol = symbol;
	blob->blb_request = request;
	blob->blb_next = request->req_blobs;
	symbol->sym_object = (gpre_ctx*) request;
	symbol->sym_type = SYM_cursor;

	request->req_references = reference;
	request->req_blobs = blob;

	if (MSC_match(KW_FILTER)) {
		if (MSC_match(KW_FROM)) {
			blob->blb_const_from_type =
				PAR_blob_subtype(request->req_database);
			if (gpreGlob.token_global.tok_keyword == KW_CHAR)
				if (blob->blb_const_from_type == isc_blob_text) {
					blob->blb_from_charset = par_char_set();
					if (act_op == ACT_blob_open
						&& blob->blb_from_charset != field->fld_charset_id)
					{
						PAR_error
							("Specified CHARACTER SET does not match BLOB column declaration.");
					}
				}
				else
					PAR_error("Only text BLOBS can specify CHARACTER SET");
			else if (blob->blb_const_from_type == isc_blob_text)
				if (act_op == ACT_blob_create)
					blob->blb_from_charset = CS_dynamic;
				else
					blob->blb_from_charset = field->fld_charset_id;
		}
		else {
			blob->blb_const_from_type = field->fld_sub_type;
			if (blob->blb_const_from_type == isc_blob_text)
				if (act_op == ACT_blob_create)
					blob->blb_from_charset = CS_dynamic;
				else
					blob->blb_from_charset = field->fld_charset_id;
		}
		if (!MSC_match(KW_TO))
			CPR_s_error("TO");
		blob->blb_const_to_type = PAR_blob_subtype(request->req_database);
		if (gpreGlob.token_global.tok_keyword == KW_CHAR)
			if (blob->blb_const_to_type == isc_blob_text) {
				blob->blb_to_charset = par_char_set();
				if (act_op == ACT_blob_create
					&& blob->blb_to_charset != field->fld_charset_id)
				{
					PAR_error
						("Specified CHARACTER SET does not match BLOB column declaration.");
				}
			}
			else
				PAR_error("Only text BLOBS can specify CHARACTER SET");
		else if (blob->blb_const_to_type == isc_blob_text)
			if (act_op == ACT_blob_create)
				blob->blb_to_charset = field->fld_charset_id;
			else
				blob->blb_to_charset = CS_dynamic;
	}
	else {						// No FILTER keyword seen 

		/*
		 *  Even if no FILTER was specified, we set one up for the special
		 *  case of InterBase TEXT blobs, in order to do character set
		 *  transliteration from the column-declared character set of the
		 *  blob to the process character set (CS_dynamic).
		 *
		 *  It is necessary to pass this information in the bpb as blob
		 *  operations are done using blob_ids, and we cannot determine
		 *  this information from the blob_id within the engine.
		 */
		if (field->fld_sub_type == isc_blob_text
			&& (field->fld_charset_id != CS_NONE))
		{
			blob->blb_const_from_type = isc_blob_text;
			blob->blb_const_to_type = isc_blob_text;
			if (act_op == ACT_blob_create) {
				blob->blb_from_charset = CS_dynamic;
				blob->blb_to_charset = field->fld_charset_id;
			}
			else {
				blob->blb_from_charset = field->fld_charset_id;
				blob->blb_to_charset = CS_dynamic;
			}
		}
	}

	if (MSC_match(KW_MAX_SEGMENT))
		blob->blb_seg_length = EXP_USHORT_ordinal(true);
	else
		blob->blb_seg_length = field->fld_seg_length;

	if (!blob->blb_seg_length)
		blob->blb_seg_length = 512;

	act* action = MSC_action(request, ACT_cursor);
	action->act_object = (REF) blob;

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL prepare statement.
//  

static act* act_prepare(void)
{
	if (gpreGlob.isc_databases && gpreGlob.isc_databases->dbb_next) {
		TEXT s[ERROR_LENGTH];
		fb_utils::snprintf(s, sizeof(s),
				"Executing dynamic SQL statement in context of database %s",
				gpreGlob.isc_databases->dbb_name->sym_string);
		CPR_warn(s);
	}

	const TEXT* transaction = NULL;
	par_options(&transaction);

	DYN statement = par_statement();
	statement->dyn_database = gpreGlob.isc_databases;
	statement->dyn_trans = transaction;

	if (MSC_match(KW_INTO)) {
		if (MSC_match(KW_SQL) && !MSC_match(KW_DESCRIPTOR))
			CPR_s_error("INTO SQL DESCRIPTOR sqlda");
		statement->dyn_sqlda = PAR_native_value(false, false);
	}

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

	switch (gpreGlob.sw_sql_dialect) {
	case 1:
		if ((!isQuoted(gpreGlob.token_global.tok_type)) && (!MSC_match(KW_COLON)))
			CPR_s_error(": <string expression>");
		break;

	default:
		if (gpreGlob.token_global.tok_type != tok_sglquoted && (!MSC_match(KW_COLON)))
			CPR_s_error(": <string expression>");
		break;
	}

	statement->dyn_string = PAR_native_value(false, false);

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_dyn_prepare;
	action->act_object = (REF) statement;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Handle the EXECUTE PROCEDURE statement.
//  

static act* act_procedure(void)
{
	gpre_req* request = MSC_request(REQ_procedure);
	par_options(&request->req_trans);

	SCHAR p_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];

	SQL_relation_name(p_name, db_name, owner_name);
	gpre_prc* procedure = SQL_procedure(request, p_name, db_name, owner_name, true);
	
	gpre_lls* values = NULL;

	SSHORT inputs = 0;
	if (!(gpreGlob.token_global.tok_keyword == KW_RETURNING) && !(gpreGlob.token_global.tok_keyword == KW_SEMI_COLON))
	{
		// parse input references

		const bool paren = MSC_match(KW_LEFT_PAREN);
		gpre_fld* field = procedure->prc_inputs;
		REF *ref_ptr = &request->req_values;
		do {
			if (MSC_match(KW_NULL))
				MSC_push(MSC_node(nod_null, 0), &values);
			else {
				REF reference = SQE_parameter(request, false);
				*ref_ptr = reference;
				reference->ref_field = field;
				MSC_push(MSC_unary(nod_value, (GPRE_NOD) reference), &values);
				ref_ptr = &reference->ref_next;
			}
			if (field)
				field = field->fld_next;
			inputs++;
		} while (MSC_match(KW_COMMA));
		if (paren)
			EXP_match_paren();
	}

	SSHORT outputs = 0;
	if (MSC_match(KW_RETURNING)) {
		// parse output references 

		const bool paren = MSC_match(KW_LEFT_PAREN);
		gpre_fld* field = procedure->prc_outputs;
		REF *ref_ptr = &request->req_references;
		do {
			REF reference = (REF) SQE_variable(request, false, NULL, NULL);
			*ref_ptr = reference;
			if (reference->ref_field = field)
				field = field->fld_next;
			ref_ptr = &reference->ref_next;
			outputs++;
		} while (MSC_match(KW_COMMA));
		if (paren)
			EXP_match_paren();
	}

	if (procedure->prc_in_count != inputs)
		PAR_error("count of input values doesn't match count of parameters");
	if (procedure->prc_out_count != outputs)
		PAR_error("count of output values doesn't match count of parameters");

	GPRE_NOD list = MSC_node(nod_list, inputs);
	request->req_node = list;
	GPRE_NOD* ptr = &list->nod_arg[inputs];
	while (values)
		*--ptr = (GPRE_NOD) MSC_pop(&values);

	act* action = MSC_action(request, ACT_procedure);
	action->act_object = (REF) procedure;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Handle the stand alone SQL select statement.
//  

static act* act_select(void)
{
	gpre_req* request = MSC_request(REQ_for);
	par_options(&request->req_trans);
	gpre_rse* select = SQE_select(request, false);
	request->req_rse = select;

	if (!MSC_match(KW_SEMI_COLON)) {
		TEXT s[ERROR_LENGTH];
		fb_utils::snprintf(s, sizeof(s),
			"Expected ';', got %s.", gpreGlob.token_global.tok_string);
		CPR_warn(s);
	}

	if (select->rse_into)
		into(request, select->rse_fields, select->rse_into);

	act* action = MSC_action(request, ACT_select);
	action->act_object = (REF) select->rse_into;
	action->act_whenever = gen_whenever();
	EXP_rse_cleanup(select);
	return action;
}


//____________________________________________________________
//  
//		Parse a SET <something>
//  

static act* act_set(const TEXT* base_directory)
{

	if (MSC_match(KW_TRANSACTION))
		return act_set_transaction();

	if (MSC_match(KW_NAMES))
		return act_set_names();

	if (MSC_match(KW_STATISTICS))
		return act_set_statistics();

	if (MSC_match(KW_SCHEMA) || MSC_match(KW_DATABASE))
		return PAR_database(true, base_directory);

	if (MSC_match(KW_GENERATOR))
		return act_set_generator();

	if (MSC_match(KW_SQL)) {
		if (MSC_match(KW_DIALECT))
			return act_set_dialect();
	}

	CPR_s_error
		("TRANSACTION, NAMES, SCHEMA, DATABASE, GENERATOR, DIALECT or STATISTICS");
	return NULL;				// silence compiler 
}


//____________________________________________________________
//  
//		Parse a SET SQL DIALECT
//  

static act* act_set_dialect(void)
{
	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_sql_dialect;

	USHORT dialect = EXP_USHORT_ordinal(false);
	if ((dialect < 1) || (dialect > 3))
		CPR_s_error("SQL DIALECT 1,2 or 3");

	if (gpreGlob.isc_databases && dialect != gpreGlob.compiletime_db_dialect
		&& gpreGlob.sw_ods_version < 10)
	{
		char warn_mesg[100];
		sprintf(warn_mesg,
				"Pre 6.0 database. Cannot use dialect %d, Resetting to %d\n",
				dialect, SQL_DIALECT_V5);
		dialect = SQL_DIALECT_V5;
		CPR_warn(warn_mesg);
	}
	else if (gpreGlob.isc_databases && dialect != gpreGlob.compiletime_db_dialect) {
		char warn_mesg[100];
		sprintf(warn_mesg,
				"Client dialect set to %d. Compiletime database dialect is %d\n",
				dialect, gpreGlob.compiletime_db_dialect);
		CPR_warn(warn_mesg);
	}

	action->act_object = (REF) MSC_alloc(SDT_LEN);
	((SDT) action->act_object)->sdt_dialect = dialect;

//  Needed because subsequent parsing pass1 looks at sw_Sql_dialect value 
	gpreGlob.sw_sql_dialect = dialect;
	gpreGlob.dialect_specified = true;

	PAR_get_token();
	return action;
}


//____________________________________________________________
//  
//		Parse a SET generator
//  

static act* act_set_generator(void)
{
	gpre_req* request = MSC_request(REQ_set_generator);
	
	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can SET GENERATOR in context of single database only");

	SGEN setgen = (SGEN) MSC_alloc(SGEN_LEN);
	setgen->sgen_name = (TEXT*) MSC_alloc(NAME_SIZE + 1);
	SQL_resolve_identifier("<identifier>", setgen->sgen_name, NAME_SIZE + 1);
	if (gpreGlob.token_global.tok_length >= NAME_SIZE)
		PAR_error("Generator name too long");
		
	if (!MET_generator(setgen->sgen_name, request->req_database)) {
		SCHAR s[ERROR_LENGTH];
		fb_utils::snprintf(s, sizeof(s),
			"generator %s not found", gpreGlob.token_global.tok_string);
		PAR_error(s);
	}
	PAR_get_token();
	MSC_match(KW_TO);
	if ((gpreGlob.sw_sql_dialect == SQL_DIALECT_V5) || (gpreGlob.sw_server_version < 6))
	{
		setgen->sgen_value = EXP_SLONG_ordinal(true);
		setgen->sgen_dialect = SQL_DIALECT_V5;
	}
	else {
// ** dialect is > 1. Server can handle int64 *

		setgen->sgen_int64value = EXP_SINT64_ordinal(true);
		setgen->sgen_dialect = SQL_DIALECT_V5 + 1;
	}

	act* action = (act*) MSC_action(request, ACT_s_start);
	action->act_whenever = gen_whenever();
	action->act_object = (REF) setgen;
	return action;
}


//____________________________________________________________
//  
//		Parse a SET NAMES <charset>;
//  

static act* act_set_names(void)
{
	if (gpreGlob.sw_auto)
		CPR_warn("SET NAMES requires -manual switch to gpre.");

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_noop;

	if (MSC_match(KW_COLON)) {
		/* User is specifying a host variable or string as
		 * the character set.  Make this the run-time set.
		 */
		TEXT* value = PAR_native_value(false, false);
		for (dbb* db = gpreGlob.isc_databases; db; db = db->dbb_next) {
			if (db->dbb_r_lc_ctype) {
				char buffer[ERROR_LENGTH];
				fb_utils::snprintf(buffer, sizeof(buffer),
						"Supersedes runtime character set for database %s",
						(db->dbb_filename) ?
							db->dbb_filename : db->dbb_name->sym_string);
				CPR_warn(buffer);
			}
			db->dbb_r_lc_ctype = value;
		}
	}
	else if (gpreGlob.token_global.tok_type == tok_ident) {
		// User is specifying the name of a character set 
		// Make this the compile time character set 

		TEXT* value = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, value);
		value[gpreGlob.token_global.tok_length] = '\0';

		/* Due to the ambiguities involved in having modules expressed
		 * in multiple compile-time character sets, we disallow it.
		 */
		if (gpreGlob.module_lc_ctype && strcmp(gpreGlob.module_lc_ctype, value) != 0)
			PAR_error("Duplicate declaration of module CHARACTER SET");

		gpreGlob.module_lc_ctype = value;
		for (dbb* db = gpreGlob.isc_databases; db; db = db->dbb_next) {
			if (db->dbb_c_lc_ctype) {
				char buffer[ERROR_LENGTH];
				fb_utils::snprintf(buffer, sizeof(buffer),
					"Supersedes character set for database %s",
					(db->dbb_filename) ?
						db->dbb_filename : db->dbb_name->sym_string);
				CPR_warn(buffer);
			}

			db->dbb_c_lc_ctype = value;
			if (!(db->dbb_flags & DBB_sqlca)) {
				/* Verify that character set name is valid,
				 * this requires a database to be previously declared
				 * so we can resolve against it.
				 * So what if we go through this code once for each database...
				 */
				if (!(MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_charset)))
					PAR_error("The named CHARACTER SET was not found");
			}
		}
		CPR_token();
	}
	else
		CPR_s_error("<character set name> or :<hostvar>");

	return action;
}


//____________________________________________________________
//  
//		Parse a SET statistics
//  

static act* act_set_statistics(void)
{
	gpre_req* request = MSC_request(REQ_ddl);
	STS stats = (STS) MSC_alloc(STS_LEN);

	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next)
		request->req_database = gpreGlob.isc_databases;
	else
		PAR_error("Can SET STATISTICS in context of single database only");

	if (MSC_match(KW_INDEX)) {
		stats->sts_flags = STS_index;
		stats->sts_name = (STR) MSC_alloc(NAME_SIZE + 1);
		SQL_resolve_identifier("<index name>", stats->sts_name->str_string, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("Index name too long");
			
		PAR_get_token();
	}
	else
		CPR_s_error("INDEX");

	act* action = (act*) MSC_action(request, ACT_statistics);
	action->act_object = (REF) stats;
	return action;
}


//____________________________________________________________
//  
//		Generate a set transaction
//  

static act* act_set_transaction(void)
{
	gpre_tra* trans = (gpre_tra*) MSC_alloc(TRA_LEN);

	if (MSC_match(KW_NAME))
		trans->tra_handle = PAR_native_value(false, true);

//  Get all the transaction parameters 

	while (true) {
		if (MSC_match(KW_ISOLATION)) {
			MSC_match(KW_LEVEL);
			if (!par_transaction_modes(trans, true))
				CPR_s_error("SNAPSHOT");

			continue;
		}

		if (par_transaction_modes(trans, false))
			continue;

		if (MSC_match(KW_NO)) {
			if (MSC_match(KW_WAIT)) {
				trans->tra_flags |= TRA_nw;
				continue;
			}
			CPR_s_error("WAIT");
		}

		if (MSC_match(KW_WAIT))
			continue;

#ifdef DEV_BUILD
		if (MSC_match(KW_AUTOCOMMIT)) {
			trans->tra_flags |= TRA_autocommit;
			continue;
		}
#endif

		if (MSC_match(KW_NO_AUTO_UNDO)) {
			trans->tra_flags |= TRA_no_auto_undo;
			continue;
		}

		break;
	}

//  send out for the list of reserved relations 

	if (MSC_match(KW_RESERVING)) {
		trans->tra_flags |= TRA_rrl;
		PAR_reserving(trans->tra_flags, true);
	}
	else if (MSC_match(KW_USING)) {
		trans->tra_flags |= TRA_inc;
		PAR_using_db();
	}

	CMP_t_start(trans);

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_start;
	action->act_object = (REF) trans;
	action->act_whenever = gen_whenever();

	return action;
}


//____________________________________________________________
//  
//		Generate a COMMIT, FINISH, or ROLLBACK.
//  

static act* act_transaction( enum act_t type)
{
	const TEXT* transaction = NULL;

	par_options(&transaction);
	MSC_match(KW_WORK);

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = type;
	action->act_whenever = gen_whenever();
	action->act_object = (REF) transaction;

	if (MSC_match(KW_RELEASE)) {
		type = (type == ACT_commit) ? ACT_finish : ACT_rfinish;
		if (transaction) {
			action->act_rest = (act*) MSC_alloc(ACT_LEN);
			action->act_rest->act_type = type;
		}
		else
			action->act_type = type;
	}
	else if ((type == ACT_commit) && (MSC_match(KW_RETAIN))) {
		MSC_match(KW_SNAPSHOT);
		action->act_type = type = ACT_commit_retain_context;
	}

	return action;
}


//____________________________________________________________
//  
//		Parse an update action.  This is a little more complicated
//		because SQL confuses the update of a cursor with a mass update.
//		The syntax, and therefor the code, I fear, is a mess.
//  

static act* act_update(void)
{
	const TEXT* transaction = NULL;

	par_options(&transaction);

//  First comes the relation.  Unfortunately, there is no way to identify
//  its database until the cursor is known.  Sigh.  Save the token. 

	SCHAR r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];
	SQL_relation_name(r_name, db_name, owner_name);

//  Parse the optional alias (context variable) 

	gpre_sym* alias = (gpreGlob.token_global.tok_symbol) ? NULL : PAR_symbol(SYM_dummy);

//  Now we need the SET list list.  Do this thru a linked list stack 

	if (!MSC_match(KW_SET))
		CPR_s_error("SET");

	gpre_req* request = MSC_request(REQ_mass_update);
	gpre_rel* relation = SQL_relation(request, r_name, db_name, owner_name, true);
	gpre_ctx* input_context = MSC_context(request);
	input_context->ctx_relation = relation;

	if (alias) {
		alias->sym_type = SYM_context;
		alias->sym_object = input_context;
		HSH_insert(alias);
		gpreGlob.token_global.tok_symbol = HSH_lookup(gpreGlob.token_global.tok_string);
	}

	gpre_lls* stack = NULL;
	SSHORT count = 0;

	do {
		GPRE_NOD set_item = MSC_node(nod_assignment, 2);
		set_item->nod_arg[1] = SQE_field(NULL, false);
		if (!MSC_match(KW_EQUALS))
			CPR_s_error("assignment operator");
		if (MSC_match(KW_NULL))
			set_item->nod_arg[0] = MSC_node(nod_null, 0);
		else
			set_item->nod_arg[0] = SQE_value(request, false, NULL, NULL);
		MSC_push(set_item, &stack);
		count++;
	} while (MSC_match(KW_COMMA));

	GPRE_NOD set_list = MSC_node(nod_list, count);
	GPRE_NOD* end_list = set_list->nod_arg + count;
	GPRE_NOD* ptr = end_list;

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

//  Now the moment of truth.  If the next few tokens are WHERE CURRENT OF
//  then this is a sub-action of an existing request.  If not, then it is
//  a free standing request 

	const bool where = MSC_match(KW_WITH);
	if (where && MSC_match(KW_CURRENT)) {
		if (!MSC_match(KW_OF))
			CPR_s_error("OF cursor");

		/* Allocate update block, flush old request block, then
		   find the right request block, and the target relation */

		upd* update = (upd*) MSC_alloc(UPD_LEN);
		update->upd_references = request->req_values;
		MSC_free_request(request);

		request = par_cursor(NULL);
		if (request->req_flags == REQ_sql_blob_create)
			PAR_error("expected a TABLE cursor, got a BLOB cursor");
		if ((transaction || request->req_trans) &&
			(!transaction || !request->req_trans ||
			 strcmp(transaction, request->req_trans)))
		{
			if (transaction)
				PAR_error("different transaction for select and update");
			else {				// does not specify transaction clause in      
				//   "update ... where cuurent of cursor" stmt 
				SSHORT trans_nm_len = strlen(request->req_trans);
				SCHAR* str_2 = (SCHAR *) MSC_alloc(trans_nm_len + 1);
				transaction = str_2;
				const SCHAR* str_1 = request->req_trans;
				do {
					*str_2++ = *str_1++;
				} while (--trans_nm_len);
			}
		}
		request->req_trans = transaction;
		relation = SQL_relation(request, r_name, db_name, owner_name, true);

		// Given the target relation, find the input context for the modify 

		gpre_rse* select = request->req_rse;
		SSHORT i;
		for (i = 0; i < select->rse_count; i++) {
			input_context = select->rse_context[i];
			if (input_context->ctx_relation == relation)
				break;
		}

		if (i == select->rse_count)
			PAR_error("table not in request");

		// Resolve input fields first 

		if (alias) {
			alias->sym_type = SYM_context;
			alias->sym_object = input_context;
			HSH_insert(alias);
		}

		for (ptr = set_list->nod_arg; ptr < end_list; ptr++) {
			GPRE_NOD set_item = *ptr;
			SQE_resolve(set_item->nod_arg[0], request, 0);
			pair(set_item->nod_arg[0], set_item->nod_arg[1]);
		}

		update->upd_request = request;
		update->upd_source = input_context;
		gpre_ctx* update_context = MSC_context(request);
		update->upd_update = update_context;
		update_context->ctx_relation = relation;
		update->upd_assignments = set_list;

		act* action = MSC_action(request, ACT_update);

		// Resolve update fields next 

		if (alias)
			alias->sym_object = update_context;

		for (ptr = set_list->nod_arg; ptr < end_list; ptr++) {
			GPRE_NOD set_item = *ptr;
			SQE_resolve(set_item->nod_arg[1], request, 0);
			REF field_ref = (REF) ((set_item->nod_arg[1])->nod_arg[0]);

			slc* slice = NULL;
			act* slice_action = (act*) field_ref->ref_slice;
			if (slice_action &&
				(slice = (slc*) slice_action->act_object))
			{
				// These gpreGlob.requests got lost in freeing the main request  

				gpre_req* slice_request = slice_action->act_request;
				slice_request->req_next = gpreGlob.requests;
				gpreGlob.requests = slice_request;
				slice->slc_field_ref = field_ref;
				slice->slc_array = (GPRE_NOD) set_item->nod_arg[0];
				slice->slc_parent_request = request;
				slice_action->act_type = ACT_put_slice;

				/* If slice ref is not yet posted, post it.
				   This is required to receive the handle for the
				   array being updated */

				bool found = false;
				for (REF req_ref = request->req_references; req_ref;
					 req_ref = req_ref->ref_next)
				{
					if (req_ref == field_ref) {
						set_item->nod_arg[1]->nod_arg[0] = (GPRE_NOD) req_ref;
						found = true;
						break;
					}
					if ((req_ref->ref_field == field_ref->ref_field) &&
						(req_ref->ref_context == field_ref->ref_context))
					{
						PAR_error
							("Can't update multiple slices of same column");
					}
				}
				if (!found) {
					field_ref->ref_next = request->req_references;
					request->req_references = field_ref;
				}
			}
			pair(set_item->nod_arg[0], set_item->nod_arg[1]);
		}

		action->act_whenever = gen_whenever();
		action->act_object = (REF) update;
		if (alias)
			HSH_remove(alias);
		return action;
	}

	request->req_trans = transaction;

//  How amusing.  After all that work, it wasn't a sub-action at all.
//  Neat.  Take the pieces and build a complete request.  Start by
//  figuring out what database is involved.  

//  Generate record select expression, then resolve input values 

	gpre_rse* select = (gpre_rse*) MSC_alloc(RSE_LEN(1));
	request->req_rse = select;
	select->rse_count = 1;
	select->rse_context[0] = input_context;

	if (!alias && !gpreGlob.token_global.tok_symbol)
		// may have a relation name put parser didn't know it when it parsed it 
		gpreGlob.token_global.tok_symbol = HSH_lookup(gpreGlob.token_global.tok_string);

	for (ptr = set_list->nod_arg; ptr < end_list; ptr++) {
		GPRE_NOD set_item = *ptr;
		SQE_resolve(set_item->nod_arg[0], request, select);
	}

//  Process boolean, if any 

	if (where)
		select->rse_boolean = SQE_boolean(request, 0);

//  Resolve update fields to update context 

	gpre_ctx* update_context = MSC_context(request);
	request->req_update = update_context;
	update_context->ctx_relation = relation;

	if (alias)
		alias->sym_object = update_context;

	for (ptr = set_list->nod_arg; ptr < end_list; ptr++) {
		GPRE_NOD set_item = *ptr;
		SQE_resolve(set_item->nod_arg[1], request, 0);
		REF field_ref = (REF) ((set_item->nod_arg[1])->nod_arg[0]);

		act* slice_action = (act*) field_ref->ref_slice;
		if (slice_action) {
			// Slices not allowed in searched updates  

			PAR_error("Updates of slices not allowed in searched updates");
		}

		/* In dialect 1, neither the value being assigned (nod_arg[0])
		   nor the field to which it is being assigned (nod_arg[1]) may
		   be of a data type added in V6. */
		if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) {
			for (int arg_num = 0; arg_num <= 1; arg_num++)
				if (nod_field == set_item->nod_arg[arg_num]->nod_type) {
					const USHORT field_dtype = ((REF)
						 (set_item->nod_arg[arg_num]->
						  nod_arg[0]))->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);
					}
				}
		}

		pair(set_item->nod_arg[0], set_item->nod_arg[1]);
	}

	GPRE_NOD modify = MSC_node(nod_modify, 1);
	request->req_node = modify;
	modify->nod_arg[0] = set_list;

	act* action = MSC_action(request, ACT_loop);
	action->act_whenever = gen_whenever();

	if (alias)
		HSH_remove(alias);

	return action;
}


//____________________________________________________________
//  
//		Handle an SQL whenever statement.  This is declaratory,
//		rather than a significant action.
//  

static act* act_whenever(void)
{
	global_whenever_list = NULL; // global var

//  Pick up condition 

	USHORT condition;
	if (MSC_match(KW_SQLERROR))
		condition = SWE_error;
	else if (MSC_match(KW_SQLWARNING))
		condition = SWE_warning;
	else if (MSC_match(KW_NOT) && MSC_match(KW_FOUND))
		condition = SWE_not_found;
	else
		CPR_s_error("WHENEVER condition");

//  Pick up action 

	swe* label;
	if (MSC_match(KW_CONTINUE))
		label = NULL;
	else if ((MSC_match(KW_GO) && MSC_match(KW_TO)) || MSC_match(KW_GOTO)) {
		MSC_match(KW_COLON);
		USHORT l = gpreGlob.token_global.tok_length;
		label = (swe*) MSC_alloc(sizeof(swe) + l);
		label->swe_condition = condition;
		label->swe_length = l;
		if (label->swe_length) {
			TEXT* p = label->swe_label;
			const TEXT* q = gpreGlob.token_global.tok_string;
			do {
				*p++ = *q++;
			} while (--l);
		}
		PAR_get_token();
		label->swe_condition = condition;
	}
	else
		CPR_s_error("GO TO or CONTINUE");

//  Set up condition vector 

	global_whenever[condition] = label;

	act* action = (act*) MSC_alloc(ACT_LEN);
	action->act_type = ACT_noop;

	MSC_match(KW_SEMI_COLON);
	return action;
}


//____________________________________________________________
//  
//		Make sure that a file path doesn't contain a
//		Decnet node name.
//  

static bool check_filename(const TEXT * name)
{
	const USHORT l = strlen(name);
	if (!l)
		return true;

	for (const TEXT* p = name; *p; p++)
	{
		if (p[0] == ':' && p[1] == ':')
			return false;
	}

	// CVC: what's this crap? It's totally useless! Maybe a copy/paste?
	//TEXT file_name[256];
	//l = MIN(l, sizeof(file_name) - 1);
	//strncpy(file_name, name, l);
	//file_name[l] = '\0';

	return true;
}


//____________________________________________________________
//  
//		Parse connect options
//  

static void connect_opts(
						 const TEXT** user,
						 const TEXT** password,
						 const TEXT** sql_role,
						 const TEXT** lc_messages,
						 USHORT* buffers)
{
	for (;;) {
		if (MSC_match(KW_CACHE)) {
			*buffers = atoi(gpreGlob.token_global.tok_string);
			PAR_get_token();
			MSC_match(KW_BUFFERS);
		}
		else if (MSC_match(KW_USER))
			*user = SQL_var_or_string(false);
		else if (MSC_match(KW_PASSWORD))
			*password = SQL_var_or_string(false);
		else if (MSC_match(KW_ROLE)) {
			if (gpreGlob.token_global.tok_type == tok_ident) {
				// reserve extra bytes for quotes and NULL
				//TEXT* s = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 3);
				TEXT* s = (TEXT*) MSC_alloc(NAME_SIZE + 2);
											   
				SQL_resolve_identifier("<Role Name>", s + 1, NAME_SIZE);
				s[0] = '\"';
				strcat(s, "\"");
				*sql_role = s;
			}
			else
				*sql_role = SQL_var_or_string(false);
		}
		else if (MSC_match(KW_LC_MESSAGES))
			*lc_messages = SQL_var_or_string(false);
		else
			break;
	}

}

#ifdef FLINT_CACHE
//____________________________________________________________
//  
//		Add a shared cache to an existing database.
//  

static FIL define_cache(void)
{
	FIL file = (FIL) MSC_alloc(sizeof(fil));
	if (isQuoted(gpreGlob.token_global.tok_type)) {
		TEXT* string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		file->fil_name = string;
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
		PAR_get_token();
	}
	else
		CPR_s_error("<quoted filename>");
	if (!check_filename(file->fil_name))
		PAR_error("node name not permitted");	// a node name is not permitted in a shared cache file name 

	if (MSC_match(KW_LENGTH)) {
		file->fil_length = EXP_ULONG_ordinal(true);
		if (file->fil_length < MIN_CACHE_BUFFERS) {
			TEXT err_string[ERROR_LENGTH];
			sprintf(err_string, "Minimum of %d cache pages required",
					MIN_CACHE_BUFFERS);
			PAR_error(err_string);
		}
		MSC_match(KW_PAGES);
	}
	else
		file->fil_length = DEF_CACHE_BUFFERS;	// default cache buffers

	return file;
}
#endif

//____________________________________________________________
//  
//		Add a new file to an existing database.
//  

static FIL define_file(void)
{
	FIL file = (FIL) MSC_alloc(sizeof(fil));
	if (isQuoted(gpreGlob.token_global.tok_type)) {
		TEXT* string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		file->fil_name = string;
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
		gpreGlob.token_global.tok_length += 2;
		PAR_get_token();
	}
	else
		CPR_s_error("<quoted filename>");

	if (!check_filename(file->fil_name))
		PAR_error("node name not permitted");	// A node name is not permitted in a shadow or secondary file name 

	while (true) {
		if (MSC_match(KW_LENGTH)) {
			MSC_match(KW_EQUALS);
			file->fil_length = EXP_ULONG_ordinal(true);
			MSC_match(KW_PAGES);
			MSC_match(KW_PAGE);
		}
		else if (MSC_match(KW_STARTS) || MSC_match(KW_STARTING)) {
			MSC_match(KW_AT);
			MSC_match(KW_PAGE);
			file->fil_start = EXP_ULONG_ordinal(true);
		}
		else
			break;
	}

	return file;
}


//____________________________________________________________
//  
//		define a log file 
//  

static FIL define_log_file(bool log_serial)
{
	FIL file = (FIL) MSC_alloc(sizeof(fil));
	if (isQuoted(gpreGlob.token_global.tok_type)) {
		TEXT* string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
		file->fil_name = string;
		MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
		PAR_get_token();
	}
	else
		CPR_s_error("<quoted filename>");

	if (!check_filename(file->fil_name))
		PAR_error("node name not permitted");	// A node name is not permitted in a shadow or secondary file name 

	while (true) {
		if (MSC_match(KW_SIZE)) {
			MSC_match(KW_EQUALS);
			file->fil_length = EXP_ULONG_ordinal(true);
		}
		else
			break;
	}

	return file;
}


static dbb* dup_dbb(const dbb* db)
{

// ****************************************
//  
//		d u p _ d b b
//  
// ****************************************
//  
//		dirty duplication of a dbb.
//		just memcpy as no memory 
//		is freed in gpre.
//  
// *************************************
	if (!db)
		return NULL;
	dbb* newdb = (dbb*) MSC_alloc(DBB_LEN);
	// CVC: the casts here should be tested and removed.
	memcpy((SCHAR*) newdb, (const SCHAR*) db, DBB_LEN);

	return newdb;
}


//____________________________________________________________
//  
//       Report an error with parameter
//  

static void error(const TEXT* format, const TEXT* string2)
{
	TEXT buffer[ERROR_LENGTH];

	fb_utils::snprintf(buffer, sizeof(buffer), format, string2);
	PAR_error(buffer);
}


//____________________________________________________________
//  
//       Extract string from "string" in
//		token.
//  

static TEXT* extract_string(bool advance_token)
{
	switch (gpreGlob.sw_sql_dialect) {
	case 1:
		if (!isQuoted(gpreGlob.token_global.tok_type))
			CPR_s_error("<string>");
		break;

	default:
		if (gpreGlob.token_global.tok_type != tok_sglquoted)
			CPR_s_error("<string>");
		break;
	}

	TEXT* string = (TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
	MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
	if (advance_token)
		PAR_get_token();
	return string;
}



//____________________________________________________________
//  
//		Generate a linked list of SQL WHENEVER items for error
//		handling.
//  

static swe* gen_whenever(void)
{
	if (global_whenever_list)
		return global_whenever_list;

	global_whenever_list = NULL; // redundant
	swe* label = NULL;

	for (int i = 0; i < SWE_max; i++)
	{
	    const swe* proto = global_whenever[i];
		if (proto) {
			swe* prior = label;
			USHORT l = proto->swe_length;
			label = (swe*) MSC_alloc(sizeof(swe) + l);
			label->swe_next = prior;
			label->swe_condition = proto->swe_condition;
			if (l) {
				TEXT* p = label->swe_label;
				const TEXT* q = proto->swe_label;
				do {
					*p++ = *q++;
				} while (--l);
			}
		}
	}

	return label;
}


//____________________________________________________________
//  
//		Correlate the into list with the select expression list
//		to form full references (post same against request).
//  

static void into( gpre_req* request, GPRE_NOD field_list, GPRE_NOD var_list)
{
	if (!var_list)
		PAR_error("INTO list is required");

	if (!field_list || field_list->nod_count != var_list->nod_count)
		PAR_error
			("column count and number of INTO list host-variables unequal");

	GPRE_NOD* var_ptr = var_list->nod_arg;
	GPRE_NOD* fld_ptr = field_list->nod_arg;

	for (GPRE_NOD* end = fld_ptr + var_list->nod_count; fld_ptr < end;
		 fld_ptr++, var_ptr++)
	{
		REF var_ref = (REF) *var_ptr;
		REF field_ref = NULL;
		gpre_req* slice_req = NULL;
		if (((*fld_ptr)->nod_type == nod_field)
			|| ((*fld_ptr)->nod_type == nod_array))
		{
			field_ref = (REF) (*fld_ptr)->nod_arg[0];
			slice_req = (gpre_req*) (*fld_ptr)->nod_arg[2];
		}

		REF reference = NULL;
		gpre_fld* field;
		if (field_ref && slice_req && (field = field_ref->ref_field)
			&& field->fld_array)
		{
			EXP_post_array(field_ref);

			// If field ref not posted yet, post it   

			bool found = false;
			for (reference = request->req_references; reference;
				 reference = reference->ref_next)
			{
				if (reference == field_ref) {
					found = true;
					break;
				}
			}
			if (!found) {
				field_ref->ref_next = request->req_references;
				request->req_references = field_ref;
			}
			reference = field_ref;
		}
		else
			reference = SQE_post_reference(request, 0, 0, *fld_ptr);
		var_ref->ref_friend = reference;
		reference->ref_null_value = var_ref->ref_null_value;
	}
}


//____________________________________________________________
//  
//		Create field in a relation for a metadata request.
//  

static gpre_fld* make_field( gpre_rel* relation)
{
	char s[NAME_SIZE];

	SQL_resolve_identifier("<column name>", s, NAME_SIZE);
	gpre_fld* field = MET_make_field(s, 0, 0, true);
	field->fld_relation = relation;
	field->fld_flags |= FLD_meta;
	PAR_get_token();

	return field;
}


//____________________________________________________________
//  
//		Create index for metadata request.
//  

static IND make_index( gpre_req* request, const TEXT* string)
{
	IND index = NULL;

	if ((gpreGlob.isc_databases) && (!(gpreGlob.isc_databases->dbb_next))) {
		// CVC: I've kept this silly code. What's the idea of the copy here?
		// If we are trying to limit the index name, the correct length is NAME_SIZE.
		TEXT s[ERROR_LENGTH];
		strncpy(s, string, sizeof(s));
		s[sizeof(s) - 1] = 0;
		index = MET_make_index(s);
		if (request)
			request->req_database = gpreGlob.isc_databases;
		index->ind_flags |= IND_meta;
	}
	else
		PAR_error("Can only reference INDEX in context of single database");

	return index;
}


//____________________________________________________________
//  
//		Create relation for a metadata request.
//  

static gpre_rel* make_relation( gpre_req* request, const TEXT * relation_name)
{
	gpre_rel* relation = NULL;

	if (gpreGlob.isc_databases && !gpreGlob.isc_databases->dbb_next) {
		TEXT r[ERROR_LENGTH];
		strncpy(r, relation_name, sizeof(r));
		r[sizeof(r) - 1] = 0;

		relation = MET_make_relation(r);
		relation->rel_database = gpreGlob.isc_databases;
		relation->rel_meta = true;

		if (request)
			request->req_database = gpreGlob.isc_databases;
	}
	else
		PAR_error("Can only reference TABLE in context of single database");

	return relation;
}


//____________________________________________________________
//  
//		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.
//		In other words, here we are guessing what the datatype is
//		of a host language variable.
//  

static void pair( GPRE_NOD expr, GPRE_NOD field_expr)
{
	REF ref1, ref2;

	if (field_expr->nod_type != nod_field)
		return;

	switch (expr->nod_type) {
	case nod_value:
		ref1 = (REF) field_expr->nod_arg[0];
		ref2 = (REF) expr->nod_arg[0];

		/* We're done if we've already determined the type of this reference
		 * (if, for instance, it's a parameter to a udf or PROCEDURE)
		 */
		if (ref2->ref_field)
			return;

		ref2->ref_field = ref1->ref_field;
		if (ref1->ref_field->fld_array) {
			ref2->ref_context = ref1->ref_context;
			ref2->ref_friend = ref1;
			EXP_post_array(ref1);
			EXP_post_array(ref2);
		}
		return;

	case nod_map_ref:
		{
			MEL element = (MEL) expr->nod_arg[0];
			pair(element->mel_expr, field_expr);
		}
		return;

	case nod_field:
	case nod_literal:
		return;
	}

	gpre_nod** ptr = expr->nod_arg;
	const gpre_nod* const* const end_ptr = ptr + expr->nod_count;

	for (; ptr < end_ptr; ptr++)
		pair(*ptr, field_expr);
}


//____________________________________________________________
//  
//		Parse the multi-dimensional array specification.
//  

static void par_array( gpre_fld* field)
{
	SSHORT i = 0;

//  Pick up ranges 

	ary* array_info = field->fld_array_info;

	while (true) {
		SLONG rangel = EXP_SSHORT_ordinal(true);
		SLONG rangeh;
		if (MSC_match(KW_COLON))
			rangeh = EXP_SSHORT_ordinal(true);
		else {
			if (rangel < 1)
				rangeh = 1;
			else {
				rangeh = rangel;
				rangel = 1;
			}
		}

		if (rangel >= rangeh)
			PAR_error("Start of array range must be less than end of range");

		if (i > MAX_ARRAY_DIMENSIONS)
			PAR_error("Array has too many dimensions");

		array_info->ary_rpt[i].ary_lower = rangel;
		array_info->ary_rpt[i++].ary_upper = rangeh;

		if (MSC_match(KW_R_BRCKET))
			break;

		if (!MSC_match(KW_COMMA))
			CPR_s_error(", (comma)");
	}

	array_info->ary_dimension_count = i;
}


//____________________________________________________________
//  
//  
//		Read in the specified character set on 
//		a READ BLOB or an INSERT BLOB.
//  

static SSHORT par_char_set(void)
{
	if (!MSC_match(KW_CHAR))
		CPR_s_error("CHARACTER SET");

	if (!MSC_match(KW_SET))
		CPR_s_error("CHARACTER SET");

	if (gpreGlob.token_global.tok_type != tok_ident)
		CPR_s_error("<character set name>");

	gpre_sym* symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_charset);
	if (!symbol)
		PAR_error("The named CHARACTER SET was not found");

	PAR_get_token();

	return (((INTLSYM) symbol->sym_object)->intlsym_charset_id);
}


//____________________________________________________________
//  
//		Create a computed field
//  

static void par_computed( gpre_req* request, gpre_fld* field)
{
	MSC_match(KW_BY);

//  If field size has been specified, save it.  Then NULL it
//  till the new size is calculated from the specified expression.
//  This will catch self references.
//  
//  The user specified values will be restored to override calculated
//  values.
//  
	// Don't move this initialization below.
	const gpre_fld save_fld = *field;

	CMPF cmp = (CMPF) MSC_alloc(CMPF_LEN);
	cmp->cmpf_text = CPR_start_text();
	cmp->cmpf_boolean = SQE_value(request, false, NULL, NULL);
	CPR_end_text(cmp->cmpf_text);

	field->fld_computed = cmp;
	field->fld_flags |= FLD_computed;

	CME_get_dtype(field->fld_computed->cmpf_boolean, field);

//  If there was user specified data type/size, restore it 

	if (save_fld.fld_dtype) {
		field->fld_dtype = save_fld.fld_dtype;
		field->fld_length = save_fld.fld_length;
		field->fld_scale = save_fld.fld_scale;
		field->fld_sub_type = save_fld.fld_sub_type;
		field->fld_ttype = save_fld.fld_ttype;
		field->fld_charset_id = save_fld.fld_charset_id;
		field->fld_collate_id = save_fld.fld_collate_id;
		field->fld_char_length = save_fld.fld_char_length;
	}
}


//____________________________________________________________
//  
//		Parse the next token as a cursor name.
//		If it is, return the request associated with the cursor.  If
//		not, produce an error and return NULL.
//  

static gpre_req* par_cursor( gpre_sym** symbol_ptr)
{
// ** 
//   par_cursor() is called to use a previously declared cursor.
//   tok_symbol == NULL means one of the two things.
//   a) The name does not belong to a cursor. OR
//   b) get_token() function in gpre.cpp was not able to find the cursor
//      in hash table.
//  
//   case a) is an error condition.
//   case b) Could have resulted because the cursor name was upcased and
//    inserted into hash table since it was not quoted and it is 
//    now being refered as it was declared.
//  
//   Hence, Try and lookup the cursor name after resolving it once more. If
//   it still cannot be located, Its an error
//*  

	SQL_resolve_identifier("<cursor name>", NULL, MAX_CURSOR_SIZE);
	gpre_sym* symbol = HSH_lookup(gpreGlob.token_global.tok_string);
	gpreGlob.token_global.tok_symbol = symbol;
	if (symbol && symbol->sym_type == SYM_keyword)
		gpreGlob.token_global.tok_keyword = (KWWORDS) symbol->sym_keyword;
	else
		gpreGlob.token_global.tok_keyword = KW_none;

	symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_cursor);
	if (!symbol)
		symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_delimited_cursor);
	if (symbol) {
		PAR_get_token();
		if (symbol_ptr)
			*symbol_ptr = symbol;
		return (gpre_req*) symbol->sym_object;
	}
	else {
		symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_dyn_cursor);
		if (symbol)
			PAR_error("DSQL cursors require DSQL update & delete statements");
	}
	CPR_s_error("<cursor name>");
	return NULL;				// silence compiler
}


//____________________________________________________________
//  
//		If this is a dynamic curser, return dynamic statement block.
//  

static DYN par_dynamic_cursor(void)
{
	gpre_sym* symbol = NULL;

	if (gpreGlob.token_global.tok_symbol == NULL) {
		SQL_resolve_identifier("<cursor name>", NULL, MAX_CURSOR_SIZE);
		gpreGlob.token_global.tok_symbol = symbol = HSH_lookup(gpreGlob.token_global.tok_string);
		if (symbol && symbol->sym_type == SYM_keyword)
			gpreGlob.token_global.tok_keyword = (KWWORDS) symbol->sym_keyword;
		else
			gpreGlob.token_global.tok_keyword = KW_none;
	}
	if (symbol = MSC_find_symbol(gpreGlob.token_global.tok_symbol, SYM_dyn_cursor)) {
		PAR_get_token();
		return (DYN) symbol->sym_object;
	}

	return NULL;
}


//____________________________________________________________
//  
//		Handle an SQL field definition in CREATE, DECLARE or 
//		ALTER TABLE statement.
//  

static gpre_fld* par_field( gpre_req* request, gpre_rel* relation)
{
	gpre_fld* field = make_field(relation);

	SQL_par_field_dtype(request, field, false);

	if (MSC_match(KW_COMPUTED)) {
		if (field->fld_global)
			PAR_error("Cannot use domains to override computed column size");
		if (field->fld_array_info)
			PAR_error("Computed columns cannot be arrays");

		par_computed(request, field);
	}

//  Check if default value was specified 

	if (gpreGlob.token_global.tok_keyword == KW_DEFAULT) {
		field->fld_default_source = CPR_start_text();
		PAR_get_token();

		if (MSC_match(KW_USER))
			field->fld_default_value = MSC_node(nod_user_name, 0);
// ** Begin sql/date/time/timestamp *
		else if (MSC_match(KW_CURRENT_DATE))
			field->fld_default_value = MSC_node(nod_current_date, 0);
		else if (MSC_match(KW_CURRENT_TIME))
			field->fld_default_value = MSC_node(nod_current_time, 0);
		else if (MSC_match(KW_CURRENT_TIMESTAMP))
			field->fld_default_value = MSC_node(nod_current_timestamp, 0);
// ** End sql/date/time/timestamp *
		else if (MSC_match(KW_NULL))
			field->fld_default_value = MSC_node(nod_null, 0);
		else {
			if (MSC_match(KW_MINUS)) {
				if (gpreGlob.token_global.tok_type != tok_number)
					CPR_s_error("<number>");

				GPRE_NOD literal_node = EXP_literal();
				field->fld_default_value = MSC_unary(nod_negate, literal_node);
			}
			else if ((field->fld_default_value = EXP_literal()) == NULL)
				CPR_s_error("<constant>");
		}
		CPR_end_text(field->fld_default_source);
	}


//  Check for any column level constraints 

	cnstrt** constraint_ref = &field->fld_constraints;
	bool in_constraints = true;

	while (in_constraints) {
		switch (gpreGlob.token_global.tok_keyword) {
		case KW_CONSTRAINT:
		case KW_PRIMARY:
		case KW_UNIQUE:
		case KW_REFERENCES:
		case KW_CHECK:
		case KW_NOT:
			*constraint_ref = par_field_constraint(request, field, relation);
			constraint_ref = &(*constraint_ref)->cnstrt_next;
			break;

		default:
			in_constraints = false;
			break;
		}
	}
//    if (MSC_match (KW_NOT))
//   if (MSC_match (KW_NULL))
// { 
// field->fld_flags |= FLD_not_null;
// if (MSC_match (KW_UNIQUE))
//    {
//    index = (IND) MSC_alloc (IND_LEN);
//    index->ind_relation = relation;
//    index->ind_fields = field;
//    index->ind_flags |= IND_dup_flag | IND_meta;
//    index->ind_flags &= ~IND_descend;
//    field->fld_index = index;
//    }
// } 
//   else
// CPR_s_error ("NULL");    

	SQL_par_field_collate(request, field);
	SQL_adjust_field_dtype(field);

	return field;
}


//____________________________________________________________
//  
//		Create a field level constraint as part of CREATE TABLE or
//		ALTER TABLE statement. Constraint maybe table or column level.
//  

static CNSTRT par_field_constraint( gpre_req* request, gpre_fld* for_field,
	gpre_rel* relation)
{
	cnstrt* new_constraint = (cnstrt*) MSC_alloc(CNSTRT_LEN);

	if (gpreGlob.token_global.tok_keyword == KW_CONSTRAINT) {
		PAR_get_token();
		new_constraint->cnstrt_name = (STR) MSC_alloc(NAME_SIZE + 1);
		SQL_resolve_identifier("<constraint name>",
							   new_constraint->cnstrt_name->str_string, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("Constraint name too long");
			
		PAR_get_token();
	}

	STR field_name;
	const enum kwwords keyword = gpreGlob.token_global.tok_keyword;
	switch (keyword) {
	case KW_NOT:
		PAR_get_token();
		if (!MSC_match(KW_NULL))
			CPR_s_error("NULL");
		new_constraint->cnstrt_type = CNSTRT_NOT_NULL;
		for_field->fld_flags |= FLD_not_null;
		break;

	case KW_PRIMARY:
	case KW_UNIQUE:
	case KW_REFERENCES:
		PAR_get_token();
		if (keyword == KW_PRIMARY) {
			if (!MSC_match(KW_KEY))
				CPR_s_error("KEY");
			new_constraint->cnstrt_type = CNSTRT_PRIMARY_KEY;
		}
		else if (keyword == KW_REFERENCES) {
			new_constraint->cnstrt_type = CNSTRT_FOREIGN_KEY;
		}
		else
			new_constraint->cnstrt_type = CNSTRT_UNIQUE;

		// Set field for PRIMARY KEY or FOREIGN KEY or UNIQUE constraint  

		field_name = (STR) MSC_alloc(NAME_SIZE + 1);
		strcpy(field_name->str_string, for_field->fld_symbol->sym_string);
		MSC_push((GPRE_NOD) field_name, &new_constraint->cnstrt_fields);

		if (keyword == KW_REFERENCES) {
			// Relation name for foreign key  

			new_constraint->cnstrt_referred_rel = (STR) MSC_alloc(NAME_SIZE + 1);
			SQL_resolve_identifier("referred <table name>",
								   new_constraint->cnstrt_referred_rel->str_string,
								   NAME_SIZE + 1);
			if (gpreGlob.token_global.tok_length >= NAME_SIZE)
				PAR_error("Referred table name too long");
				
			PAR_get_token();

			if (MSC_match(KW_LEFT_PAREN)) {
				// Field specified for referred relation  

				field_name = (STR) MSC_alloc(NAME_SIZE + 1);
				SQL_resolve_identifier("<column name>", field_name->str_string, NAME_SIZE + 1);
				if (gpreGlob.token_global.tok_length >= NAME_SIZE)
					PAR_error("Referred field name too long");
					
				MSC_push((GPRE_NOD) field_name, &new_constraint->cnstrt_referred_fields);
				CPR_token();
				EXP_match_paren();
			}

			if (gpreGlob.token_global.tok_keyword == KW_ON) {
				par_fkey_extension(new_constraint);
				PAR_get_token();
				if (gpreGlob.token_global.tok_keyword == KW_ON) {
					par_fkey_extension(new_constraint);
					PAR_get_token();
				}
			}
		}
		break;

	case KW_CHECK:
		PAR_get_token();
		new_constraint->cnstrt_type = CNSTRT_CHECK;
		new_constraint->cnstrt_text = CPR_start_text();
		new_constraint->cnstrt_boolean = SQE_boolean(request, 0);
		CPR_end_text(new_constraint->cnstrt_text);
		break;

	default:
		PAR_error("Invalid constraint type");
	}

	return new_constraint;
}


//____________________________________________________________
//  
//		Parse the INTO clause for a dynamic SQL statement.
//      Nobody uses its returned value currently.

static bool par_into( DYN statement)
{

	if (!MSC_match(KW_INTO))
		return false;

	MSC_match(KW_SQL);
	// "SQL" keyword is optional for backward compatibility

	if (!MSC_match(KW_DESCRIPTOR))
		CPR_s_error("DESCRIPTOR");

	statement->dyn_sqlda2 = PAR_native_value(false, false);

	return true;
}


//____________________________________________________________
//  
//		Parse request options.
//  

static void par_options(const TEXT** transaction)
{

	*transaction = NULL;

	if (!MSC_match(KW_TRANSACTION))
		return;

	*transaction = PAR_native_value(false, true);
}


//____________________________________________________________
//  
//		parse the page_size clause of a
//		create database statement
//  

static int par_page_size(void)
{
	MSC_match(KW_EQUALS);
	const int n1 = EXP_USHORT_ordinal(false);
	int n2 = n1;

	if (n1 <= 1024)
		n2 = 1024;
	else if (n1 <= 2048)
		n2 = 2048;
	else if (n1 <= 4096)
		n2 = 4096;
	else if (n1 <= 8192)
		n2 = 8192;
	else if (n1 <= 16384)
	    n2 = 16384;
	else
		CPR_s_error("<page size> in range 1024 to 16384");
	PAR_get_token();

	return n2;
}


//____________________________________________________________
//  
//		Parse the next thing as a relation.
//  

static gpre_rel* par_relation( gpre_req* request)
{
	SCHAR r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE];

	SQL_relation_name(r_name, db_name, owner_name);

	return make_relation(request, r_name);
}


//____________________________________________________________
//  
//		Parse a dynamic statement name returning a dynamic
//		statement block.
//  

static DYN par_statement(void)
{
	DYN statement = (DYN) MSC_alloc(DYN_LEN);
	statement->dyn_statement_name = PAR_symbol(SYM_dummy);
	statement->dyn_database = gpreGlob.isc_databases;

	return statement;
}


//____________________________________________________________
//  
//		Handle an extended foreign key definition as part of CREATE TABLE 
//		or ALTER TABLE statements.
//  

static void par_fkey_extension(cnstrt* cnstrt_val)
{
//
//   Extended foreign key definition could be as follows :
//  
//   [ON DELETE { NO ACTION | CASCADE | SET DEFAULT | SET NULL } ]
//   [ON UPDATE { NO ACTION | CASCADE | SET DEFAULT | SET NULL } ]
//  

	fb_assert(gpreGlob.token_global.tok_keyword == KW_ON);
	fb_assert(cnstrt_val != NULL);

	PAR_get_token();

	const enum kwwords keyword = gpreGlob.token_global.tok_keyword;
	switch (keyword) {
	case KW_DELETE:
		// NOTE: action must be defined only once 
		if (cnstrt_val->cnstrt_fkey_def_type & REF_DELETE_ACTION)
			CPR_s_error("UPDATE");
		else
			cnstrt_val->cnstrt_fkey_def_type |= REF_DELETE_ACTION;
		break;
	case KW_UPDATE:
		// NOTE: action must be defined only once 
		if (cnstrt_val->cnstrt_fkey_def_type & REF_UPDATE_ACTION)
			CPR_s_error("DELETE");
		else
			cnstrt_val->cnstrt_fkey_def_type |= REF_UPDATE_ACTION;
		break;
	default:
		// unexpected keyword 
		CPR_s_error("UPDATE or DELETE");
		break;
	}

	PAR_get_token();

	switch (gpreGlob.token_global.tok_keyword) {
	case KW_NO:
		PAR_get_token();
		if (gpreGlob.token_global.tok_keyword != KW_ACTION)
			CPR_s_error("ACTION");
		else if (keyword == KW_DELETE)
			cnstrt_val->cnstrt_fkey_def_type |= REF_DEL_NONE;
		else
			cnstrt_val->cnstrt_fkey_def_type |= REF_UPD_NONE;
		break;
	case KW_CASCADE:
		if (keyword == KW_DELETE)
			cnstrt_val->cnstrt_fkey_def_type |= REF_DEL_CASCADE;
		else
			cnstrt_val->cnstrt_fkey_def_type |= REF_UPD_CASCADE;
		break;
	case KW_SET:
		PAR_get_token();
		switch (gpreGlob.token_global.tok_keyword) {
		case KW_DEFAULT:
			if (keyword == KW_DELETE)
				cnstrt_val->cnstrt_fkey_def_type |= REF_DEL_SET_DEFAULT;
			else
				cnstrt_val->cnstrt_fkey_def_type |= REF_UPD_SET_DEFAULT;
			break;
		case KW_NULL:
			if (keyword == KW_DELETE)
				cnstrt_val->cnstrt_fkey_def_type |= REF_DEL_SET_NULL;
			else
				cnstrt_val->cnstrt_fkey_def_type |= REF_UPD_SET_NULL;
			break;
		default:
			// unexpected keyword 
			CPR_s_error("NULL or DEFAULT");
			break;
		}
		break;
	default:
		// unexpected keyword 
		CPR_s_error("NO ACTION or CASCADE or SET DEFAULT or SET NULL");
		break;
	}
}


//____________________________________________________________
//  
//		Handle a create constraint verb as part of CREATE TABLE or
//		ALTER TABLE statement. Constraint maybe table or column level.
//  

static CNSTRT par_table_constraint( gpre_req* request, gpre_rel* relation)
{
	cnstrt* constraint = (cnstrt*) MSC_alloc(CNSTRT_LEN);

	if (gpreGlob.token_global.tok_keyword == KW_CONSTRAINT) {
		PAR_get_token();
		constraint->cnstrt_name = (STR) MSC_alloc(NAME_SIZE + 1);
		SQL_resolve_identifier("<constraint name>",
							   constraint->cnstrt_name->str_string, NAME_SIZE + 1);
		if (gpreGlob.token_global.tok_length >= NAME_SIZE)
			PAR_error("Constraint name too long");
			
		PAR_get_token();
	}

	gpre_lls** fields;
	USHORT num_for_key_flds = 0, num_prim_key_flds = 0;

	const enum kwwords keyword = gpreGlob.token_global.tok_keyword;
	switch (keyword) {
	case KW_PRIMARY:
	case KW_UNIQUE:
	case KW_FOREIGN:
		PAR_get_token();
		if (keyword == KW_PRIMARY) {
			if (!MSC_match(KW_KEY))
				CPR_s_error("KEY");
			constraint->cnstrt_type = CNSTRT_PRIMARY_KEY;
		}
		else if (keyword == KW_FOREIGN) {
			if (!MSC_match(KW_KEY))
				CPR_s_error("KEY");
			constraint->cnstrt_type = CNSTRT_FOREIGN_KEY;
		}
		else
			constraint->cnstrt_type = CNSTRT_UNIQUE;

		EXP_left_paren(0);

		/* Get list of fields for PRIMARY KEY or FOREIGN KEY or UNIQUE
		   constraint */

		fields = &constraint->cnstrt_fields;
		do {
			STR field_name = (STR) MSC_alloc(NAME_SIZE + 1);
			SQL_resolve_identifier("<column name>", field_name->str_string, NAME_SIZE + 1);
			if (gpreGlob.token_global.tok_length >= NAME_SIZE)
				PAR_error("Field name too long");
				
			MSC_push((GPRE_NOD) field_name, fields);
			fields = &(*fields)->lls_next;
			++num_for_key_flds;
			CPR_token();
		} while (MSC_match(KW_COMMA));

		if (keyword != KW_FOREIGN)
			num_for_key_flds = 0;

		EXP_match_paren();
		if (keyword == KW_FOREIGN) {
			if (!MSC_match(KW_REFERENCES))
				CPR_s_error("REFERENCES");

			// Relation name for foreign key  

			constraint->cnstrt_referred_rel = (STR) MSC_alloc(NAME_SIZE + 1);
			SQL_resolve_identifier("referred <table name>",
								   constraint->cnstrt_referred_rel->str_string,
								   NAME_SIZE + 1);
			if (gpreGlob.token_global.tok_length >= NAME_SIZE)
				PAR_error("Referred table name too long");
				
			PAR_get_token();

			constraint->cnstrt_referred_fields = NULL;

			if (MSC_match(KW_LEFT_PAREN)) {
				// Fields specified for referred relation  

				fields = &constraint->cnstrt_referred_fields;
				do {
					STR field_name = (STR) MSC_alloc(NAME_SIZE + 1);
					SQL_resolve_identifier("<column name>",
										   field_name->str_string, NAME_SIZE + 1);
					if (gpreGlob.token_global.tok_length >= NAME_SIZE)
						PAR_error("Referred field name too long");
						
					MSC_push((GPRE_NOD) field_name, fields);
					fields = &(*fields)->lls_next;
					++num_prim_key_flds;
					CPR_token();
				} while (MSC_match(KW_COMMA));

				EXP_match_paren();
			}

			/* Don't print error message in case if <referenced column list>
			   is not specified. Try to catch them in cmd.c[create_constraint]
			   later on */
			if (constraint->cnstrt_referred_fields != NULL &&
				num_prim_key_flds != num_for_key_flds)
			{
				PAR_error
					("FOREIGN KEY column count does not match PRIMARY KEY");
			}
			if (gpreGlob.token_global.tok_keyword == KW_ON) {
				par_fkey_extension(constraint);
				PAR_get_token();
				if (gpreGlob.token_global.tok_keyword == KW_ON) {
					par_fkey_extension(constraint);
					PAR_get_token();
				}
			}
		}						// if KW_FOREIGN 
		break;

	case KW_CHECK:
		PAR_get_token();
		constraint->cnstrt_type = CNSTRT_CHECK;
		constraint->cnstrt_text = CPR_start_text();
		constraint->cnstrt_boolean = SQE_boolean(request, 0);
		CPR_end_text(constraint->cnstrt_text);
		break;

	default:
		PAR_error("Invalid constraint type");
	}

	return constraint;
}


//____________________________________________________________
//  
//		Parse the isolation level part.
//		If expect_iso is true, an isolation level is required.
//		Returns true if found a match else false.
//  

static bool par_transaction_modes(gpre_tra* trans,
								  bool expect_iso)
{

	if (MSC_match(KW_READ)) {
		if (MSC_match(KW_ONLY)) {
			if (expect_iso)
				CPR_s_error("SNAPSHOT");

			trans->tra_flags |= TRA_ro;
			return true;
		}
		else if (MSC_match(KW_WRITE)) {
			if (expect_iso)
				CPR_s_error("SNAPSHOT");

			return true;
		}

		if (!(MSC_match(KW_COMMITTED) || MSC_match(KW_UNCOMMITTED)))
			CPR_s_error("COMMITTED");

		trans->tra_flags |= TRA_read_committed;

		if (MSC_match(KW_NO)) {
			if (MSC_match(KW_VERSION))
				return true;
			else if (MSC_match(KW_WAIT)) {
				trans->tra_flags |= TRA_nw;
				return true;
			}
			else
				CPR_s_error("WAIT or VERSION");
		}

		if (MSC_match(KW_VERSION))
			trans->tra_flags |= TRA_rec_version;

		return true;
	}
	else if (MSC_match(KW_SNAPSHOT)) {
		if (MSC_match(KW_TABLE)) {
			trans->tra_flags |= TRA_con;

			MSC_match(KW_STABILITY);
			return true;
		}
		else
			return true;
	}
	return false;
}


//____________________________________________________________
//  
//		Parse the USING clause for a dynamic SQL statement.
//  

static bool par_using( DYN statement)
{

	if (!MSC_match(KW_USING))
		return false;

	MSC_match(KW_SQL);
	// keyword "SQL" is optional for backward compatibility

	if (MSC_match(KW_DESCRIPTOR))
		statement->dyn_sqlda = PAR_native_value(false, false);
	else
		statement->dyn_using = (GPRE_NOD) SQE_list(SQE_variable, NULL, false);

	return true;
}


//____________________________________________________________
//  
//		Figure out the correct dtypes
//  

static USHORT resolve_dtypes(KWWORDS typ,
							 bool sql_date)
{
	TEXT err_mesg[ERROR_LENGTH];

	switch (typ) {
	case KW_DATE:
		if ((gpreGlob.sw_ods_version < 10) || (gpreGlob.sw_server_version < 6))
			switch (gpreGlob.sw_sql_dialect) {
			case 1:
				return dtype_timestamp;
			case 2:
				sprintf(err_mesg,
						"Encountered column type DATE which is ambiguous in dialect %d\n",
						gpreGlob.sw_sql_dialect);
				PAR_error(err_mesg);
				return dtype_unknown;	// TMN: FIX FIX
				/* return; */
			default:
				sprintf(err_mesg,
						"Encountered column type DATE which is not supported in ods version %d\n",
						gpreGlob.sw_ods_version);
				PAR_error(err_mesg);
			}
		else {
		/** column definition SQL DATE is unambiguous in any dialect **/
			if (sql_date)
				return dtype_sql_date;
			switch (gpreGlob.sw_sql_dialect) {
			case 1:
				return dtype_timestamp;
			case 2:
				sprintf(err_mesg,
						"Encountered column type DATE which is ambiguous in dialect %d\n",
						gpreGlob.sw_sql_dialect);
				PAR_error(err_mesg);
				return dtype_unknown;	// TMN: FIX FIX
				/* return; */
			default:
				return dtype_sql_date;
			}
		}
		break;

	case KW_TIME:
		if ((gpreGlob.sw_ods_version < 10) || (gpreGlob.sw_server_version < 6)) {
			sprintf(err_mesg,
					"Encountered column type TIME which is not supported by pre 6.0 Servers\n");
			PAR_error(err_mesg);
			return dtype_unknown;	// TMN: FIX FIX
			/* return; */
		}
		else
			return dtype_sql_time;
		break;

	case KW_TIMESTAMP:
		return dtype_timestamp;
		break;

	default:
		sprintf(err_mesg, "resolve_dtypes(): Unknown dtype %d\n", typ);
		PAR_error(err_mesg);
		break;
	}
//  
//  TMN: FIX FIX Added "return dtype_unknown;" to silence compiler, but
//  this is really a logic error we have to fix.
//  
	return dtype_unknown;
}


//____________________________________________________________
//  
//		Parse the tail of a CREATE DATABASE statement.
//  

static bool tail_database(enum act_t action_type,
						  DBB database)
{
	TEXT* string = NULL;

//  parse options for the database parameter block 

	bool extend_dpb = false;

	while (true) {
		if (MSC_match(KW_LENGTH)) {
			database->dbb_length = EXP_ULONG_ordinal(true);
			MSC_match(KW_PAGES);
		}
		else if (MSC_match(KW_USER)) {
			if (isQuoted(gpreGlob.token_global.tok_type)) {
				database->dbb_c_user = string =
					(TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
				MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);

				// - 2 here ???
				string[gpreGlob.token_global.tok_length - 2] = '\0';
				PAR_get_token();
			}
			else
				/* Some non-C languages (such as Fortran) have problems
				 * with extending the static DPB to add the runtime options
				 * needed for a runtime user name or password.
				 * 11 April 1995
				 */
			if (gpreGlob.sw_language == lang_c) {
				if (!MSC_match(KW_COLON))
					CPR_s_error("<colon> or <quoted string>");
		/** Should I bother about dialects here ?? **/
				if (isQuoted(gpreGlob.token_global.tok_type))
					CPR_s_error("<host variable>");
				database->dbb_r_user = PAR_native_value(false, false);
				extend_dpb = true;
			}
			else
				CPR_s_error("<quoted string>");

		}
		else if (MSC_match(KW_PASSWORD)) {
			if (isQuoted(gpreGlob.token_global.tok_type)) {
				database->dbb_c_password = string =
					(TEXT*) MSC_alloc(gpreGlob.token_global.tok_length + 1);
				MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string);
				string[gpreGlob.token_global.tok_length] = '\0';
				PAR_get_token();
			}
			else
				/* Some non-C languages (such as Fortran) have problems
				 * with extending the static DPB to add the runtime options
				 * needed for a runtime user name or password.
				 * 11 April 1995
				 */
			if (gpreGlob.sw_language == lang_c) {
				if (!MSC_match(KW_COLON))
					CPR_s_error("<colon> or <quoted string>");
				if (isQuoted(gpreGlob.token_global.tok_type))
					CPR_s_error("<host variable>");
				database->dbb_r_password = PAR_native_value(false, false);
				extend_dpb = true;
			}
			else
				CPR_s_error("<quoted string>");
		}
		else if (MSC_match(KW_DEFAULT)) {
			if (!MSC_match(KW_CHAR) || !MSC_match(KW_SET))
				CPR_s_error("CHARACTER SET");
			database->dbb_def_charset = PAR_native_value(false, false);
		}
		else
			break;
	}

	if (action_type == ACT_drop_database) {
		if (MSC_match(KW_CASCADE))
			database->dbb_flags |= DBB_cascade;
		/* TMN: ERROR ERROR we cant return _nothing* from a function returning */
		// a bool. I changed this to false to flag an error, but we have to 
		// look into this. 
		return false;
		/* return; */
	}

//  parse add/drop items 
	bool logdefined = false;
	while (true) {
		MSC_match(KW_ADD);
		if (MSC_match(KW_DROP)) {
			if (MSC_match(KW_LOG_FILE))
				; // Ignore DROP LOG database->dbb_flags |= DBB_drop_log;
			else if (MSC_match(KW_CASCADE))
				database->dbb_flags |= DBB_cascade;
#ifdef FLINT_CACHE
			else if (MSC_match(KW_CACHE))
				database->dbb_flags |= DBB_drop_cache;
			else
				PAR_error("only log files or shared cache can be dropped");	// msg 121 only SECURITY_CLASS, DESCRIPTION and CACHE can be dropped 
#else
			else
				PAR_error("only log files can be dropped");	// msg 121 only SECURITY_CLASS, DESCRIPTION and CACHE can be dropped 
#endif // FLINT_CACHE 

// ****
// else if (MSC_match (KW_DESCRIP))
//    database->dbb_flags	|= DBB_null_description;
// else if (MSC_match (KW_SECURITY_CLASS))
//    database->dbb_flags |= DBB_null_security_class;
//***
		}
// ****
//   else if (gpreGlob.token_global.tok_keyword == KW_DESCRIPTION)
// database->dbb_description = parse_description();
//   else if (MSC_match (KW_SECURITY_CLASS))
// database->dbb_security_class = PARSE_symbol (tok_ident); 
//***
		else if (MSC_match(KW_FILE)) {
			FIL file = define_file();
			file->fil_next = database->dbb_files;
			database->dbb_files = file;
		}
		else if (MSC_match(KW_PAGE_SIZE) || MSC_match(KW_PAGESIZE)) {
			if (action_type == ACT_alter_database)
				PAR_error("PAGE SIZE can not be modified");
			database->dbb_pagesize = par_page_size();
		}
		else if (MSC_match(KW_CHECK_POINT_LEN)) {
			MSC_match(KW_EQUALS);
			EXP_ULONG_ordinal(true); // skip
		}
		else if (MSC_match(KW_NUM_LOG_BUFS)) {
			MSC_match(KW_EQUALS);
			EXP_USHORT_ordinal(true); // skip
		}
		else if (MSC_match(KW_LOG_BUF_SIZE)) {
			MSC_match(KW_EQUALS);
			EXP_USHORT_ordinal(true); // skip
		}
		else if (MSC_match(KW_GROUP_COMMIT_WAIT)) {
			MSC_match(KW_EQUALS);
			EXP_ULONG_ordinal(true); // skip
		}
		else if (MSC_match(KW_LOG_FILE)) {
			if (logdefined)
				PAR_error("log redefinition");
			logdefined = true;
			if (MSC_match(KW_LEFT_PAREN)) {
				while (true) {
					FIL logfile = define_log_file(false);
					logfile->fil_next = database->dbb_logfiles;
					database->dbb_logfiles = logfile;
					if (!MSC_match(KW_COMMA)) {
						EXP_match_paren();
						break;
					}
				}

				if (MSC_match(KW_OVERFLOW))
					define_log_file(true); // skip
				else
					PAR_error
						("Overflow log specification required for this configuration");
			}
			else if (MSC_match(KW_BASE_NAME)) {
				database->dbb_flags |= DBB_log_serial;
				database->dbb_logfiles = define_log_file(true);
			}
		}
#ifdef FLINT_CACHE
		else if (MSC_match(KW_CACHE))
			database->dbb_cache_file = define_cache();
#endif // FLINT_CACHE 
		else
			break;
	}

	return extend_dpb;
}


//____________________________________________________________
//  
//		Upcase a string into another string.
//  

static void to_upcase(const TEXT* p, TEXT* q, int target_size)
{
	UCHAR c;
	USHORT l = 0;

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


//____________________________________________________________
//  
//  To do: move these to the correct position in the file.
// Idea: if we don't need a result in a variable, we don't pass it, since the
// internal buffer will be used instead (in that case, bigger size cannot surpass
// the size of a cursor). We can provide a size smaller than MAX_CURSOR size
// with the internal buffer. We should provide the correct size if we provide
// the output variable to put the result in it.

void SQL_resolve_identifier( const TEXT* err_mesg, TEXT* str_in, int in_size)
{
	static TEXT internal_buffer[MAX_CURSOR_SIZE];
	TEXT* str;
	int len;
	if (str_in)
	{
		str = str_in;
		len = in_size - 1;
	}
	else
	{
		str = internal_buffer;
		len = sizeof(internal_buffer) - 1;
		if (in_size > 0 && in_size <= len)
		    len = in_size - 1;
		else if (in_size > len + 1)
		    PAR_error("Provide your own buffer for sizes bigger than 64.");
	}
	
	TEXT* const tk_string = gpreGlob.token_global.tok_string;

	switch (gpreGlob.sw_sql_dialect) {
	case 2:
		if (gpreGlob.token_global.tok_type == tok_dblquoted)
			PAR_error("Ambiguous use of double quotes in dialect 2");
	case 1:
		if (gpreGlob.token_global.tok_type != tok_ident)
			CPR_s_error(err_mesg);
		else
			to_upcase(tk_string, str, len + 1);
		break;
	case 3:
		if (gpreGlob.token_global.tok_type == tok_dblquoted) {
			// strip_quotes is too dumb to handle C escape sequences
			// or SQL escape sequences in quoted identifiers.
			if (tk_string[0] == '\"')
				strip_quotes(gpreGlob.token_global);
			strncpy(str, tk_string, len);
			str[len] = 0;
		}
		else if (gpreGlob.token_global.tok_type == tok_ident)
			to_upcase(tk_string, str, len + 1);
		else
			CPR_s_error(err_mesg);
		break;
	}
	strcpy(tk_string, str);
}


void SQL_dialect1_bad_type(USHORT field_dtype)
{
	char buffer[200];
	const char* s = "unknown";

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

